@hienlh/ppm 0.19.2 → 0.19.3

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 (617) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/cli-reference.md +4 -4
  5. package/assets/skills/ppm/references/http-api.md +3 -2
  6. package/dist/web/assets/accounts-settings-section-LtSVxNNp.js +1 -0
  7. package/dist/web/assets/ai-resource-editor-BqFT1EUB.js +1 -0
  8. package/dist/web/assets/ai-settings-section-CY6spbEx.js +1 -0
  9. package/dist/web/assets/ai-settings-section-Po3K8j-g.js +1 -0
  10. package/dist/web/assets/{angular-html-BlcrLjUx.js → angular-html-BuHNQIbQ.js} +1 -1
  11. package/dist/web/assets/{angular-ts-BUjtceAJ.js → angular-ts-DHRWoLc-.js} +1 -1
  12. package/dist/web/assets/api-client-BogqsFna.js +1 -0
  13. package/dist/web/assets/api-settings-CTMdz_VF.js +1 -0
  14. package/dist/web/assets/{api-themes-YsZgf6Xj.js → api-themes-Cknp3Q-P.js} +1 -1
  15. package/dist/web/assets/{apl-B3g0v1pk.js → apl-C1VSWVei.js} +1 -1
  16. package/dist/web/assets/appearance-settings-section-L9Z-nej_.js +1 -0
  17. package/dist/web/assets/architecture-PBZL5I3N-Ca5IpdzP.js +1 -0
  18. package/dist/web/assets/arrow-left-CyFcsyzD.js +1 -0
  19. package/dist/web/assets/{astro-FRnyn7cH.js → astro-RX-rChsZ.js} +1 -1
  20. package/dist/web/assets/{audio-preview-DWTRm4HC.js → audio-preview-CCd1jdje.js} +1 -1
  21. package/dist/web/assets/{blade-NLdOqUKw.js → blade-CZ6bxMbK.js} +1 -1
  22. package/dist/web/assets/{pencil-BlIIg6RH.js → brain-KDa9jFRq.js} +1 -1
  23. package/dist/web/assets/{bsl-CenHZfaU.js → bsl-BhgCXLWk.js} +1 -1
  24. package/dist/web/assets/{button-Cq8yixjf.js → button-BZtEJcUv.js} +1 -1
  25. package/dist/web/assets/{cairo-B_WR0aiQ.js → cairo-BrwygPkn.js} +1 -1
  26. package/dist/web/assets/calendar-clock-C1asT-gk.js +1 -0
  27. package/dist/web/assets/chat-tab-BIO-VhWT.js +15 -0
  28. package/dist/web/assets/check-DN75Ucvz.js +1 -0
  29. package/dist/web/assets/chevron-up-8PbsNiJp.js +1 -0
  30. package/dist/web/assets/{cobol-DOynhWYj.js → cobol-C5TGW1Ir.js} +1 -1
  31. package/dist/web/assets/code-editor-B9lVjElM.js +7 -0
  32. package/dist/web/assets/{coffee-Bn6dbw4P.js → coffee-CFgKlmgI.js} +1 -1
  33. package/dist/web/assets/{conflict-editor-Cgb-z12D.js → conflict-editor-BoRPwL3L.js} +1 -1
  34. package/dist/web/assets/context-menu-C13b1uT3.js +1 -0
  35. package/dist/web/assets/{cpp-CoZR9t8r.js → cpp-BhG5q3-d.js} +1 -1
  36. package/dist/web/assets/{crystal-BSheONSg.js → crystal-DvuI7_tY.js} +1 -1
  37. package/dist/web/assets/{csv-preview-BZMHfFH3.js → csv-preview-BpoJqZQi.js} +1 -1
  38. package/dist/web/assets/{data-grid-overlay-editor-gwPoVSCU.js → data-grid-overlay-editor-DP1xkog4.js} +1 -1
  39. package/dist/web/assets/database-viewer-I-9cER1L.js +1 -0
  40. package/dist/web/assets/diff-viewer-_xHZsxvr.js +4 -0
  41. package/dist/web/assets/{docx-preview-DqVDOMmv.js → docx-preview-CWIDVNZn.js} +1 -1
  42. package/dist/web/assets/{edge-DTtoslQu.js → edge-BSIEsmlI.js} +1 -1
  43. package/dist/web/assets/{edit-diff-preview-CXSbSUBX.js → edit-diff-preview-DeHb85DN.js} +1 -1
  44. package/dist/web/assets/{elixir-CIBCZ3Zi.js → elixir-BF-lrGXM.js} +1 -1
  45. package/dist/web/assets/{elm-BLmb7K1H.js → elm-8mCsJOeN.js} +1 -1
  46. package/dist/web/assets/{erb-BBY8ApRd.js → erb-Dw5PUVKa.js} +1 -1
  47. package/dist/web/assets/{erlang-D5Zb5_P0.js → erlang-BvjBIA6O.js} +1 -1
  48. package/dist/web/assets/{esm-6F8v1jLL.js → esm-Bt9OwSnU.js} +1 -1
  49. package/dist/web/assets/explorer-body-DapwsOnp.js +2 -0
  50. package/dist/web/assets/explorer-window-content-DD6Z15pm.js +1 -0
  51. package/dist/web/assets/extension-manager-section-Cu9D9Udt.js +1 -0
  52. package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-Diz2OzVq.js} +1 -1
  53. package/dist/web/assets/{plus-BR0qewJI.js → eye-gWwaF4Lg.js} +1 -1
  54. package/dist/web/assets/eye-off-GjXVuAOn.js +1 -0
  55. package/dist/web/assets/files-settings-section-B1e7BgKQ.js +1 -0
  56. package/dist/web/assets/flask-conical-tp8gyZGh.js +1 -0
  57. package/dist/web/assets/{fortran-fixed-form-CBdwkEUJ.js → fortran-fixed-form-CRjUJe9K.js} +1 -1
  58. package/dist/web/assets/{fsharp-AT4XdVYH.js → fsharp-CjUIHIHp.js} +1 -1
  59. package/dist/web/assets/{gdresource-D852fWBR.js → gdresource-x_NUQFrZ.js} +1 -1
  60. package/dist/web/assets/general-settings-section-DWdHHRDy.js +1 -0
  61. package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-BOpxSbns.js} +1 -1
  62. package/dist/web/assets/git-log-panel-CMMsvU64.js +1 -0
  63. package/dist/web/assets/{git-rebase-C6opk8gr.js → git-rebase-BW4xlK7k.js} +1 -1
  64. package/dist/web/assets/gitGraph-HDMCJU4V-BloZfB0Q.js +1 -0
  65. package/dist/web/assets/{glide-data-grid-CICFjJon.js → glide-data-grid-pKQuyYFj.js} +8 -8
  66. package/dist/web/assets/{glimmer-js-DLi5mWYO.js → glimmer-js-CT9jrmFj.js} +1 -1
  67. package/dist/web/assets/{glimmer-ts-D-scFX30.js → glimmer-ts-CjQT1lxK.js} +1 -1
  68. package/dist/web/assets/{glsl-dbQzF4iA.js → glsl-DsFJvtg5.js} +1 -1
  69. package/dist/web/assets/{graphql-D1m5Wen2.js → graphql-Dhlnmkn_.js} +1 -1
  70. package/dist/web/assets/group-chat-tab-U5u0Loii.js +1 -0
  71. package/dist/web/assets/{hack-CqYmpO7u.js → hack-BKHbsJ7u.js} +1 -1
  72. package/dist/web/assets/{haml-BXzKWEag.js → haml-C1Ih25Y2.js} +1 -1
  73. package/dist/web/assets/{handlebars-DXnrtHtq.js → handlebars-CaFitvmC.js} +1 -1
  74. package/dist/web/assets/{html-DhgQJ-xL.js → html-BwpKIE6V.js} +1 -1
  75. package/dist/web/assets/{html-derivative-xZRft-YE.js → html-derivative-uuTEquZU.js} +1 -1
  76. package/dist/web/assets/{http-B53mSj14.js → http-Cv4Pg5cR.js} +1 -1
  77. package/dist/web/assets/{hurl-CsomVy04.js → hurl-DubkI6TG.js} +1 -1
  78. package/dist/web/assets/{hxml-LffjWwKA.js → hxml-B9OVZFAB.js} +1 -1
  79. package/dist/web/assets/{image-preview-CNA6XMNS.js → image-preview-DRbZ0FA2.js} +1 -1
  80. package/dist/web/assets/index--KWVW-Ga.js +52 -0
  81. package/dist/web/assets/index-CvGx7wBQ.css +2 -0
  82. package/dist/web/assets/info-3K5VOQVL-c0W8wp5o.js +1 -0
  83. package/dist/web/assets/{jinja-CCHURl7V.js → jinja-BHb1jyl6.js} +1 -1
  84. package/dist/web/assets/jira-watcher-section-uA5oyB8s.js +1 -0
  85. package/dist/web/assets/{jison-7qBd8dnR.js → jison-CtpfXDzp.js} +1 -1
  86. package/dist/web/assets/{julia-BY9pnTcy.js → julia-TYjgpazU.js} +1 -1
  87. package/dist/web/assets/{just-D7wRSNRr.js → just-D27bzZuW.js} +1 -1
  88. package/dist/web/assets/key-round-CQpDrDxg.js +1 -0
  89. package/dist/web/assets/keybindings-store-Ccf0etMJ.js +1 -0
  90. package/dist/web/assets/keyboard-shortcuts-section-B7IxJHiI.js +1 -0
  91. package/dist/web/assets/{latex-RgsCae55.js → latex-D_MzopTn.js} +1 -1
  92. package/dist/web/assets/{liquid-CnDFzvOZ.js → liquid-BG_FG0Pe.js} +1 -1
  93. package/dist/web/assets/{lua-0lj5W8sm.js → lua-Bl29oC2L.js} +1 -1
  94. package/dist/web/assets/markdown-renderer-CY1epGRc.js +1 -0
  95. package/dist/web/assets/{markdown-renderer-dbLIvU2M.js → markdown-renderer-DDcIGQEd.js} +3 -3
  96. package/dist/web/assets/{marko-CQgqAhbV.js → marko-D-HI7dbm.js} +1 -1
  97. package/dist/web/assets/{mdc-BkIcTiIy.js → mdc-y444JQTQ.js} +1 -1
  98. package/dist/web/assets/{media-url-DDciwvLU.js → media-url-VzecHk9g.js} +1 -1
  99. package/dist/web/assets/{mobile-bottom-sheet-pjcHKorA.js → mobile-bottom-sheet-BD5b7KJF.js} +1 -1
  100. package/dist/web/assets/mobile-explorer-sheet-CqkwbCRl.js +1 -0
  101. package/dist/web/assets/{nextflow-DvIW68DS.js → nextflow-C5sKcldC.js} +1 -1
  102. package/dist/web/assets/{nginx-O-hft0gG.js → nginx-bXnEC3wm.js} +1 -1
  103. package/dist/web/assets/{nim-BxrR4HwQ.js → nim-pevhf4WD.js} +1 -1
  104. package/dist/web/assets/notification-store-BNSYk3E7.js +1 -0
  105. package/dist/web/assets/notifications-settings-section-v_628h4B.js +1 -0
  106. package/dist/web/assets/{number-overlay-editor-BoLRYuZT.js → number-overlay-editor-B7WINIW4.js} +1 -1
  107. package/dist/web/assets/{overview-panel-l3g1gT5e.js → overview-panel-mhN0qw6Q.js} +1 -1
  108. package/dist/web/assets/packet-RMMSAZCW-D_BoHr_M.js +1 -0
  109. package/dist/web/assets/panel-store-BRrnDO_i.js +1 -0
  110. package/dist/web/assets/pdf-preview-CI3q1MnH.js +1 -0
  111. package/dist/web/assets/{perl-Da-uNqmL.js → perl-B1KUtMnW.js} +1 -1
  112. package/dist/web/assets/{php-JMu45mEx.js → php-BGmVZBAJ.js} +1 -1
  113. package/dist/web/assets/pie-UPGHQEXC-BaLfuEf8.js +1 -0
  114. package/dist/web/assets/{postgres-viewer-DstZwUai.js → postgres-viewer-jaR0FYJ_.js} +3 -3
  115. package/dist/web/assets/power-CriQkeK5.js +1 -0
  116. package/dist/web/assets/ppmbot-settings-section-BET4VmKb.js +1 -0
  117. package/dist/web/assets/{process-table-kmQ2-8qJ.js → process-table-D1r-Gupi.js} +1 -1
  118. package/dist/web/assets/{project-store-DTUCS65m.js → project-store-BuY7rxn_.js} +1 -1
  119. package/dist/web/assets/proxy-settings-section-0DvNh1B-.js +3 -0
  120. package/dist/web/assets/{pug-CLzyQhia.js → pug-BfN8CpEH.js} +1 -1
  121. package/dist/web/assets/{qml-BHXfGB1l.js → qml-D6fRdLZt.js} +1 -1
  122. package/dist/web/assets/query-audit-section-BA5rT6Sh.js +1 -0
  123. package/dist/web/assets/radar-KQ55EAFF-DBIZEaIU.js +1 -0
  124. package/dist/web/assets/{razor-BqetJLN3.js → razor-fg-9AScR.js} +1 -1
  125. package/dist/web/assets/remote-desktop-mobile-sheet-CPa3XElX.js +2 -0
  126. package/dist/web/assets/remote-desktop-mobile-view-De_a4skd.js +1 -0
  127. package/dist/web/assets/remote-desktop-readiness-gate-CPrDIv2d.js +1 -0
  128. package/dist/web/assets/remote-desktop-window-content-CG1StfCE.js +1 -0
  129. package/dist/web/assets/{rst-iQdJZ0BP.js → rst-BZgx076b.js} +1 -1
  130. package/dist/web/assets/{ruby-CNwzZGdH.js → ruby-iUkWDcLo.js} +1 -1
  131. package/dist/web/assets/{run-in-terminal-CBXmVGWa.js → run-in-terminal-bKRwuO2z.js} +1 -1
  132. package/dist/web/assets/{sas-BpBIT2eR.js → sas-CLXO0lZ6.js} +1 -1
  133. package/dist/web/assets/schedules-settings-section-B2f7V7Xr.js +1 -0
  134. package/dist/web/assets/{scss-D1MotuEp.js → scss-DQqteGaG.js} +1 -1
  135. package/dist/web/assets/send-Bb0jtcR6.js +1 -0
  136. package/dist/web/assets/{send-to-chat-DJAdlvJ8.js → send-to-chat-S8LCNX7V.js} +1 -1
  137. package/dist/web/assets/separator-DqGg3f9c.js +1 -0
  138. package/dist/web/assets/settings-body-omv04jbb.js +2 -0
  139. package/dist/web/assets/settings-store-Brjd0QnO.js +2 -0
  140. package/dist/web/assets/settings-tab-NdnBTqyj.js +1 -0
  141. package/dist/web/assets/settings-window-content-Chou1JGa.js +1 -0
  142. package/dist/web/assets/{shaderlab-BNqmcPQi.js → shaderlab-CeMTqagR.js} +1 -1
  143. package/dist/web/assets/{shellsession-BBLOVOO-.js → shellsession-BAkquZAV.js} +1 -1
  144. package/dist/web/assets/{soy-BpTGo9uy.js → soy-Bxm9Ez55.js} +1 -1
  145. package/dist/web/assets/{sparql-D7A3yKob.js → sparql-C1phwdLc.js} +1 -1
  146. package/dist/web/assets/sql-query-editor-Dk9y3jqy.js +1 -0
  147. package/dist/web/assets/sqlite-viewer-4xFhkLYy.js +1 -0
  148. package/dist/web/assets/{stata-B7N56ptS.js → stata-DGNMQdEb.js} +1 -1
  149. package/dist/web/assets/{surrealql-CTmnD-lv.js → surrealql-ByVrV320.js} +1 -1
  150. package/dist/web/assets/{svelte-BppwY9TR.js → svelte-CNWjIMhl.js} +1 -1
  151. package/dist/web/assets/system-monitor-body-B_1iMVOI.js +2 -0
  152. package/dist/web/assets/system-monitor-tab-B2j9mWBW.js +1 -0
  153. package/dist/web/assets/{system-monitor-window-content-B9Y98UW6.js → system-monitor-window-content-BOjq53uU.js} +1 -1
  154. package/dist/web/assets/{tab-host-window-content-C1j8zr68.js → tab-host-window-content-CW_dlYlW.js} +1 -1
  155. package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-PZsP5ZwG.js} +1 -1
  156. package/dist/web/assets/team-member-sheet-CkR4qO7V.js +2 -0
  157. package/dist/web/assets/team-member-window-content-CmF5ia3n.js +1 -0
  158. package/dist/web/assets/{templ-CvTMKKra.js → templ-Cf0gQL4I.js} +1 -1
  159. package/dist/web/assets/{terminal-tab-B9I_HlES.js → terminal-tab-HXZkicIc.js} +3 -3
  160. package/dist/web/assets/{tex--c4POEUE.js → tex-BTIqPw_R.js} +1 -1
  161. package/dist/web/assets/tool-cards-jA1BTLZj.js +9 -0
  162. package/dist/web/assets/treemap-KZPCXAKY-DFiG9UYW.js +1 -0
  163. package/dist/web/assets/{ts-tags--kx7D-Hw.js → ts-tags-B8dN_Yf6.js} +1 -1
  164. package/dist/web/assets/{twig-UG9uMHGd.js → twig-BgemonbJ.js} +1 -1
  165. package/dist/web/assets/use-accounts-data-BI_5qNvr.js +1 -0
  166. package/dist/web/assets/{use-blob-url-Bdxopwwc.js → use-blob-url-Gfi69PtP.js} +1 -1
  167. package/dist/web/assets/use-monaco-theme-BcQqKVbE.js +1 -0
  168. package/dist/web/assets/{use-open-team-member-D9EOB8-i.js → use-open-team-member-CruUTqzD.js} +1 -1
  169. package/dist/web/assets/use-remote-desktop-display-choice-DY5BWRTo.js +1 -0
  170. package/dist/web/assets/{use-websocket-xOOwBdwP.js → use-websocket-DetaY8OI.js} +1 -1
  171. package/dist/web/assets/{vendor-mermaid-0Nt-ew76.js → vendor-mermaid-BKWscKt0.js} +1 -1
  172. package/dist/web/assets/video-preview-C0uH2228.js +1 -0
  173. package/dist/web/assets/{vue-jjTomlbt.js → vue-D3D4lbXz.js} +1 -1
  174. package/dist/web/assets/{vue-html-8KEW6-I9.js → vue-html-DYEQFAiK.js} +1 -1
  175. package/dist/web/assets/{vue-vine-C5tohCkU.js → vue-vine-D0OPNscN.js} +1 -1
  176. package/dist/web/assets/{window-pip-registry-FdZdGF7h.js → window-pip-registry-BEN0QNSW.js} +1 -1
  177. package/dist/web/assets/window-store-DGKf9rPG.js +1 -0
  178. package/dist/web/assets/ws-auth-Dawx17PM.js +1 -0
  179. package/dist/web/assets/{xml-ByFFosqL.js → xml-DHaWgpya.js} +1 -1
  180. package/dist/web/assets/{xsl-CZUwiLkj.js → xsl-DVRN8JsA.js} +1 -1
  181. package/dist/web/index.html +32 -55
  182. package/dist/web/sw.js +1 -1
  183. package/docs/codebase-summary.md +9 -5
  184. package/docs/lessons-learned.md +11 -0
  185. package/package.json +2 -2
  186. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  187. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  188. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  189. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  190. package/src/providers/codex-app-server/codex-event-mapper.ts +70 -5
  191. package/src/providers/codex-app-server/codex-history.ts +24 -0
  192. package/src/providers/codex-app-server/codex-jsonrpc-client.ts +39 -2
  193. package/src/providers/codex-app-server/codex-protocol.ts +24 -0
  194. package/src/providers/codex-app-server/codex-provider.ts +159 -17
  195. package/src/providers/codex-app-server/codex-rollout-items.ts +149 -0
  196. package/src/providers/codex-app-server/codex-skill-parser.ts +63 -0
  197. package/src/providers/codex-app-server/codex-usage-fetch.ts +22 -20
  198. package/src/providers/codex-app-server/codex-usage-source.ts +37 -0
  199. package/src/server/routes/chat.ts +67 -9
  200. package/src/server/routes/codex-accounts.ts +9 -5
  201. package/src/server/ws/chat.ts +46 -1
  202. package/src/services/claude-usage-source.ts +40 -0
  203. package/src/services/claude-usage.service.ts +77 -146
  204. package/src/services/codex-account-login.ts +123 -55
  205. package/src/services/codex-account.service.ts +27 -4
  206. package/src/services/db.service.ts +161 -20
  207. package/src/services/edge-forwarder.ts +191 -191
  208. package/src/services/fs-credential-path-guard.ts +35 -8
  209. package/src/services/fs-path-guard.service.ts +1 -0
  210. package/src/services/postgres.service.ts +144 -72
  211. package/src/services/provider-usage/index.ts +63 -0
  212. package/src/services/provider-usage/register-usage-sources.ts +18 -0
  213. package/src/services/provider-usage/usage-memory-cache.ts +71 -0
  214. package/src/services/provider-usage/usage-registry.ts +157 -0
  215. package/src/services/provider-usage/usage-scheduler.ts +117 -0
  216. package/src/services/provider-usage/usage-snapshot-store.ts +126 -0
  217. package/src/services/provider-usage/usage-source.ts +82 -0
  218. package/src/services/slash-discovery/builtin-commands.ts +13 -0
  219. package/src/services/slash-discovery/codex-skill-items.ts +57 -0
  220. package/src/services/slash-discovery/provider-skill-sigil.ts +19 -0
  221. package/src/services/slash-discovery/types.ts +19 -0
  222. package/src/services/windows-process-tree.ts +351 -351
  223. package/src/types/chat.ts +7 -1
  224. package/src/web/app.tsx +1 -7
  225. package/src/web/components/chat/chat-history-bar.tsx +10 -1
  226. package/src/web/components/chat/chat-tab.tsx +17 -1
  227. package/src/web/components/chat/message-input.tsx +11 -4
  228. package/src/web/components/chat/message-list.tsx +14 -0
  229. package/src/web/components/chat/slash-command-picker.tsx +22 -3
  230. package/src/web/components/chat/tool-cards.tsx +47 -4
  231. package/src/web/components/chat/usage-badge.tsx +43 -447
  232. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  233. package/src/web/components/floating-window/window-store-types.ts +1 -1
  234. package/src/web/components/layout/command-palette.tsx +2 -2
  235. package/src/web/components/layout/mobile-drawer.tsx +5 -4
  236. package/src/web/components/layout/mobile-nav.tsx +5 -1
  237. package/src/web/components/layout/nav-section-rail.tsx +2 -1
  238. package/src/web/components/layout/project-bottom-sheet.tsx +3 -9
  239. package/src/web/components/layout/sidebar.tsx +0 -2
  240. package/src/web/components/layout/tab-pop-out-menu-item.tsx +3 -2
  241. package/src/web/components/settings/accounts/account-add-dialog.tsx +155 -0
  242. package/src/web/components/settings/accounts/account-backup-format.ts +19 -0
  243. package/src/web/components/settings/accounts/account-bucket-row.tsx +55 -0
  244. package/src/web/components/settings/accounts/account-card.tsx +127 -0
  245. package/src/web/components/settings/accounts/account-delete-confirm.tsx +62 -0
  246. package/src/web/components/settings/accounts/account-export-dialog.tsx +154 -0
  247. package/src/web/components/settings/accounts/account-export-simulation.tsx +196 -0
  248. package/src/web/components/settings/accounts/account-import-dialog.tsx +72 -0
  249. package/src/web/components/settings/accounts/account-profile-panel.tsx +52 -0
  250. package/src/web/components/{chat → settings/accounts}/account-rotation-settings.tsx +1 -1
  251. package/src/web/components/settings/accounts/account-token-test-dialog.tsx +147 -0
  252. package/src/web/components/settings/accounts/account-usage-format.ts +92 -0
  253. package/src/web/components/{chat/usage-pattern-chart.tsx → settings/accounts/account-usage-pattern-chart.tsx} +1 -1
  254. package/src/web/components/settings/accounts/accounts-pane-header.tsx +93 -0
  255. package/src/web/components/settings/accounts/accounts-settings-section.tsx +80 -0
  256. package/src/web/components/settings/accounts/claude-accounts-section.tsx +168 -0
  257. package/src/web/components/settings/accounts/codex-accounts-section.tsx +137 -0
  258. package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +130 -0
  259. package/src/web/components/settings/accounts/codex-backup-dialog.tsx +101 -0
  260. package/src/web/components/settings/accounts/codex-rotation-dialog.tsx +65 -0
  261. package/src/web/components/settings/accounts/use-accounts-data.ts +81 -0
  262. package/src/web/components/settings/accounts/use-codex-accounts.ts +151 -0
  263. package/src/web/components/settings/ai-settings-section.tsx +14 -4
  264. package/src/web/components/settings/appearance-settings-section.tsx +125 -0
  265. package/src/web/components/settings/general-settings-section.tsx +87 -0
  266. package/src/web/components/settings/jira-watcher-section.tsx +34 -0
  267. package/src/web/components/settings/notifications-settings-section.tsx +37 -0
  268. package/src/web/components/settings/open-settings.ts +42 -0
  269. package/src/web/components/settings/resolve-open-settings-action.ts +45 -0
  270. package/src/web/components/settings/settings-body.tsx +110 -0
  271. package/src/web/components/settings/settings-categories.ts +102 -0
  272. package/src/web/components/settings/settings-category-list.tsx +56 -0
  273. package/src/web/components/settings/settings-category-rail.tsx +62 -0
  274. package/src/web/components/settings/settings-section-content.tsx +43 -0
  275. package/src/web/components/settings/settings-tab.tsx +15 -329
  276. package/src/web/components/settings/settings-window-content.tsx +24 -0
  277. package/src/web/hooks/use-chat.ts +38 -1
  278. package/src/web/hooks/use-global-keybindings.ts +3 -4
  279. package/src/web/hooks/use-url-sync.ts +30 -0
  280. package/src/web/hooks/use-usage.ts +23 -11
  281. package/src/web/lib/sidebar-tabs/tab-registry.ts +9 -9
  282. package/src/web/lib/slash-items-cache.ts +27 -7
  283. package/src/web/stores/panel-utils.ts +11 -0
  284. package/src/web/stores/settings-store.ts +4 -2
  285. package/src/web/stores/window-panel-actions.ts +1 -3
  286. package/src/web/stores/window-panel-persistence.ts +9 -4
  287. package/dist/web/assets/adaptive-context-menu-BbuRVTLn.js +0 -1
  288. package/dist/web/assets/ai-resource-editor-BCCOUzox.js +0 -1
  289. package/dist/web/assets/ai-settings-section-BEM1z7kp.js +0 -1
  290. package/dist/web/assets/api-client-D1CePgQ4.js +0 -1
  291. package/dist/web/assets/api-settings-CNtKkHqW.js +0 -1
  292. package/dist/web/assets/api-settings-Cubuqjsu.js +0 -1
  293. package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +0 -1
  294. package/dist/web/assets/chat-tab-CmXGsJGc.js +0 -15
  295. package/dist/web/assets/clipboard-B-gwJ8DF.js +0 -1
  296. package/dist/web/assets/code-editor-DtHXzNZ1.js +0 -7
  297. package/dist/web/assets/context-menu-DfWZAxQy.js +0 -1
  298. package/dist/web/assets/database-viewer-GVaQVMdb.js +0 -1
  299. package/dist/web/assets/dialog-BSfuiTkJ.js +0 -1
  300. package/dist/web/assets/diff-viewer-BG5_fH1E.js +0 -4
  301. package/dist/web/assets/explorer-body-BPFnjxYj.js +0 -2
  302. package/dist/web/assets/explorer-window-content-Da1T9uM4.js +0 -1
  303. package/dist/web/assets/extension-store-Bhu--fPX.js +0 -1
  304. package/dist/web/assets/external-link-B6Hl0U4X.js +0 -1
  305. package/dist/web/assets/eye-off-CUdzgmX8.js +0 -1
  306. package/dist/web/assets/file-store-6HW2Lfl4.js +0 -1
  307. package/dist/web/assets/git-log-panel-DNC9fV0M.js +0 -1
  308. package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +0 -1
  309. package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +0 -1
  310. package/dist/web/assets/index-DTqbMiVc.js +0 -52
  311. package/dist/web/assets/index-SFFv2-VM.css +0 -2
  312. package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +0 -1
  313. package/dist/web/assets/input-D0ol5jW0.js +0 -1
  314. package/dist/web/assets/keybindings-store-BGkB72gM.js +0 -1
  315. package/dist/web/assets/keybindings-store-Bq6VWl5X.js +0 -1
  316. package/dist/web/assets/lock-CNxUGpTk.js +0 -1
  317. package/dist/web/assets/markdown-renderer-DEAVpo0C.js +0 -1
  318. package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +0 -1
  319. package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +0 -1
  320. package/dist/web/assets/notification-store-Dgbsw9p9.js +0 -1
  321. package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +0 -1
  322. package/dist/web/assets/panel-store-UOV8wrfR.js +0 -1
  323. package/dist/web/assets/pdf-preview-B2CJ_EKv.js +0 -1
  324. package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +0 -1
  325. package/dist/web/assets/play-DpEEBJTT.js +0 -1
  326. package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +0 -1
  327. package/dist/web/assets/refresh-cw-BQU5g2CY.js +0 -1
  328. package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +0 -2
  329. package/dist/web/assets/remote-desktop-mobile-view-C1ST4-Fm.js +0 -1
  330. package/dist/web/assets/remote-desktop-readiness-gate-BH3BE5wA.js +0 -1
  331. package/dist/web/assets/remote-desktop-window-content-BaZJQt_N.js +0 -1
  332. package/dist/web/assets/rotate-ccw-CpjRo90J.js +0 -1
  333. package/dist/web/assets/scroll-area-lc0ZHORr.js +0 -1
  334. package/dist/web/assets/send-C_sLFt56.js +0 -1
  335. package/dist/web/assets/settings-store-zbPkG7o-.js +0 -2
  336. package/dist/web/assets/settings-tab-Cn-AqUP_.js +0 -1
  337. package/dist/web/assets/settings-tab-xyRpt2gq.js +0 -3
  338. package/dist/web/assets/shallow-CmPMAPte.js +0 -1
  339. package/dist/web/assets/sql-query-editor-B86NiZHI.js +0 -1
  340. package/dist/web/assets/sqlite-viewer-DB775F6C.js +0 -1
  341. package/dist/web/assets/switch-BmjKoIpR.js +0 -1
  342. package/dist/web/assets/system-monitor-body-BDxHQvy9.js +0 -2
  343. package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +0 -1
  344. package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +0 -2
  345. package/dist/web/assets/team-member-window-content-CUSuRHCB.js +0 -1
  346. package/dist/web/assets/tool-cards-D3KSzd2K.js +0 -8
  347. package/dist/web/assets/trash-2-By4VMbC4.js +0 -1
  348. package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +0 -1
  349. package/dist/web/assets/triangle-alert-CUTURFbG.js +0 -1
  350. package/dist/web/assets/upload-BNlX8D07.js +0 -1
  351. package/dist/web/assets/use-monaco-theme-CVCye2kq.js +0 -1
  352. package/dist/web/assets/use-remote-desktop-display-choice-neUqyE3p.js +0 -1
  353. package/dist/web/assets/video-preview-DvpgBDhM.js +0 -1
  354. package/dist/web/assets/window-store-D0x_fxr0.js +0 -1
  355. package/src/web/components/chat/account-dialogs.tsx +0 -384
  356. package/src/web/components/settings/accounts-settings-section.tsx +0 -1090
  357. package/src/web/components/settings/codex-accounts-section.tsx +0 -198
  358. package/src/web/components/settings/telegram-settings-section.tsx +0 -113
  359. /package/dist/web/assets/{abap-C5DFF0s3.js → abap-h3QyH8MW.js} +0 -0
  360. /package/dist/web/assets/{actionscript-3-B0qyRNbc.js → actionscript-3-mkxkq-FN.js} +0 -0
  361. /package/dist/web/assets/{ada-bAJzplug.js → ada-DhISK_EH.js} +0 -0
  362. /package/dist/web/assets/{andromeeda-DUCpISy6.js → andromeeda-DnlWWesX.js} +0 -0
  363. /package/dist/web/assets/{apache-BhAr84Z_.js → apache-P5AK3sfA.js} +0 -0
  364. /package/dist/web/assets/{apex-CRv8YnNq.js → apex-CJUPehg2.js} +0 -0
  365. /package/dist/web/assets/{api-client-BzWM7XPR.js → api-client-oY1tC3tM.js} +0 -0
  366. /package/dist/web/assets/{applescript-CD-Cw8bt.js → applescript-Dx6GGbay.js} +0 -0
  367. /package/dist/web/assets/{apply-theme-DJ794zSQ.js → apply-theme-DxTXOAcV.js} +0 -0
  368. /package/dist/web/assets/{ara-BUzeQYoG.js → ara-95Kx3qKb.js} +0 -0
  369. /package/dist/web/assets/{asciidoc-CT2hTg-v.js → asciidoc-Dp0RbIej.js} +0 -0
  370. /package/dist/web/assets/{asm-CAu35JeY.js → asm-C82coUjU.js} +0 -0
  371. /package/dist/web/assets/{aurora-x-BeFJoIA1.js → aurora-x-BFc3zeop.js} +0 -0
  372. /package/dist/web/assets/{awk-Vau-DrGN.js → awk-ChjC0icr.js} +0 -0
  373. /package/dist/web/assets/{ayu-dark-CLe-OTsQ.js → ayu-dark-DYfUf_PB.js} +0 -0
  374. /package/dist/web/assets/{ayu-light-CJbQfu4M.js → ayu-light-Bh6O0plO.js} +0 -0
  375. /package/dist/web/assets/{ayu-mirage-GWaKwdrx.js → ayu-mirage-DlNhHTGq.js} +0 -0
  376. /package/dist/web/assets/{ballerina-DWu0USI5.js → ballerina-Ceoll3Sk.js} +0 -0
  377. /package/dist/web/assets/{bat-_07Huw7E.js → bat-C7QS6Lfq.js} +0 -0
  378. /package/dist/web/assets/{beancount-EcvogrcF.js → beancount-5NMEAZgs.js} +0 -0
  379. /package/dist/web/assets/{berry-BBB87SY7.js → berry-BBL83Vbk.js} +0 -0
  380. /package/dist/web/assets/{bibtex-DbueS87A.js → bibtex-L7rPkPSG.js} +0 -0
  381. /package/dist/web/assets/{bicep-tcmXnUo4.js → bicep-BOwHIINh.js} +0 -0
  382. /package/dist/web/assets/{bird2-B2qwilIR.js → bird2-CWLsMom2.js} +0 -0
  383. /package/dist/web/assets/{c-Z58oivSt.js → c-CD4Ku5Cr.js} +0 -0
  384. /package/dist/web/assets/{c3-U40cGT9p.js → c3-CpMl-w3G.js} +0 -0
  385. /package/dist/web/assets/{cadence-BFcD-LyM.js → cadence-CvgF6ehN.js} +0 -0
  386. /package/dist/web/assets/{can-open-in-ppm-CdCK2c2a.js → can-open-in-ppm-j3jh7fUo.js} +0 -0
  387. /package/dist/web/assets/{catppuccin-frappe-RkC9AX6J.js → catppuccin-frappe-eo_dTm3F.js} +0 -0
  388. /package/dist/web/assets/{catppuccin-latte-Bsi4l-nO.js → catppuccin-latte-DoiMCU_7.js} +0 -0
  389. /package/dist/web/assets/{catppuccin-macchiato-B8JIdTW-.js → catppuccin-macchiato-UQxsK2Vh.js} +0 -0
  390. /package/dist/web/assets/{catppuccin-mocha-oH8wwrDj.js → catppuccin-mocha-J0i-8RNy.js} +0 -0
  391. /package/dist/web/assets/{clarity-C3WNeRCR.js → clarity-DXYM8-SA.js} +0 -0
  392. /package/dist/web/assets/{clojure-CFoAz7u9.js → clojure-DzamsL69.js} +0 -0
  393. /package/dist/web/assets/{cmake-Bcsqp9yR.js → cmake-CR6RP_T_.js} +0 -0
  394. /package/dist/web/assets/{codeowners-DC2Z7heP.js → codeowners-D1s6-IyY.js} +0 -0
  395. /package/dist/web/assets/{codeql-BfG68Al5.js → codeql-DAqNWMkS.js} +0 -0
  396. /package/dist/web/assets/{common-lisp-Z5P-lKQe.js → common-lisp-8e-KtitB.js} +0 -0
  397. /package/dist/web/assets/{coq-BJslqrnv.js → coq-UEq0WUcO.js} +0 -0
  398. /package/dist/web/assets/{csharp-DhhZTOTs.js → csharp-wxJ0TuCm.js} +0 -0
  399. /package/dist/web/assets/{css-DiFIpMfE.js → css-Cerub-yB.js} +0 -0
  400. /package/dist/web/assets/{csv-DViLbHxI.js → csv-Bh9_WD_h.js} +0 -0
  401. /package/dist/web/assets/{csv-parser-B2v8MoUy.js → csv-parser-BPIDt6ni.js} +0 -0
  402. /package/dist/web/assets/{cue-CoG3mg2I.js → cue-rzM1ztqU.js} +0 -0
  403. /package/dist/web/assets/{cypher-jTxC8NAB.js → cypher-DGaTQ8m0.js} +0 -0
  404. /package/dist/web/assets/{d-6hKIUAbU.js → d-DbkZGuhB.js} +0 -0
  405. /package/dist/web/assets/{dark-plus-D_-np2Sr.js → dark-plus-BO2bzZcA.js} +0 -0
  406. /package/dist/web/assets/{dart-CVpxLpVn.js → dart-BDhCoqVg.js} +0 -0
  407. /package/dist/web/assets/{data-grid-types-Dq_H1IPa.js → data-grid-types-CdAg-4W1.js} +0 -0
  408. /package/dist/web/assets/{dax-BPqJ8qIY.js → dax-Bh03stWh.js} +0 -0
  409. /package/dist/web/assets/{desktop-u_y5_Hr5.js → desktop-BOdr_fwq.js} +0 -0
  410. /package/dist/web/assets/{diff-Duj1XMrd.js → diff-Bb_gTsS2.js} +0 -0
  411. /package/dist/web/assets/{dist-CEZ4plgH.js → dist-BUMDu0av.js} +0 -0
  412. /package/dist/web/assets/{dist-aDSxTR_v.js → dist-DonmFWG8.js} +0 -0
  413. /package/dist/web/assets/{docker-8UGSCc7N.js → docker-BDsTF5ce.js} +0 -0
  414. /package/dist/web/assets/{dotenv-DVnj8kyR.js → dotenv-CbCWp9Ti.js} +0 -0
  415. /package/dist/web/assets/{dracula-D0cEmSg7.js → dracula-Cd1fxvoX.js} +0 -0
  416. /package/dist/web/assets/{dracula-soft-DfGS5Yfr.js → dracula-soft-B7eXkfXG.js} +0 -0
  417. /package/dist/web/assets/{dream-maker-qG8kMGTF.js → dream-maker-XlzuS21R.js} +0 -0
  418. /package/dist/web/assets/{emacs-lisp-iVqyMLyT.js → emacs-lisp-CXV1Ki_7.js} +0 -0
  419. /package/dist/web/assets/{everforest-dark-Cruygd5U.js → everforest-dark-DuqR60Dg.js} +0 -0
  420. /package/dist/web/assets/{everforest-light-BiJa8RHL.js → everforest-light-q4BPnenD.js} +0 -0
  421. /package/dist/web/assets/{fennel-H6XSEIoz.js → fennel-B-e0TX2Q.js} +0 -0
  422. /package/dist/web/assets/{fish-ho_7CxX6.js → fish-Bzb6-TtF.js} +0 -0
  423. /package/dist/web/assets/{fluent-1l5ssJTF.js → fluent-B-szH2-f.js} +0 -0
  424. /package/dist/web/assets/{folder-symlink-Dx1hOk8X.js → folder-symlink-fQRoG_rV.js} +0 -0
  425. /package/dist/web/assets/{format-bytes-gQw5wzqa.js → format-bytes-CKXk9k2d.js} +0 -0
  426. /package/dist/web/assets/{fortran-free-form-Bo1x233A.js → fortran-free-form-OS6SGYC0.js} +0 -0
  427. /package/dist/web/assets/{gdscript-DffFMp_k.js → gdscript-FYq4OP2d.js} +0 -0
  428. /package/dist/web/assets/{gdshader-CvnpAiAF.js → gdshader-BpG6P8rw.js} +0 -0
  429. /package/dist/web/assets/{genie-2eOvAEzW.js → genie-BS9RLAna.js} +0 -0
  430. /package/dist/web/assets/{gherkin-DL-5OUlV.js → gherkin-B_9FdtGs.js} +0 -0
  431. /package/dist/web/assets/{github-dark-DrTZprgG.js → github-dark-cfaIlnBV.js} +0 -0
  432. /package/dist/web/assets/{github-dark-default-_mDH8IMP.js → github-dark-default-B2Wv65He.js} +0 -0
  433. /package/dist/web/assets/{github-dark-dimmed-C48hJuLn.js → github-dark-dimmed-CtM9X76s.js} +0 -0
  434. /package/dist/web/assets/{github-dark-high-contrast-BQ-zGGNe.js → github-dark-high-contrast-DLHyk0tG.js} +0 -0
  435. /package/dist/web/assets/{github-light-BYc90Yx8.js → github-light-DWEI10iG.js} +0 -0
  436. /package/dist/web/assets/{github-light-default-Daecj4J5.js → github-light-default-DcJkK1YN.js} +0 -0
  437. /package/dist/web/assets/{github-light-high-contrast-DoafCbsU.js → github-light-high-contrast-Bcx9iKDt.js} +0 -0
  438. /package/dist/web/assets/{gleam-hUWWjmnp.js → gleam-DVpiM6b2.js} +0 -0
  439. /package/dist/web/assets/{gn-7PJ3R70o.js → gn-B80DNc0i.js} +0 -0
  440. /package/dist/web/assets/{gnuplot-V-Pxck7b.js → gnuplot-D9i3MaT-.js} +0 -0
  441. /package/dist/web/assets/{go-ARF6DprD.js → go-CwCBf_Pt.js} +0 -0
  442. /package/dist/web/assets/{groovy-CKGBIPOz.js → groovy-D7mEfwmW.js} +0 -0
  443. /package/dist/web/assets/{gruvbox-dark-hard-Cm9wqdI3.js → gruvbox-dark-hard-C4R4MOoH.js} +0 -0
  444. /package/dist/web/assets/{gruvbox-dark-medium-Cmsd6dG8.js → gruvbox-dark-medium-CZnFC0s8.js} +0 -0
  445. /package/dist/web/assets/{gruvbox-dark-soft-BBGlGGCz.js → gruvbox-dark-soft-DFcJlEBl.js} +0 -0
  446. /package/dist/web/assets/{gruvbox-light-hard-BCR7KmqG.js → gruvbox-light-hard-B6IF_1L-.js} +0 -0
  447. /package/dist/web/assets/{gruvbox-light-medium-CP32bga8.js → gruvbox-light-medium-C19AZJv2.js} +0 -0
  448. /package/dist/web/assets/{gruvbox-light-soft-qjxk8ps6.js → gruvbox-light-soft-BAnpDm9B.js} +0 -0
  449. /package/dist/web/assets/{haskell-C5DzwGRa.js → haskell-CsXSYXRG.js} +0 -0
  450. /package/dist/web/assets/{haxe-C8f_jCVv.js → haxe-B1uyiI9t.js} +0 -0
  451. /package/dist/web/assets/{hcl-DetJkf6E.js → hcl-DlYKn5q9.js} +0 -0
  452. /package/dist/web/assets/{hjson-DeZhwIdo.js → hjson-C_0VMGJJ.js} +0 -0
  453. /package/dist/web/assets/{hlsl-SMIDLRyX.js → hlsl-zt2PEzbg.js} +0 -0
  454. /package/dist/web/assets/{horizon-BMAVDxMx.js → horizon-6wQfyOK0.js} +0 -0
  455. /package/dist/web/assets/{horizon-bright-3BaV_VGD.js → horizon-bright-CqI2qTim.js} +0 -0
  456. /package/dist/web/assets/{houston-BWBbEPvb.js → houston-BljpHDoG.js} +0 -0
  457. /package/dist/web/assets/{hy-DcsmE-Nm.js → hy-XzuL55dw.js} +0 -0
  458. /package/dist/web/assets/{image-gallery-Bqb-wjNw.js → image-gallery-Cj8nd4xW.js} +0 -0
  459. /package/dist/web/assets/{imba-CnkgnYMf.js → imba-CqhhjBrP.js} +0 -0
  460. /package/dist/web/assets/{ini-BnvTdau7.js → ini-CiFDi6cY.js} +0 -0
  461. /package/dist/web/assets/{java-BLFACXat.js → java-OsQgTL5-.js} +0 -0
  462. /package/dist/web/assets/{javascript-BKcFWENk.js → javascript-qWAOScxl.js} +0 -0
  463. /package/dist/web/assets/{json-3AJtJADf.js → json-RiAKsaD1.js} +0 -0
  464. /package/dist/web/assets/{json5-DLyDVlwe.js → json5-BeCnS3H2.js} +0 -0
  465. /package/dist/web/assets/{jsonc-B5DgF1jW.js → jsonc-CbTW1Mka.js} +0 -0
  466. /package/dist/web/assets/{jsonl-CV_O71a2.js → jsonl-BLSi_669.js} +0 -0
  467. /package/dist/web/assets/{jsonnet-DZfPLgYz.js → jsonnet-C3xAggou.js} +0 -0
  468. /package/dist/web/assets/{jssm-Cr1zYOsY.js → jssm-CLdiagC9.js} +0 -0
  469. /package/dist/web/assets/{jsx-VN-JS2_q.js → jsx-lYE1lb50.js} +0 -0
  470. /package/dist/web/assets/{kanagawa-dragon-D9CcT3xg.js → kanagawa-dragon-Cd1vcrpD.js} +0 -0
  471. /package/dist/web/assets/{kanagawa-lotus-DLXsB8fr.js → kanagawa-lotus-Dw3QKtk9.js} +0 -0
  472. /package/dist/web/assets/{kanagawa-wave-weDs3Wjz.js → kanagawa-wave-DIq5Zv5G.js} +0 -0
  473. /package/dist/web/assets/{kdl-0XF56jnT.js → kdl-BfDV0825.js} +0 -0
  474. /package/dist/web/assets/{keyboard-DBBaoQSu.js → keyboard-DiOYrs6C.js} +0 -0
  475. /package/dist/web/assets/{kotlin-BPkH9vzk.js → kotlin-DONxR4wD.js} +0 -0
  476. /package/dist/web/assets/{kusto-DMVpADb9.js → kusto-BDP63Fh5.js} +0 -0
  477. /package/dist/web/assets/{laserwave-Cl6T2cGP.js → laserwave-BeBrWApL.js} +0 -0
  478. /package/dist/web/assets/{lean-BvwzQGl1.js → lean-DOZc0N1A.js} +0 -0
  479. /package/dist/web/assets/{less-DI_03xkC.js → less-C3H9NlDv.js} +0 -0
  480. /package/dist/web/assets/{lib-v9x_qXgI.js → lib-xrSJ4EZX.js} +0 -0
  481. /package/dist/web/assets/{libesm-dcbw4_18.js → libesm-DLBE59ul.js} +0 -0
  482. /package/dist/web/assets/{light-plus-hIiIsQN_.js → light-plus-C_kTL608.js} +0 -0
  483. /package/dist/web/assets/{llvm-B5hjjSWc.js → llvm-Dsp27U3P.js} +0 -0
  484. /package/dist/web/assets/{loader-circle-DouKgIXy.js → loader-circle-DPMrnLQ2.js} +0 -0
  485. /package/dist/web/assets/{log-D28raQiE.js → log-BywQaTCy.js} +0 -0
  486. /package/dist/web/assets/{logo-6Cdd4ASb.js → logo-BirJgciO.js} +0 -0
  487. /package/dist/web/assets/{luau-CK9nKpOX.js → luau-Btrz5kJB.js} +0 -0
  488. /package/dist/web/assets/{make-CVnxKCB6.js → make-Kl7nFQrS.js} +0 -0
  489. /package/dist/web/assets/{markdown-B9mMQzD5.js → markdown-qsC4P8S-.js} +0 -0
  490. /package/dist/web/assets/{material-theme-BZSs2o0j.js → material-theme-CC__ui3p.js} +0 -0
  491. /package/dist/web/assets/{material-theme-darker-BeAQBzQ_.js → material-theme-darker-D4FxG2FZ.js} +0 -0
  492. /package/dist/web/assets/{material-theme-lighter-BAe0IXcs.js → material-theme-lighter-zGqiWcxn.js} +0 -0
  493. /package/dist/web/assets/{material-theme-ocean-CiPIf0Np.js → material-theme-ocean-CZIdTTB1.js} +0 -0
  494. /package/dist/web/assets/{material-theme-palenight-DJBb4_XN.js → material-theme-palenight-CEIs0GcK.js} +0 -0
  495. /package/dist/web/assets/{matlab-CY0dbp_s.js → matlab-Cf0aUjN9.js} +0 -0
  496. /package/dist/web/assets/{mdx-DU9zbF7h.js → mdx-CIsXjH86.js} +0 -0
  497. /package/dist/web/assets/{mermaid-UmmjGwdh.js → mermaid-B06WO9Qc.js} +0 -0
  498. /package/dist/web/assets/{min-dark-C59GkzOm.js → min-dark-z8fyp0FT.js} +0 -0
  499. /package/dist/web/assets/{min-light-B9QglrdQ.js → min-light-Dr-qesbW.js} +0 -0
  500. /package/dist/web/assets/{mipsasm-Dc6Jsr_D.js → mipsasm-CWHzYLcE.js} +0 -0
  501. /package/dist/web/assets/{mojo-D5ij-nGW.js → mojo-D9zXrKmv.js} +0 -0
  502. /package/dist/web/assets/{monitor-CxPV_fQI.js → monitor-C835ucWH.js} +0 -0
  503. /package/dist/web/assets/{monokai-QAtE_yCP.js → monokai-4b-AKOpX.js} +0 -0
  504. /package/dist/web/assets/{moonbit-BMtIdiTL.js → moonbit-Dl4dhOZJ.js} +0 -0
  505. /package/dist/web/assets/{move-BB_jMHYZ.js → move-DTYKJmXy.js} +0 -0
  506. /package/dist/web/assets/{narrat-BvvoKGVn.js → narrat-DnvXOS-o.js} +0 -0
  507. /package/dist/web/assets/{nextflow-groovy-CqxtAsnF.js → nextflow-groovy-Di8QnEXg.js} +0 -0
  508. /package/dist/web/assets/{night-owl-CLoE9kjt.js → night-owl-Czh0yq-H.js} +0 -0
  509. /package/dist/web/assets/{night-owl-light-BwIFT5Xd.js → night-owl-light-D_OpZqOR.js} +0 -0
  510. /package/dist/web/assets/{nix-BKlnVT9r.js → nix-2MvcjpK3.js} +0 -0
  511. /package/dist/web/assets/{nord-B7R1KRAK.js → nord-CG_r4cyk.js} +0 -0
  512. /package/dist/web/assets/{nushell-CzUme-NJ.js → nushell-DINskMcU.js} +0 -0
  513. /package/dist/web/assets/{objective-c-D-UKKiOc.js → objective-c-Dv7BIicR.js} +0 -0
  514. /package/dist/web/assets/{objective-cpp-BucWn4Fx.js → objective-cpp-CpqsCxPb.js} +0 -0
  515. /package/dist/web/assets/{ocaml-EJCNXMGo.js → ocaml-DwTsYSmW.js} +0 -0
  516. /package/dist/web/assets/{odin-CEjnRU_3.js → odin-G-4O4k4t.js} +0 -0
  517. /package/dist/web/assets/{one-dark-pro-Bv469NX5.js → one-dark-pro-13Pv5SjI.js} +0 -0
  518. /package/dist/web/assets/{one-light-BlzO5RWI.js → one-light-Bi2e86mS.js} +0 -0
  519. /package/dist/web/assets/{openscad-Czl6alLw.js → openscad-D0WsOTij.js} +0 -0
  520. /package/dist/web/assets/{pascal-BI9fLvG4.js → pascal-DmDStuXr.js} +0 -0
  521. /package/dist/web/assets/{pkl-ChQLsyjU.js → pkl-Bl6Qjtuo.js} +0 -0
  522. /package/dist/web/assets/{plastic-DGL3mtdL.js → plastic-UeSVSjTI.js} +0 -0
  523. /package/dist/web/assets/{plsql-BJDjqcWp.js → plsql-DreFC689.js} +0 -0
  524. /package/dist/web/assets/{po-DK5O3OpF.js → po-ZIjccxsX.js} +0 -0
  525. /package/dist/web/assets/{poimandres-CnuqZT4T.js → poimandres-CvQHJPrI.js} +0 -0
  526. /package/dist/web/assets/{polar-DCsUxSsQ.js → polar-BrMB0Kgh.js} +0 -0
  527. /package/dist/web/assets/{portal-container-context-D-495m5C.js → portal-container-context-a399Hd9F.js} +0 -0
  528. /package/dist/web/assets/{postcss-8UjdFmis.js → postcss-pgUsil1T.js} +0 -0
  529. /package/dist/web/assets/{powerquery-CAp0CGQj.js → powerquery-CTsZkCg_.js} +0 -0
  530. /package/dist/web/assets/{powershell-CbBsKZJP.js → powershell-CcwVuBAp.js} +0 -0
  531. /package/dist/web/assets/{prisma-BnNpXCJN.js → prisma-spjnAI5W.js} +0 -0
  532. /package/dist/web/assets/{prolog-CC-l9E8Y.js → prolog-ChCkZpDO.js} +0 -0
  533. /package/dist/web/assets/{proto-CV_1k2rL.js → proto-wVGqK4gB.js} +0 -0
  534. /package/dist/web/assets/{puppet-2D8HU2bv.js → puppet-CED7iwPQ.js} +0 -0
  535. /package/dist/web/assets/{purescript-BEJAORgU.js → purescript-u9lGiQKV.js} +0 -0
  536. /package/dist/web/assets/{python-BBdbb7nn.js → python-DeqTx6ef.js} +0 -0
  537. /package/dist/web/assets/{qmldir-D5ZYI4Ie.js → qmldir-BH1uoPrX.js} +0 -0
  538. /package/dist/web/assets/{qss-BOJVGyXE.js → qss-CUVKA_13.js} +0 -0
  539. /package/dist/web/assets/{r-B5dlEtkj.js → r-BqdPzqYH.js} +0 -0
  540. /package/dist/web/assets/{racket-BUAEygJ6.js → racket-C2eTgPN3.js} +0 -0
  541. /package/dist/web/assets/{raku-BG0Ss0ID.js → raku-uAay1sWx.js} +0 -0
  542. /package/dist/web/assets/{react-B549sK_T.js → react-X4jUntEf.js} +0 -0
  543. /package/dist/web/assets/{red-C5yyQbtG.js → red-DTZP1ZfC.js} +0 -0
  544. /package/dist/web/assets/{reg-DHoBWtLf.js → reg-Cdk3YsxY.js} +0 -0
  545. /package/dist/web/assets/{regexp-DDtJrlKv.js → regexp-Bpa2aS7K.js} +0 -0
  546. /package/dist/web/assets/{rel-CR6WJDRD.js → rel-bNsb1-3L.js} +0 -0
  547. /package/dist/web/assets/{resolve-theme-4fexHWl6.js → resolve-theme-DQk_YcsT.js} +0 -0
  548. /package/dist/web/assets/{riscv-2V9tC-nx.js → riscv-DSaubByW.js} +0 -0
  549. /package/dist/web/assets/{ron-QLTmqGUw.js → ron-DbQzYM6i.js} +0 -0
  550. /package/dist/web/assets/{rose-pine-be0__X3A.js → rose-pine-C7RsprTu.js} +0 -0
  551. /package/dist/web/assets/{rose-pine-dawn-n_WQJwsW.js → rose-pine-dawn-VP976YzL.js} +0 -0
  552. /package/dist/web/assets/{rose-pine-moon-t2aLtzgB.js → rose-pine-moon-XHZNhOqd.js} +0 -0
  553. /package/dist/web/assets/{rosmsg-Ce1JD8vt.js → rosmsg-BVQlx0Lj.js} +0 -0
  554. /package/dist/web/assets/{rust-5fHH-ZoW.js → rust-VHaQ3rh3.js} +0 -0
  555. /package/dist/web/assets/{sass-DPmGFiOJ.js → sass-CO-kMkwD.js} +0 -0
  556. /package/dist/web/assets/{save-DYLiVzU8.js → save-CipGrG1k.js} +0 -0
  557. /package/dist/web/assets/{scala-BRMVFbZF.js → scala-cY0GXkiz.js} +0 -0
  558. /package/dist/web/assets/{scheme-BMSJWevF.js → scheme-D4LNZE0X.js} +0 -0
  559. /package/dist/web/assets/{sdbl-DTZ-pEHI.js → sdbl-Bh2Fd2cn.js} +0 -0
  560. /package/dist/web/assets/{shellscript-6-9yg5Cz.js → shellscript-KKclmCnt.js} +0 -0
  561. /package/dist/web/assets/{shield-check-CUI36aSc.js → shield-check-BsG3LV8i.js} +0 -0
  562. /package/dist/web/assets/{shield-off-GdkkVhb3.js → shield-off-7jKWpJBD.js} +0 -0
  563. /package/dist/web/assets/{slack-dark-DKJgiR7e.js → slack-dark-D6YQ-fqf.js} +0 -0
  564. /package/dist/web/assets/{slack-ochin-WjehVgyK.js → slack-ochin-CDf66CDj.js} +0 -0
  565. /package/dist/web/assets/{smalltalk-BChf4SN1.js → smalltalk-D2OH_QIl.js} +0 -0
  566. /package/dist/web/assets/{snazzy-light-C1WAwOx6.js → snazzy-light-CQeF8G1K.js} +0 -0
  567. /package/dist/web/assets/{solarized-dark-BxREZrGx.js → solarized-dark-LTPf1iLZ.js} +0 -0
  568. /package/dist/web/assets/{solarized-light-DG29B1pR.js → solarized-light-Ose1n88k.js} +0 -0
  569. /package/dist/web/assets/{solidity-bDbtGwdR.js → solidity-lFuOMHki.js} +0 -0
  570. /package/dist/web/assets/{splunk-Ceqx5njI.js → splunk-Bov-sB5S.js} +0 -0
  571. /package/dist/web/assets/{sql-OwMEKbz8.js → sql-BKyZ6wmk.js} +0 -0
  572. /package/dist/web/assets/{ssh-config-WAR3lu2k.js → ssh-config-CqJoMGMb.js} +0 -0
  573. /package/dist/web/assets/{stylus-Cjmoz3d8.js → stylus-Bf5pWRLI.js} +0 -0
  574. /package/dist/web/assets/{swift-S_ZSBG1q.js → swift-D5d1hn_u.js} +0 -0
  575. /package/dist/web/assets/{synthwave-84-DiUeWMSh.js → synthwave-84-B4c6YAgO.js} +0 -0
  576. /package/dist/web/assets/{system-verilog-RRjqPjUV.js → system-verilog-D7fXyDsI.js} +0 -0
  577. /package/dist/web/assets/{systemd-BD3SkXoU.js → systemd-oNagzlBK.js} +0 -0
  578. /package/dist/web/assets/{table-D5Q4ap4W.js → table-CRJsmfeg.js} +0 -0
  579. /package/dist/web/assets/{talonscript-DWefISOm.js → talonscript-C6Nqm9Dm.js} +0 -0
  580. /package/dist/web/assets/{tasl-DCjSS-09.js → tasl-CaWy-cBX.js} +0 -0
  581. /package/dist/web/assets/{tcl-DTYqT_66.js → tcl-C435P5Mq.js} +0 -0
  582. /package/dist/web/assets/{terraform-CkbP5QnR.js → terraform-CVDxqdPf.js} +0 -0
  583. /package/dist/web/assets/{text-wrap-DBvQD3kc.js → text-wrap-oiekm5SB.js} +0 -0
  584. /package/dist/web/assets/{tokyo-night-qVSqO9xy.js → tokyo-night-Uhs17tQ3.js} +0 -0
  585. /package/dist/web/assets/{toml-D9F5Osqs.js → toml-j6fPOJ6P.js} +0 -0
  586. /package/dist/web/assets/{tsv-C5tpHoxm.js → tsv-h78O6xrL.js} +0 -0
  587. /package/dist/web/assets/{tsx-CMWkGtGm.js → tsx-Bol-vCrQ.js} +0 -0
  588. /package/dist/web/assets/{turtle-Cn4z3xOi.js → turtle-Dw8eFf04.js} +0 -0
  589. /package/dist/web/assets/{typescript-DSyAEWsn.js → typescript-Dtz7DPci.js} +0 -0
  590. /package/dist/web/assets/{typespec-B93Qap7y.js → typespec-BIOQ7dMP.js} +0 -0
  591. /package/dist/web/assets/{typst-DtlOmFlX.js → typst-CoaGMjzu.js} +0 -0
  592. /package/dist/web/assets/{use-is-mobile-B9UZNwSm.js → use-is-mobile-CYW3FJFE.js} +0 -0
  593. /package/dist/web/assets/{use-visual-viewport-Sfb6CZ80.js → use-visual-viewport-rnvuooQZ.js} +0 -0
  594. /package/dist/web/assets/{utils-QHe_Q6UF.js → utils-C9OjCrou.js} +0 -0
  595. /package/dist/web/assets/{v-Dfzf-szS.js → v-BoqFdpJ7.js} +0 -0
  596. /package/dist/web/assets/{vala-CyiQZQ9f.js → vala-COBpNnCC.js} +0 -0
  597. /package/dist/web/assets/{vb-C7DGSldd.js → vb-DxI86znh.js} +0 -0
  598. /package/dist/web/assets/{vendor-xterm-BHKF_Nsf.js → vendor-xterm-BaMOj65g.js} +0 -0
  599. /package/dist/web/assets/{verilog-kF-hClY2.js → verilog-DcTAamuQ.js} +0 -0
  600. /package/dist/web/assets/{vesper-BmKcADuz.js → vesper-DO298JSv.js} +0 -0
  601. /package/dist/web/assets/{vhdl-CDnb16KJ.js → vhdl-Dq-bDVSN.js} +0 -0
  602. /package/dist/web/assets/{viml-B8cs6Ady.js → viml-zHTRWuQh.js} +0 -0
  603. /package/dist/web/assets/{vitesse-black-BR6o9Gto.js → vitesse-black-jlYMJCBf.js} +0 -0
  604. /package/dist/web/assets/{vitesse-dark-Dye14-KB.js → vitesse-dark-DS0sUlSZ.js} +0 -0
  605. /package/dist/web/assets/{vitesse-light-BLpswIxf.js → vitesse-light-HSeZ2YhG.js} +0 -0
  606. /package/dist/web/assets/{vyper-DU51Z97W.js → vyper-BQfKVO3M.js} +0 -0
  607. /package/dist/web/assets/{wasm-ChtccNfv.js → wasm-B1xp9bLp.js} +0 -0
  608. /package/dist/web/assets/{wasm-CVqlJAR2.js → wasm-B6mCuI8h.js} +0 -0
  609. /package/dist/web/assets/{wenyan-DhCa3hCn.js → wenyan-CfpS05sP.js} +0 -0
  610. /package/dist/web/assets/{wgsl-Bj1Ec4vv.js → wgsl-14GDsWRD.js} +0 -0
  611. /package/dist/web/assets/{wikitext-C90DHNa0.js → wikitext-Ccbq5EVX.js} +0 -0
  612. /package/dist/web/assets/{wit-DUgbOD7Z.js → wit-C19hb3R5.js} +0 -0
  613. /package/dist/web/assets/{wolfram-BgwUqD1I.js → wolfram-B-ZSwGwP.js} +0 -0
  614. /package/dist/web/assets/{x-DiOUR_mp.js → x-YDu4m2Iq.js} +0 -0
  615. /package/dist/web/assets/{yaml-DEQulj-8.js → yaml-mw_JUeio.js} +0 -0
  616. /package/dist/web/assets/{zenscript-C7Jp7TgT.js → zenscript-WbzDRjvF.js} +0 -0
  617. /package/dist/web/assets/{zig-9x4qXYLg.js → zig-F0vvImTJ.js} +0 -0
@@ -1,1090 +0,0 @@
1
- import { useState, useEffect, useRef } from "react";
2
- import { Button } from "@/components/ui/button";
3
- import { Badge } from "@/components/ui/badge";
4
- import { Switch } from "@/components/ui/switch";
5
- import { Input } from "@/components/ui/input";
6
- import { Label } from "@/components/ui/label";
7
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
8
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
9
- import { Eye, Loader2, Copy, X, Download, Upload, Lock, FlaskConical } from "lucide-react";
10
- import { getAuthToken } from "../../lib/api-client";
11
- import { copyToClipboard } from "@/lib/clipboard";
12
- import {
13
- getAccounts,
14
- getActiveAccount,
15
- addAccount,
16
- deleteAccount,
17
- patchAccount,
18
- getOAuthUrl,
19
- exchangeOAuthCode,
20
- getAccountSettings,
21
- updateAccountSettings,
22
- getAllAccountUsages,
23
- importAccounts,
24
- testAccountToken,
25
- testExport,
26
- testRawToken,
27
- type AccountInfo,
28
- type AccountSettings,
29
- type AccountUsageEntry,
30
- type OAuthProfileData,
31
- type TokenTestResult,
32
- type ExportedTokenInfo,
33
- } from "../../lib/api-settings";
34
-
35
- function detectTokenType(token: string): string {
36
- if (token.startsWith("sk-ant-oat")) return "OAuth token (Claude Max/Pro)";
37
- if (token.startsWith("sk-ant-api")) return "API key";
38
- return "Unknown format";
39
- }
40
-
41
- function miniBarColor(pct: number): string {
42
- if (pct >= 90) return "bg-error";
43
- if (pct >= 70) return "bg-warning";
44
- return "bg-success";
45
- }
46
-
47
- function miniPctColor(pct: number): string {
48
- if (pct >= 90) return "text-error";
49
- if (pct >= 70) return "text-warning";
50
- return "text-success";
51
- }
52
-
53
- function MiniBar({ label, value }: { label: string; value: number }) {
54
- const pct = Math.round(value * 100);
55
- return (
56
- <div className="flex items-center gap-1">
57
- <span className="text-[9px] text-muted-foreground w-5 shrink-0">{label}</span>
58
- <div className="w-12 h-1.5 rounded-full bg-border overflow-hidden">
59
- <div className={`h-full rounded-full ${miniBarColor(pct)}`} style={{ width: `${Math.min(pct, 100)}%` }} />
60
- </div>
61
- <span className={`text-[9px] tabular-nums w-7 ${miniPctColor(pct)}`}>{pct}%</span>
62
- </div>
63
- );
64
- }
65
-
66
- function CompactUsageBars({ usage }: { usage: AccountUsageEntry["usage"] }) {
67
- if (!usage.session && !usage.weekly) return null;
68
- return (
69
- <div className="flex flex-col gap-0.5 mt-1">
70
- {usage.session && <MiniBar label="5h" value={usage.session.utilization} />}
71
- {usage.weekly && <MiniBar label="Wk" value={usage.weekly.utilization} />}
72
- </div>
73
- );
74
- }
75
-
76
- function subscriptionLabel(profile: OAuthProfileData): string {
77
- const org = profile.organization;
78
- if (!org) return "";
79
- const type = org.organization_type?.replace(/_/g, " ") ?? "";
80
- const tier = org.rate_limit_tier?.replace(/^default_/, "").replace(/_/g, " ") ?? "";
81
- return [type, tier].filter(Boolean).join(" / ");
82
- }
83
-
84
- function ProfileDetailDialog({ profile, open, onClose }: { profile: OAuthProfileData; open: boolean; onClose: () => void }) {
85
- const acc = profile.account;
86
- const org = profile.organization;
87
- return (
88
- <Dialog open={open} onOpenChange={(v) => !v && onClose()}>
89
- <DialogContent className="sm:max-w-md">
90
- <DialogHeader>
91
- <DialogTitle className="text-sm">Account Profile</DialogTitle>
92
- </DialogHeader>
93
- <div className="space-y-3 text-xs">
94
- {acc && (
95
- <div className="space-y-1">
96
- <p className="text-[11px] font-medium text-muted-foreground">User</p>
97
- <div className="grid grid-cols-[80px_1fr] gap-1 text-xs">
98
- {acc.display_name && <><span className="text-muted-foreground">Name</span><span>{acc.display_name}</span></>}
99
- {acc.email && <><span className="text-muted-foreground">Email</span><span>{acc.email}</span></>}
100
- {acc.uuid && <><span className="text-muted-foreground">UUID</span><span className="font-mono text-[10px] break-all">{acc.uuid}</span></>}
101
- </div>
102
- </div>
103
- )}
104
- {org && (
105
- <div className="space-y-1">
106
- <p className="text-[11px] font-medium text-muted-foreground">Organization</p>
107
- <div className="grid grid-cols-[80px_1fr] gap-1 text-xs">
108
- {org.name && <><span className="text-muted-foreground">Name</span><span>{org.name}</span></>}
109
- {org.organization_type && <><span className="text-muted-foreground">Type</span><span>{org.organization_type}</span></>}
110
- {org.rate_limit_tier && <><span className="text-muted-foreground">Tier</span><span>{org.rate_limit_tier}</span></>}
111
- {org.subscription_status && <><span className="text-muted-foreground">Status</span><span>{org.subscription_status}</span></>}
112
- {org.has_extra_usage_enabled !== undefined && <><span className="text-muted-foreground">Extra usage</span><span>{org.has_extra_usage_enabled ? "Enabled" : "Disabled"}</span></>}
113
- </div>
114
- </div>
115
- )}
116
- {!acc && !org && <p className="text-muted-foreground">No profile data available.</p>}
117
- </div>
118
- <DialogFooter>
119
- <Button size="sm" variant="outline" className="text-xs h-7" onClick={onClose}>Close</Button>
120
- </DialogFooter>
121
- </DialogContent>
122
- </Dialog>
123
- );
124
- }
125
-
126
- export function AccountsSettingsSection() {
127
- const [accounts, setAccounts] = useState<AccountInfo[]>([]);
128
- const [activeAccountId, setActiveAccountId] = useState<string | null>(null);
129
- const [settings, setSettings] = useState<AccountSettings | null>(null);
130
- const [usageMap, setUsageMap] = useState<Map<string, AccountUsageEntry["usage"]>>(new Map());
131
- const [loading, setLoading] = useState(true);
132
- const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
133
- const msgTimerRef = useRef<ReturnType<typeof setTimeout>>(null);
134
-
135
- function showMessage(msg: { type: "success" | "error"; text: string }) {
136
- if (msgTimerRef.current) clearTimeout(msgTimerRef.current);
137
- setMessage(msg);
138
- msgTimerRef.current = setTimeout(() => setMessage(null), 4000);
139
- }
140
- const [showAddDialog, setShowAddDialog] = useState(false);
141
- const [newToken, setNewToken] = useState("");
142
- const [newLabel, setNewLabel] = useState("");
143
- const [adding, setAdding] = useState(false);
144
- const [addError, setAddError] = useState<string | null>(null);
145
- const [profileView, setProfileView] = useState<OAuthProfileData | null>(null);
146
- const [oauthState, setOauthState] = useState<string | null>(null);
147
- const [oauthCode, setOauthCode] = useState("");
148
- const [oauthLoading, setOauthLoading] = useState(false);
149
- const [oauthStep, setOauthStep] = useState<"idle" | "waiting">("idle");
150
- // Delete confirmation
151
- const [deleteTarget, setDeleteTarget] = useState<{ id: string; display: string } | null>(null);
152
- const [deleting, setDeleting] = useState(false);
153
-
154
- // Export dialog
155
- const [showExportDialog, setShowExportDialog] = useState(false);
156
- const [exportPassword, setExportPassword] = useState("");
157
- const [exportConfirm, setExportConfirm] = useState("");
158
- const [exportSelected, setExportSelected] = useState<Set<string>>(new Set());
159
- const [exporting, setExporting] = useState(false);
160
- const [exportFullTransfer, setExportFullTransfer] = useState(false);
161
- const [exportRefreshBefore, setExportRefreshBefore] = useState(false);
162
-
163
- // Import dialog
164
- const [showImportDialog, setShowImportDialog] = useState(false);
165
- const [importPassword, setImportPassword] = useState("");
166
- const [importData, setImportData] = useState("");
167
- const [importing, setImporting] = useState(false);
168
- const [importError, setImportError] = useState<string | null>(null);
169
-
170
- // Token test dialog
171
- const [showTokenTest, setShowTokenTest] = useState(false);
172
- const [tokenTestResults, setTokenTestResults] = useState<Map<string, { loading: boolean; result?: TokenTestResult; error?: string }>>(new Map());
173
- const [tokenTestRefresh, setTokenTestRefresh] = useState(false);
174
- // Export simulation — accumulate rounds
175
- const [exportRounds, setExportRounds] = useState<{ round: number; time: string; includeRefresh: boolean; items: ExportedTokenInfo[] }[]>([]);
176
- const [exportSimLoading, setExportSimLoading] = useState(false);
177
- const [exportSimIncludeRefresh, setExportSimIncludeRefresh] = useState(false);
178
- const [rawTokenTests, setRawTokenTests] = useState<Map<string, { loading: boolean; status?: string; code?: number; error?: string }>>(new Map());
179
-
180
- useEffect(() => {
181
- refresh();
182
- }, []);
183
-
184
- async function refresh() {
185
- setLoading(true);
186
- const [accs, cfg, active, usages] = await Promise.allSettled([
187
- getAccounts(), getAccountSettings(), getActiveAccount(), getAllAccountUsages(),
188
- ]);
189
- if (accs.status === "fulfilled") setAccounts(accs.value);
190
- if (cfg.status === "fulfilled") setSettings(cfg.value);
191
- if (active.status === "fulfilled") setActiveAccountId(active.value?.id ?? null);
192
- if (usages.status === "fulfilled") setUsageMap(new Map(usages.value.map((u) => [u.accountId, u.usage])));
193
- setLoading(false);
194
- }
195
-
196
- async function handleAddAccount() {
197
- if (!newToken.trim()) return;
198
- setAdding(true);
199
- setAddError(null);
200
- try {
201
- await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });
202
- showMessage({ type: "success", text: "Account added successfully!" });
203
- setShowAddDialog(false);
204
- setNewToken("");
205
- setNewLabel("");
206
- setAddError(null);
207
- refresh();
208
- } catch (e) {
209
- setAddError((e as Error).message);
210
- }
211
- setAdding(false);
212
- }
213
-
214
- async function handleOAuthLogin() {
215
- setOauthLoading(true);
216
- setAddError(null);
217
- try {
218
- const { url, state } = await getOAuthUrl();
219
- setOauthState(state);
220
- setOauthStep("waiting");
221
- window.open(url, "_blank");
222
- } catch (e) {
223
- setAddError((e as Error).message);
224
- }
225
- setOauthLoading(false);
226
- }
227
-
228
- async function handleOAuthExchange() {
229
- if (!oauthCode.trim() || !oauthState) return;
230
- setOauthLoading(true);
231
- setAddError(null);
232
- try {
233
- // Parse code — platform returns "CODE#STATE" or just the code
234
- let code = oauthCode.trim();
235
- if (code.includes("#")) code = code.split("#")[0] ?? code;
236
- await exchangeOAuthCode(code, oauthState);
237
- showMessage({ type: "success", text: "Account connected via OAuth!" });
238
- setShowAddDialog(false);
239
- resetOAuthState();
240
- refresh();
241
- } catch (e) {
242
- setAddError((e as Error).message);
243
- }
244
- setOauthLoading(false);
245
- }
246
-
247
- function resetOAuthState() {
248
- setOauthState(null);
249
- setOauthCode("");
250
- setOauthStep("idle");
251
- setAddError(null);
252
- }
253
-
254
- async function handleToggle(id: string, currentStatus: string) {
255
- const newStatus = currentStatus === "disabled" ? "active" : "disabled";
256
- await patchAccount(id, { status: newStatus });
257
- refresh();
258
- }
259
-
260
- function handleDelete(id: string, email: string | null) {
261
- setDeleteTarget({ id, display: email ?? id.slice(0, 8) });
262
- }
263
-
264
- async function confirmDelete() {
265
- if (!deleteTarget) return;
266
- setDeleting(true);
267
- try {
268
- await deleteAccount(deleteTarget.id);
269
- showMessage({ type: "success", text: "Account removed." });
270
- refresh();
271
- } catch (e) {
272
- showMessage({ type: "error", text: (e as Error).message });
273
- }
274
- setDeleting(false);
275
- setDeleteTarget(null);
276
- }
277
-
278
- function formatLastUsed(ts: number | null): string {
279
- if (!ts) return "Never";
280
- const diff = Math.floor(Date.now() / 1000 - ts);
281
- if (diff < 60) return "Just now";
282
- if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
283
- if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
284
- return `${Math.floor(diff / 86400)}d ago`;
285
- }
286
-
287
- function formatCooldown(cooldownUntil: number | null): string {
288
- if (!cooldownUntil) return "";
289
- const remaining = cooldownUntil - Math.floor(Date.now() / 1000);
290
- if (remaining <= 0) return "";
291
- if (remaining < 60) return `${remaining}s`;
292
- return `${Math.floor(remaining / 60)}m ${remaining % 60}s`;
293
- }
294
-
295
- function statusBadge(acc: AccountInfo) {
296
- const badges = [];
297
- if (!acc.hasRefreshToken) {
298
- const nowS = Math.floor(Date.now() / 1000);
299
- const expired = acc.expiresAt && acc.expiresAt < nowS;
300
- if (expired) {
301
- badges.push(<Badge key="temp" variant="destructive" className="text-[10px] px-1.5 py-0">Expired</Badge>);
302
- } else {
303
- const remaining = acc.expiresAt ? acc.expiresAt - nowS : 0;
304
- const mins = Math.floor(remaining / 60);
305
- const timeStr = mins > 0 ? `${mins}m` : `${remaining}s`;
306
- badges.push(<Badge key="temp" variant="secondary" className="text-[10px] px-1.5 py-0">Temp · {timeStr}</Badge>);
307
- }
308
- }
309
- if (acc.status === "cooldown") {
310
- const cd = formatCooldown(acc.cooldownUntil);
311
- badges.push(<Badge key="cd" variant="destructive" className="text-[10px] px-1.5 py-0">Cooldown{cd ? ` (${cd})` : ""}</Badge>);
312
- }
313
- return badges.length > 0 ? <>{badges}</> : null;
314
- }
315
-
316
- // Only accounts with refresh token are exportable
317
- const exportableAccounts = accounts.filter((a) => a.hasRefreshToken);
318
-
319
- function openExportDialog() {
320
- setExportPassword("");
321
- setExportConfirm("");
322
- setExportSelected(new Set(exportableAccounts.map((a) => a.id)));
323
- setExportFullTransfer(false);
324
- setExportRefreshBefore(false);
325
- setShowExportDialog(true);
326
- }
327
-
328
- function openImportDialog(prefillData?: string) {
329
- setImportPassword("");
330
- setImportData(prefillData ?? "");
331
- setImportError(null);
332
- setShowImportDialog(true);
333
- }
334
-
335
- async function doExport(toClipboard: boolean) {
336
- if (exportPassword.length < 4) return;
337
- if (exportPassword !== exportConfirm) return;
338
- setExporting(true);
339
- try {
340
- const headers: HeadersInit = { "Content-Type": "application/json" };
341
- const token = getAuthToken();
342
- if (token) headers["Authorization"] = `Bearer ${token}`;
343
- const res = await fetch("/api/accounts/export", {
344
- method: "POST",
345
- headers,
346
- body: JSON.stringify({ password: exportPassword, accountIds: [...exportSelected], includeRefreshToken: exportFullTransfer, refreshBeforeExport: exportRefreshBefore }),
347
- });
348
- if (!res.ok) { const j = await res.json() as any; throw new Error(j.error ?? `Export failed: ${res.status}`); }
349
- const text = await res.text();
350
- if (toClipboard) {
351
- if (await copyToClipboard(text)) {
352
- showMessage({ type: "success", text: "Backup copied to clipboard!" });
353
- } else {
354
- const blob = new Blob([text], { type: "application/json" });
355
- const a = document.createElement("a");
356
- a.href = URL.createObjectURL(blob);
357
- a.download = "ppm-accounts-backup.json";
358
- a.click();
359
- URL.revokeObjectURL(a.href);
360
- showMessage({ type: "success", text: "Clipboard unavailable — downloaded as file." });
361
- }
362
- } else {
363
- const blob = new Blob([text], { type: "application/json" });
364
- const a = document.createElement("a");
365
- a.href = URL.createObjectURL(blob);
366
- a.download = "ppm-accounts-backup.json";
367
- a.click();
368
- URL.revokeObjectURL(a.href);
369
- showMessage({ type: "success", text: "Backup downloaded." });
370
- }
371
- setShowExportDialog(false);
372
- } catch (e) {
373
- showMessage({ type: "error", text: (e as Error).message });
374
- }
375
- setExporting(false);
376
- }
377
-
378
- async function doImport() {
379
- if (!importData.trim() || !importPassword) return;
380
- setImporting(true);
381
- setImportError(null);
382
- try {
383
- const result = await importAccounts({ data: importData.trim(), password: importPassword });
384
- const note = result.refreshed > 0 ? ` ${result.refreshed} claimed (source machine will expire).` : " Temporary accounts expire in ~1h.";
385
- showMessage({ type: "success", text: `Imported ${result.imported} account(s).${note}` });
386
- setShowImportDialog(false);
387
- refresh();
388
- } catch (e) {
389
- setImportError((e as Error).message || "Import failed");
390
- }
391
- setImporting(false);
392
- }
393
-
394
-
395
- async function simulateExport() {
396
- const oauthIds = accounts.filter((a) => a.hasRefreshToken).map((a) => a.id);
397
- if (!oauthIds.length) return;
398
- setExportSimLoading(true);
399
- try {
400
- const data = await testExport(oauthIds, exportSimIncludeRefresh);
401
- const roundNum = exportRounds.length + 1;
402
- const time = new Date().toLocaleTimeString();
403
- setExportRounds((prev) => [...prev, { round: roundNum, time, includeRefresh: exportSimIncludeRefresh, items: data }]);
404
- } catch { /* ignore */ }
405
- setExportSimLoading(false);
406
- }
407
-
408
- async function testRawTokenClick(key: string, token: string) {
409
- setRawTokenTests((prev) => new Map(prev).set(key, { loading: true }));
410
- try {
411
- const result = await testRawToken(token);
412
- setRawTokenTests((prev) => new Map(prev).set(key, { loading: false, ...result }));
413
- } catch (e) {
414
- setRawTokenTests((prev) => new Map(prev).set(key, { loading: false, status: "error", error: (e as Error).message }));
415
- }
416
- }
417
-
418
- async function runTokenTest(id: string) {
419
- setTokenTestResults((prev) => new Map(prev).set(id, { loading: true }));
420
- try {
421
- const result = await testAccountToken(id, tokenTestRefresh);
422
- setTokenTestResults((prev) => new Map(prev).set(id, { loading: false, result }));
423
- } catch (e) {
424
- setTokenTestResults((prev) => new Map(prev).set(id, { loading: false, error: (e as Error).message }));
425
- }
426
- }
427
-
428
- async function runAllTokenTests() {
429
- const oauthAccounts = accounts.filter((a) => a.expiresAt !== null);
430
- for (const acc of oauthAccounts) {
431
- runTokenTest(acc.id);
432
- }
433
- }
434
-
435
- const tokenHint = newToken.trim() ? detectTokenType(newToken.trim()) : "";
436
-
437
- return (
438
- <div className="space-y-4">
439
- <div>
440
- <p className="text-[11px] text-muted-foreground mb-3">
441
- Connect multiple Claude accounts. PPM rotates between them automatically to avoid rate limits.
442
- </p>
443
-
444
- {message && (
445
- <div className={`text-[11px] mb-3 p-2 rounded ${message.type === "success" ? "bg-success/10 text-success" : "bg-error/10 text-error"}`}>
446
- {message.text}
447
- </div>
448
- )}
449
-
450
- <div className="space-y-1.5 mb-3">
451
- {loading && <p className="text-[11px] text-muted-foreground">Loading...</p>}
452
- {!loading && accounts.length === 0 && (
453
- <p className="text-[11px] text-muted-foreground">No accounts connected.</p>
454
- )}
455
- {accounts.map((acc) => {
456
- const usage = usageMap.get(acc.id);
457
- return (
458
- <div key={acc.id} className="p-2.5 rounded-lg border bg-card space-y-1.5">
459
- {/* Row 1: name + badges + actions */}
460
- <div className="flex items-center justify-between gap-1">
461
- <div className="flex items-center gap-1.5 min-w-0 flex-1">
462
- <span className="text-xs font-medium truncate">{acc.label ?? acc.email ?? acc.id.slice(0, 8)}</span>
463
- {statusBadge(acc)}
464
- </div>
465
- <div className="flex items-center shrink-0">
466
- {acc.profileData && (
467
- <Button size="icon" variant="ghost" className="size-7 cursor-pointer text-muted-foreground hover:text-foreground" onClick={() => setProfileView(acc.profileData)} title="View profile">
468
- <Eye className="size-3" />
469
- </Button>
470
- )}
471
- <Switch
472
- checked={acc.status !== "disabled"}
473
- onCheckedChange={() => handleToggle(acc.id, acc.status)}
474
- disabled={acc.status === "cooldown" || (!acc.hasRefreshToken && !!acc.expiresAt && acc.expiresAt < Math.floor(Date.now() / 1000))}
475
- className="scale-[0.65] cursor-pointer"
476
- title={!acc.hasRefreshToken && !!acc.expiresAt && acc.expiresAt < Math.floor(Date.now() / 1000) ? "Expired temporary account — cannot re-enable" : undefined}
477
- />
478
- <Button size="icon" variant="ghost" className="size-7 cursor-pointer text-muted-foreground hover:text-destructive" onClick={() => handleDelete(acc.id, acc.email)} title="Remove">
479
- <X className="size-3" />
480
- </Button>
481
- </div>
482
- </div>
483
- {/* Row 2: meta + usage inline */}
484
- <div className="flex items-center justify-between gap-2 text-[10px] text-muted-foreground">
485
- <div className="flex gap-1.5 items-center min-w-0 truncate">
486
- {acc.email && acc.label && <span className="truncate">{acc.email}</span>}
487
- <span>{acc.totalRequests} req{acc.totalRequests !== 1 ? "s" : ""}</span>
488
- <span>· {formatLastUsed(acc.lastUsedAt)}</span>
489
- </div>
490
- {usage && (usage.session || usage.weekly) && (
491
- <div className="flex gap-2 shrink-0 tabular-nums">
492
- {usage.session && <span className={miniPctColor(Math.round(usage.session.utilization * 100))}>5h {Math.round(usage.session.utilization * 100)}%</span>}
493
- {usage.weekly && <span className={miniPctColor(Math.round(usage.weekly.utilization * 100))}>Wk {Math.round(usage.weekly.utilization * 100)}%</span>}
494
- </div>
495
- )}
496
- </div>
497
- </div>
498
- );
499
- })}
500
- </div>
501
-
502
- <div className={`grid gap-1.5 ${import.meta.env.DEV ? "grid-cols-4" : "grid-cols-3"}`}>
503
- <Button size="sm" className="h-8 text-xs cursor-pointer" onClick={() => setShowAddDialog(true)}>
504
- + Add
505
- </Button>
506
- <Button size="sm" variant="outline" className="h-8 text-xs cursor-pointer" onClick={() => openExportDialog()}>
507
- <Download className="size-3.5 mr-1" /> Export
508
- </Button>
509
- <Button size="sm" variant="outline" className="h-8 text-xs cursor-pointer" onClick={() => openImportDialog()}>
510
- <Upload className="size-3.5 mr-1" /> Import
511
- </Button>
512
- {import.meta.env.DEV && (
513
- <Button size="sm" variant="outline" className="h-8 text-xs cursor-pointer" onClick={() => { setTokenTestResults(new Map()); setTokenTestRefresh(false); setExportRounds([]); setRawTokenTests(new Map()); setExportSimIncludeRefresh(false); setShowTokenTest(true); }}>
514
- <FlaskConical className="size-3.5 mr-1" /> Test
515
- </Button>
516
- )}
517
- </div>
518
- </div>
519
-
520
- {settings && (
521
- <div className="border-t pt-3 space-y-2">
522
- <p className="text-[11px] font-medium text-muted-foreground">Rotation Settings</p>
523
- <div className="flex items-center gap-2">
524
- <label className="text-[11px] text-muted-foreground w-20 shrink-0">Strategy</label>
525
- <Select
526
- value={settings.strategy}
527
- onValueChange={async (v) => {
528
- const updated = await updateAccountSettings({ strategy: v as "round-robin" | "fill-first" | "lowest-usage" });
529
- setSettings(updated);
530
- }}
531
- >
532
- <SelectTrigger className="w-32 h-7 text-xs">
533
- <SelectValue />
534
- </SelectTrigger>
535
- <SelectContent>
536
- <SelectItem value="round-robin">Round-robin</SelectItem>
537
- <SelectItem value="fill-first">Fill-first</SelectItem>
538
- <SelectItem value="lowest-usage">Lowest usage</SelectItem>
539
- </SelectContent>
540
- </Select>
541
- </div>
542
- <div className="flex items-center gap-2">
543
- <label className="text-[11px] text-muted-foreground w-20 shrink-0">Max retry</label>
544
- <input
545
- type="number"
546
- min={0}
547
- value={settings.maxRetry}
548
- className="w-14 h-7 text-xs border rounded px-2 bg-background"
549
- onChange={async (e) => {
550
- const v = parseInt(e.target.value, 10);
551
- if (!isNaN(v) && v >= 0) {
552
- const updated = await updateAccountSettings({ maxRetry: v });
553
- setSettings(updated);
554
- }
555
- }}
556
- />
557
- <span className="text-[11px] text-muted-foreground">(0 = try all)</span>
558
- </div>
559
- <div className="flex items-center gap-2">
560
- <label className="text-[11px] text-muted-foreground w-20 shrink-0">Cooldown</label>
561
- <Switch
562
- checked={settings.cooldownEnabled}
563
- onCheckedChange={async (v) => {
564
- const updated = await updateAccountSettings({ cooldownEnabled: v });
565
- setSettings(updated);
566
- }}
567
- />
568
- <span className="text-[11px] text-muted-foreground">
569
- {settings.cooldownEnabled ? "park failing accounts" : "off — rotation still skips ≥95% usage"}
570
- </span>
571
- </div>
572
- <p className="text-[11px] text-muted-foreground">
573
- Active accounts: {settings.activeCount}
574
- </p>
575
- </div>
576
- )}
577
-
578
- {/* Profile detail dialog */}
579
- {profileView && (
580
- <ProfileDetailDialog
581
- profile={profileView}
582
- open={!!profileView}
583
- onClose={() => setProfileView(null)}
584
- />
585
- )}
586
-
587
- <Dialog open={showAddDialog} onOpenChange={(v) => { setShowAddDialog(v); if (!v) resetOAuthState(); }}>
588
- <DialogContent className="sm:max-w-md">
589
- <DialogHeader>
590
- <DialogTitle className="text-sm">Add Claude Account</DialogTitle>
591
- <DialogDescription className="text-xs leading-relaxed">
592
- Connect your Claude account via OAuth (recommended) or paste a token manually.
593
- </DialogDescription>
594
- </DialogHeader>
595
- <div className="space-y-3">
596
- {/* OAuth login — recommended */}
597
- <div className="rounded-md border p-3 space-y-2">
598
- <p className="text-[11px] font-medium">Recommended: Login with Claude</p>
599
- <p className="text-[10px] text-muted-foreground">
600
- Fetches your profile, enables auto-refresh, and uses the correct permissions.
601
- </p>
602
- {oauthStep === "idle" ? (
603
- <Button
604
- size="sm"
605
- className="w-full h-8 text-xs"
606
- onClick={handleOAuthLogin}
607
- disabled={oauthLoading}
608
- >
609
- {oauthLoading ? <><Loader2 className="size-3 animate-spin mr-1" /> Opening...</> : "Login with Claude"}
610
- </Button>
611
- ) : (
612
- <div className="space-y-2">
613
- <p className="text-[10px] text-muted-foreground">
614
- Authorize in the opened tab, then paste the code shown on the page:
615
- </p>
616
- <Input
617
- placeholder="Paste code here..."
618
- value={oauthCode}
619
- onChange={(e) => setOauthCode(e.target.value)}
620
- className="text-xs h-8 font-mono"
621
- autoFocus
622
- />
623
- <div className="flex gap-1.5">
624
- <Button
625
- size="sm"
626
- className="flex-1 h-7 text-xs"
627
- onClick={handleOAuthExchange}
628
- disabled={!oauthCode.trim() || oauthLoading}
629
- >
630
- {oauthLoading ? <><Loader2 className="size-3 animate-spin mr-1" /> Connecting...</> : "Connect Account"}
631
- </Button>
632
- <Button size="sm" variant="ghost" className="h-7 text-xs" onClick={resetOAuthState}>
633
- Cancel
634
- </Button>
635
- </div>
636
- </div>
637
- )}
638
- </div>
639
-
640
- <div className="flex items-center gap-2">
641
- <div className="flex-1 border-t" />
642
- <span className="text-[10px] text-muted-foreground">or paste token manually</span>
643
- <div className="flex-1 border-t" />
644
- </div>
645
-
646
- {/* Manual token input */}
647
- <div className="space-y-1.5">
648
- <Label htmlFor="token" className="text-xs">Token</Label>
649
- <Input
650
- id="token"
651
- type="password"
652
- placeholder="sk-ant-..."
653
- value={newToken}
654
- onChange={(e) => setNewToken(e.target.value)}
655
- className="text-xs h-8 font-mono"
656
- />
657
- {tokenHint && (
658
- <p className="text-[10px] text-muted-foreground">
659
- Detected: {tokenHint}
660
- </p>
661
- )}
662
- </div>
663
- <div className="space-y-1.5">
664
- <Label htmlFor="label" className="text-xs">Label (optional)</Label>
665
- <Input
666
- id="label"
667
- placeholder="e.g. Personal, Work"
668
- value={newLabel}
669
- onChange={(e) => setNewLabel(e.target.value)}
670
- className="text-xs h-8"
671
- />
672
- </div>
673
- <div className="rounded-md bg-muted/50 p-2.5 space-y-1.5">
674
- <p className="text-[10px] font-medium text-muted-foreground">How to get a token manually:</p>
675
- <div className="text-[10px] text-muted-foreground space-y-1">
676
- <p><span className="font-medium">Claude Max/Pro:</span> Run <code className="bg-background rounded px-1 font-mono">claude setup-token</code></p>
677
- <p className="text-[9px]">Valid 1 year but no auto-refresh, may lack profile data.</p>
678
- <p><span className="font-medium">API key:</span>{" "}
679
- <a
680
- href="https://console.anthropic.com/settings/keys"
681
- target="_blank"
682
- rel="noopener noreferrer"
683
- className="text-primary underline hover:no-underline"
684
- >
685
- console.anthropic.com/settings/keys
686
- </a>
687
- </p>
688
- </div>
689
- </div>
690
- </div>
691
- {addError && (
692
- <div className="text-[11px] p-2 rounded bg-error/10 text-error">
693
- {addError}
694
- </div>
695
- )}
696
- <DialogFooter>
697
- <Button size="sm" variant="outline" className="text-xs h-7" onClick={() => { setShowAddDialog(false); resetOAuthState(); }}>
698
- Cancel
699
- </Button>
700
- <Button size="sm" className="text-xs h-7" onClick={handleAddAccount} disabled={!newToken.trim() || adding}>
701
- {adding ? "Adding..." : "Add Token"}
702
- </Button>
703
- </DialogFooter>
704
- </DialogContent>
705
- </Dialog>
706
-
707
- {/* Export dialog — account selection + password */}
708
- <Dialog open={showExportDialog} onOpenChange={(v) => { if (!v) setShowExportDialog(false); }}>
709
- <DialogContent className="sm:max-w-md">
710
- <DialogHeader>
711
- <DialogTitle className="text-sm flex items-center gap-1.5"><Lock className="size-3.5" /> Export Accounts</DialogTitle>
712
- <DialogDescription className="text-xs">Select accounts and set a password to protect the backup.</DialogDescription>
713
- </DialogHeader>
714
- <div className="space-y-3">
715
- {/* Account selection — only exportable (has refresh token) */}
716
- <div className="space-y-1">
717
- <div className="flex items-center justify-between mb-1">
718
- <p className="text-[11px] font-medium text-muted-foreground">Accounts to export</p>
719
- <button
720
- className="text-[10px] text-primary hover:underline cursor-pointer"
721
- onClick={() => setExportSelected(
722
- exportSelected.size === exportableAccounts.length ? new Set() : new Set(exportableAccounts.map((a) => a.id))
723
- )}
724
- >
725
- {exportSelected.size === exportableAccounts.length ? "Deselect all" : "Select all"}
726
- </button>
727
- </div>
728
- {exportableAccounts.length === 0 ? (
729
- <p className="text-[10px] text-muted-foreground p-2 border rounded">No exportable accounts. Temporary or expired accounts cannot be exported.</p>
730
- ) : (
731
- <div className="max-h-36 overflow-y-auto space-y-1 border rounded p-2">
732
- {exportableAccounts.map((acc) => (
733
- <div key={acc.id} className="flex items-center gap-2">
734
- <input
735
- type="checkbox"
736
- id={`exp-${acc.id}`}
737
- checked={exportSelected.has(acc.id)}
738
- onChange={(e) => {
739
- const s = new Set(exportSelected);
740
- e.target.checked ? s.add(acc.id) : s.delete(acc.id);
741
- setExportSelected(s);
742
- }}
743
- className="size-3.5 accent-primary cursor-pointer"
744
- />
745
- <label htmlFor={`exp-${acc.id}`} className="text-xs cursor-pointer truncate">
746
- {acc.label ?? acc.email ?? acc.id.slice(0, 8)}
747
- {acc.email && acc.label && <span className="text-muted-foreground ml-1 text-[10px]">({acc.email})</span>}
748
- </label>
749
- </div>
750
- ))}
751
- </div>
752
- )}
753
- </div>
754
- {/* Password */}
755
- <div className="space-y-1.5">
756
- <Label className="text-xs">Password</Label>
757
- <Input
758
- type="password"
759
- placeholder="Min 4 characters"
760
- value={exportPassword}
761
- onChange={(e) => setExportPassword(e.target.value)}
762
- className="text-xs h-8"
763
- autoComplete="new-password"
764
- />
765
- </div>
766
- <div className="space-y-1.5">
767
- <Label className="text-xs">Confirm password</Label>
768
- <Input
769
- type="password"
770
- placeholder="Re-enter password"
771
- value={exportConfirm}
772
- onChange={(e) => setExportConfirm(e.target.value)}
773
- className="text-xs h-8"
774
- autoComplete="new-password"
775
- />
776
- {exportConfirm && exportPassword !== exportConfirm && (
777
- <p className="text-[10px] text-error">Passwords do not match</p>
778
- )}
779
- </div>
780
- {/* Full transfer toggle */}
781
- <div className="flex items-center gap-2">
782
- <input
783
- type="checkbox"
784
- id="export-full-transfer"
785
- checked={exportFullTransfer}
786
- onChange={(e) => setExportFullTransfer(e.target.checked)}
787
- className="size-3.5 accent-primary cursor-pointer"
788
- />
789
- <label htmlFor="export-full-transfer" className="text-[11px] cursor-pointer">
790
- Include refresh tokens (full transfer)
791
- </label>
792
- </div>
793
- {/* Refresh before export toggle */}
794
- <div className="flex items-center gap-2">
795
- <input
796
- type="checkbox"
797
- id="export-refresh-before"
798
- checked={exportRefreshBefore}
799
- onChange={(e) => setExportRefreshBefore(e.target.checked)}
800
- className="size-3.5 accent-primary cursor-pointer"
801
- />
802
- <label htmlFor="export-refresh-before" className="text-[11px] cursor-pointer">
803
- Refresh tokens before export
804
- </label>
805
- </div>
806
- {exportFullTransfer ? (
807
- <div className="rounded-md border border-error/30 bg-error/5 p-2.5 space-y-1">
808
- <p className="text-[10px] font-medium text-error">Full transfer — source accounts will expire</p>
809
- <p className="text-[10px] text-muted-foreground leading-relaxed">
810
- Refresh tokens are included. Once the target machine refreshes, <strong>accounts on this machine will only work for ~1h</strong> then become temporary. Use this only to move accounts to another machine.
811
- </p>
812
- </div>
813
- ) : exportRefreshBefore ? (
814
- <div className="rounded-md border border-warning/30 bg-warning/5 p-2.5 space-y-1">
815
- <p className="text-[10px] font-medium text-warning">Refresh before export — invalidates previous shares</p>
816
- <p className="text-[10px] text-muted-foreground leading-relaxed">
817
- Tokens will be refreshed to maximize validity (~1h). But <strong>any previously shared tokens will be invalidated</strong> because Anthropic only allows 1 active access token per account.
818
- </p>
819
- </div>
820
- ) : (
821
- <div className="rounded-md border border-success/30 bg-success/5 p-2.5 space-y-1">
822
- <p className="text-[10px] font-medium text-success">Share current token (default, safe)</p>
823
- <p className="text-[10px] text-muted-foreground leading-relaxed">
824
- Exports the current access token as-is. Host and target share the same token. No invalidation. Token validity = remaining time until next auto-refresh.
825
- </p>
826
- </div>
827
- )}
828
- <p className="text-[10px] text-muted-foreground">
829
- Encrypted with AES-256-GCM + scrypt. Keep the password safe — it cannot be recovered.
830
- </p>
831
- </div>
832
- <DialogFooter className="gap-1.5 flex-col sm:flex-row">
833
- <Button size="sm" variant="outline" className="text-xs h-7 cursor-pointer" onClick={() => setShowExportDialog(false)}>
834
- Cancel
835
- </Button>
836
- <Button
837
- size="sm" variant="outline" className="text-xs h-7 cursor-pointer"
838
- disabled={exportPassword.length < 4 || exportPassword !== exportConfirm || exportSelected.size === 0 || exporting}
839
- onClick={() => doExport(true)}
840
- >
841
- <Copy className="size-3 mr-1" /> Copy to clipboard
842
- </Button>
843
- <Button
844
- size="sm" className="text-xs h-7 cursor-pointer"
845
- disabled={exportPassword.length < 4 || exportPassword !== exportConfirm || exportSelected.size === 0 || exporting}
846
- onClick={() => doExport(false)}
847
- >
848
- {exporting ? <><Loader2 className="size-3 animate-spin mr-1" /> Exporting...</> : <><Download className="size-3 mr-1" /> Download</>}
849
- </Button>
850
- </DialogFooter>
851
- </DialogContent>
852
- </Dialog>
853
-
854
- {/* Delete confirmation dialog */}
855
- <Dialog open={!!deleteTarget} onOpenChange={(v) => { if (!v) setDeleteTarget(null); }}>
856
- <DialogContent className="sm:max-w-xs">
857
- <DialogHeader>
858
- <DialogTitle className="text-sm">Remove Account</DialogTitle>
859
- <DialogDescription className="text-xs">
860
- Remove <strong>{deleteTarget?.display}</strong>? This cannot be undone.
861
- </DialogDescription>
862
- </DialogHeader>
863
- <DialogFooter>
864
- <Button size="sm" variant="outline" className="text-xs h-7 cursor-pointer" onClick={() => setDeleteTarget(null)} disabled={deleting}>
865
- Cancel
866
- </Button>
867
- <Button size="sm" variant="destructive" className="text-xs h-7 cursor-pointer" onClick={confirmDelete} disabled={deleting}>
868
- {deleting ? <><Loader2 className="size-3 animate-spin mr-1" /> Removing...</> : "Remove"}
869
- </Button>
870
- </DialogFooter>
871
- </DialogContent>
872
- </Dialog>
873
-
874
- {/* Token test dialog */}
875
- <Dialog open={showTokenTest} onOpenChange={(v) => { if (!v) setShowTokenTest(false); }}>
876
- <DialogContent className="sm:max-w-xl max-h-[85vh] flex flex-col">
877
- <DialogHeader>
878
- <DialogTitle className="text-sm flex items-center gap-1.5"><FlaskConical className="size-3.5" /> Token Test</DialogTitle>
879
- <DialogDescription className="text-xs">Test tokens & simulate export to compare token validity.</DialogDescription>
880
- </DialogHeader>
881
- <div className="space-y-3 overflow-y-auto flex-1 pr-1">
882
- {/* Section 1: Quick test current DB tokens */}
883
- <div className="space-y-2">
884
- <div className="flex items-center justify-between">
885
- <p className="text-[11px] font-medium">Current Tokens (DB)</p>
886
- <Button size="sm" variant="outline" className="h-6 text-[10px] cursor-pointer" onClick={runAllTokenTests}>
887
- Test All
888
- </Button>
889
- </div>
890
- <div className="space-y-1.5">
891
- {accounts.map((acc) => {
892
- const entry = tokenTestResults.get(acc.id);
893
- return (
894
- <div key={acc.id} className="p-2 rounded border bg-card space-y-1">
895
- <div className="flex items-center justify-between gap-2">
896
- <div className="min-w-0 flex-1">
897
- <span className="text-[11px] font-medium truncate block">{acc.label ?? acc.email ?? acc.id.slice(0, 8)}</span>
898
- {acc.expiresAt && (
899
- <span className="text-[10px] text-muted-foreground">
900
- {acc.expiresAt > Math.floor(Date.now() / 1000)
901
- ? `${Math.floor((acc.expiresAt - Math.floor(Date.now() / 1000)) / 60)}m left`
902
- : `expired ${Math.floor((Math.floor(Date.now() / 1000) - acc.expiresAt) / 60)}m ago`}
903
- </span>
904
- )}
905
- </div>
906
- <Button size="sm" variant="outline" className="h-6 text-[10px] cursor-pointer shrink-0" disabled={entry?.loading} onClick={() => runTokenTest(acc.id)}>
907
- {entry?.loading ? <Loader2 className="size-3 animate-spin" /> : "Test"}
908
- </Button>
909
- </div>
910
- {entry && !entry.loading && (
911
- <div className="text-[10px] pl-1 border-l-2 border-muted ml-1">
912
- {entry.error && <p className="text-error">{entry.error}</p>}
913
- {entry.result && (
914
- <span className={entry.result.accessToken.status.startsWith("valid") ? "text-success" : "text-error"}>
915
- {entry.result.accessToken.status} {entry.result.accessToken.code && `(${entry.result.accessToken.code})`}
916
- </span>
917
- )}
918
- </div>
919
- )}
920
- </div>
921
- );
922
- })}
923
- </div>
924
- </div>
925
-
926
- {/* Section 2: Export simulation */}
927
- <div className="border-t pt-3 space-y-2">
928
- <div className="flex items-center justify-between">
929
- <p className="text-[11px] font-medium">Simulate Export</p>
930
- {exportRounds.length > 0 && (
931
- <span className="text-[10px] text-muted-foreground">{exportRounds.length} round(s)</span>
932
- )}
933
- </div>
934
- <p className="text-[10px] text-muted-foreground">
935
- Each "Run Export" appends a new round. All tokens from every round are kept for comparison.
936
- </p>
937
- <div className="flex items-center gap-3">
938
- <div className="flex items-center gap-1.5">
939
- <input
940
- type="checkbox"
941
- id="sim-include-refresh"
942
- checked={exportSimIncludeRefresh}
943
- onChange={(e) => setExportSimIncludeRefresh(e.target.checked)}
944
- className="size-3 accent-primary cursor-pointer"
945
- />
946
- <label htmlFor="sim-include-refresh" className="text-[10px] cursor-pointer">Include refresh tokens</label>
947
- </div>
948
- <Button size="sm" className="h-7 text-[11px] cursor-pointer" disabled={exportSimLoading} onClick={simulateExport}>
949
- {exportSimLoading ? <><Loader2 className="size-3 animate-spin mr-1" /> Exporting...</> : `Run Export #${exportRounds.length + 1}`}
950
- </Button>
951
- </div>
952
-
953
- {exportRounds.length > 0 && (
954
- <div className="space-y-3">
955
- {/* Test All button across all rounds */}
956
- <Button
957
- size="sm"
958
- variant="outline"
959
- className="w-full h-7 text-[10px] cursor-pointer"
960
- onClick={() => {
961
- for (const round of exportRounds) {
962
- for (const item of round.items) {
963
- if (item.preExportTokenFull) testRawTokenClick(`r${round.round}-pre-${item.id}`, item.preExportTokenFull);
964
- if (item.exportedTokenFull) testRawTokenClick(`r${round.round}-exp-${item.id}`, item.exportedTokenFull);
965
- if (item.postExportTokenFull) testRawTokenClick(`r${round.round}-post-${item.id}`, item.postExportTokenFull);
966
- }
967
- }
968
- }}
969
- >
970
- Test All Tokens ({exportRounds.reduce((n, r) => n + r.items.length * 3, 0)} tokens)
971
- </Button>
972
-
973
- {/* Render each round */}
974
- {exportRounds.map((round) => (
975
- <div key={round.round} className="space-y-1.5">
976
- <div className="flex items-center gap-2">
977
- <p className="text-[11px] font-medium text-primary">Round #{round.round}</p>
978
- <span className="text-[9px] text-muted-foreground">{round.time}</span>
979
- <Badge variant={round.includeRefresh ? "destructive" : "secondary"} className="text-[8px] px-1 py-0">
980
- {round.includeRefresh ? "with refresh" : "access only"}
981
- </Badge>
982
- </div>
983
- {round.items.map((item) => (
984
- <div key={`r${round.round}-${item.id}`} className="p-2 rounded-lg border bg-card space-y-1.5">
985
- <div className="flex items-center gap-2">
986
- <span className="text-[10px] font-medium">{item.label ?? item.email ?? item.id.slice(0, 8)}</span>
987
- {item.tokenChanged && <Badge variant="secondary" className="text-[8px] px-1 py-0">DB token changed</Badge>}
988
- </div>
989
- {[
990
- { key: `r${round.round}-pre-${item.id}`, label: "Pre-export", token: item.preExportTokenFull, preview: item.preExportToken, expires: item.preExportExpires },
991
- { key: `r${round.round}-exp-${item.id}`, label: "Exported", token: item.exportedTokenFull, preview: item.exportedToken, expires: item.exportedExpires },
992
- { key: `r${round.round}-post-${item.id}`, label: "Post-export", token: item.postExportTokenFull, preview: item.postExportToken, expires: item.postExportExpires },
993
- ].map((row) => {
994
- const test = rawTokenTests.get(row.key);
995
- return (
996
- <div key={row.key} className="flex items-center gap-1.5 text-[10px]">
997
- <span className="w-20 shrink-0 text-muted-foreground text-[9px]">{row.label}</span>
998
- <code className="flex-1 truncate font-mono text-[9px] bg-muted px-1 rounded">{row.preview ?? "N/A"}</code>
999
- {row.expires && (
1000
- <span className="text-[9px] text-muted-foreground shrink-0">
1001
- {row.expires > Math.floor(Date.now() / 1000)
1002
- ? `${Math.floor((row.expires - Math.floor(Date.now() / 1000)) / 60)}m`
1003
- : `exp`}
1004
- </span>
1005
- )}
1006
- {row.token ? (
1007
- <Button
1008
- size="sm"
1009
- variant="outline"
1010
- className="h-5 text-[9px] px-1.5 cursor-pointer shrink-0"
1011
- disabled={test?.loading}
1012
- onClick={() => testRawTokenClick(row.key, row.token!)}
1013
- >
1014
- {test?.loading ? <Loader2 className="size-2.5 animate-spin" /> : "Test"}
1015
- </Button>
1016
- ) : <span className="text-[9px] text-muted-foreground">-</span>}
1017
- {test && !test.loading && (
1018
- <span className={`text-[9px] font-medium shrink-0 ${test.status?.startsWith("valid") ? "text-success" : "text-error"}`}>
1019
- {test.status}
1020
- </span>
1021
- )}
1022
- </div>
1023
- );
1024
- })}
1025
- </div>
1026
- ))}
1027
- </div>
1028
- ))}
1029
- </div>
1030
- )}
1031
- </div>
1032
- </div>
1033
- <DialogFooter>
1034
- <Button size="sm" variant="outline" className="text-xs h-7 cursor-pointer" onClick={() => setShowTokenTest(false)}>
1035
- Close
1036
- </Button>
1037
- </DialogFooter>
1038
- </DialogContent>
1039
- </Dialog>
1040
-
1041
- {/* Import dialog — paste/file data + password */}
1042
- <Dialog open={showImportDialog} onOpenChange={(v) => { if (!v) setShowImportDialog(false); }}>
1043
- <DialogContent className="sm:max-w-md">
1044
- <DialogHeader>
1045
- <DialogTitle className="text-sm flex items-center gap-1.5"><Lock className="size-3.5" /> Import Accounts</DialogTitle>
1046
- <DialogDescription className="text-xs">Paste backup data and enter the export password. Imported accounts are temporary (~1h).</DialogDescription>
1047
- </DialogHeader>
1048
- <div className="space-y-3">
1049
- <div className="space-y-1.5">
1050
- <Label className="text-xs">Backup data</Label>
1051
- <textarea
1052
- value={importData}
1053
- onChange={(e) => setImportData(e.target.value)}
1054
- placeholder="Paste backup JSON here..."
1055
- rows={4}
1056
- className="w-full text-xs p-2 rounded border border-border bg-background font-mono resize-none focus:outline-none focus:ring-1 focus:ring-primary"
1057
- />
1058
- </div>
1059
- <div className="space-y-1.5">
1060
- <Label className="text-xs">Password</Label>
1061
- <Input
1062
- type="password"
1063
- placeholder="Password used during export"
1064
- value={importPassword}
1065
- onChange={(e) => setImportPassword(e.target.value)}
1066
- className="text-xs h-8"
1067
- autoComplete="current-password"
1068
- />
1069
- </div>
1070
- </div>
1071
- {importError && (
1072
- <div className="text-[11px] p-2 rounded bg-error/10 text-error">{importError}</div>
1073
- )}
1074
- <DialogFooter>
1075
- <Button size="sm" variant="outline" className="text-xs h-7 cursor-pointer" onClick={() => setShowImportDialog(false)}>
1076
- Cancel
1077
- </Button>
1078
- <Button
1079
- size="sm" className="text-xs h-7 cursor-pointer"
1080
- disabled={!importData.trim() || !importPassword || importing}
1081
- onClick={doImport}
1082
- >
1083
- {importing ? <><Loader2 className="size-3 animate-spin mr-1" /> Importing...</> : "Import"}
1084
- </Button>
1085
- </DialogFooter>
1086
- </DialogContent>
1087
- </Dialog>
1088
- </div>
1089
- );
1090
- }