@hienlh/ppm 0.19.2 → 0.19.4
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.
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +3 -2
- package/dist/web/assets/accounts-settings-section-CCFQV-RB.js +1 -0
- package/dist/web/assets/ai-resource-editor-BTWds-aC.js +1 -0
- package/dist/web/assets/ai-settings-section-CmoBsSpY.js +1 -0
- package/dist/web/assets/ai-settings-section-DjbYj4wK.js +1 -0
- package/dist/web/assets/{angular-html-BlcrLjUx.js → angular-html-BuHNQIbQ.js} +1 -1
- package/dist/web/assets/{angular-ts-BUjtceAJ.js → angular-ts-DHRWoLc-.js} +1 -1
- package/dist/web/assets/api-client-BogqsFna.js +1 -0
- package/dist/web/assets/api-settings-CTMdz_VF.js +1 -0
- package/dist/web/assets/{api-themes-YsZgf6Xj.js → api-themes-Cknp3Q-P.js} +1 -1
- package/dist/web/assets/{apl-B3g0v1pk.js → apl-C1VSWVei.js} +1 -1
- package/dist/web/assets/appearance-settings-section-vLmakLtA.js +1 -0
- package/dist/web/assets/architecture-PBZL5I3N-Ca5IpdzP.js +1 -0
- package/dist/web/assets/arrow-left-CyFcsyzD.js +1 -0
- package/dist/web/assets/{astro-FRnyn7cH.js → astro-RX-rChsZ.js} +1 -1
- package/dist/web/assets/{audio-preview-DWTRm4HC.js → audio-preview-CCd1jdje.js} +1 -1
- package/dist/web/assets/{blade-NLdOqUKw.js → blade-CZ6bxMbK.js} +1 -1
- package/dist/web/assets/{pencil-BlIIg6RH.js → brain-KDa9jFRq.js} +1 -1
- package/dist/web/assets/{bsl-CenHZfaU.js → bsl-BhgCXLWk.js} +1 -1
- package/dist/web/assets/{button-Cq8yixjf.js → button-BZtEJcUv.js} +1 -1
- package/dist/web/assets/{cairo-B_WR0aiQ.js → cairo-BrwygPkn.js} +1 -1
- package/dist/web/assets/calendar-clock-C1asT-gk.js +1 -0
- package/dist/web/assets/chat-tab-CLRlS8Jc.js +15 -0
- package/dist/web/assets/check-DN75Ucvz.js +1 -0
- package/dist/web/assets/chevron-up-8PbsNiJp.js +1 -0
- package/dist/web/assets/{cobol-DOynhWYj.js → cobol-C5TGW1Ir.js} +1 -1
- package/dist/web/assets/code-editor-CCwVu5F5.js +7 -0
- package/dist/web/assets/{coffee-Bn6dbw4P.js → coffee-CFgKlmgI.js} +1 -1
- package/dist/web/assets/{conflict-editor-Cgb-z12D.js → conflict-editor-CvWYcbwD.js} +1 -1
- package/dist/web/assets/context-menu-C13b1uT3.js +1 -0
- package/dist/web/assets/{cpp-CoZR9t8r.js → cpp-BhG5q3-d.js} +1 -1
- package/dist/web/assets/{crystal-BSheONSg.js → crystal-DvuI7_tY.js} +1 -1
- package/dist/web/assets/{csv-preview-BZMHfFH3.js → csv-preview-DzuDuyE_.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-gwPoVSCU.js → data-grid-overlay-editor-DP1xkog4.js} +1 -1
- package/dist/web/assets/database-viewer-D4XGEDHm.js +1 -0
- package/dist/web/assets/diff-viewer-B5N1X61R.js +4 -0
- package/dist/web/assets/{docx-preview-DqVDOMmv.js → docx-preview-CWIDVNZn.js} +1 -1
- package/dist/web/assets/{edge-DTtoslQu.js → edge-BSIEsmlI.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-CXSbSUBX.js → edit-diff-preview-DeHb85DN.js} +1 -1
- package/dist/web/assets/{elixir-CIBCZ3Zi.js → elixir-BF-lrGXM.js} +1 -1
- package/dist/web/assets/{elm-BLmb7K1H.js → elm-8mCsJOeN.js} +1 -1
- package/dist/web/assets/{erb-BBY8ApRd.js → erb-Dw5PUVKa.js} +1 -1
- package/dist/web/assets/{erlang-D5Zb5_P0.js → erlang-BvjBIA6O.js} +1 -1
- package/dist/web/assets/{esm-6F8v1jLL.js → esm-Bt9OwSnU.js} +1 -1
- package/dist/web/assets/explorer-body-BxLLjthF.js +2 -0
- package/dist/web/assets/explorer-window-content-CL1Cipj3.js +1 -0
- package/dist/web/assets/extension-manager-section-BpyGq15A.js +1 -0
- package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-BM8FXhuN.js} +1 -1
- package/dist/web/assets/{plus-BR0qewJI.js → eye-gWwaF4Lg.js} +1 -1
- package/dist/web/assets/eye-off-GjXVuAOn.js +1 -0
- package/dist/web/assets/files-settings-section-B0_aR1nL.js +1 -0
- package/dist/web/assets/flask-conical-tp8gyZGh.js +1 -0
- package/dist/web/assets/{fortran-fixed-form-CBdwkEUJ.js → fortran-fixed-form-CRjUJe9K.js} +1 -1
- package/dist/web/assets/{fsharp-AT4XdVYH.js → fsharp-CjUIHIHp.js} +1 -1
- package/dist/web/assets/{gdresource-D852fWBR.js → gdresource-x_NUQFrZ.js} +1 -1
- package/dist/web/assets/general-settings-section-C1EJzjhU.js +1 -0
- package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-BOpxSbns.js} +1 -1
- package/dist/web/assets/git-log-panel-D1yGTUE_.js +1 -0
- package/dist/web/assets/{git-rebase-C6opk8gr.js → git-rebase-BW4xlK7k.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BloZfB0Q.js +1 -0
- package/dist/web/assets/{glide-data-grid-CICFjJon.js → glide-data-grid-Cu0STKML.js} +12 -12
- package/dist/web/assets/{glimmer-js-DLi5mWYO.js → glimmer-js-CT9jrmFj.js} +1 -1
- package/dist/web/assets/{glimmer-ts-D-scFX30.js → glimmer-ts-CjQT1lxK.js} +1 -1
- package/dist/web/assets/{glsl-dbQzF4iA.js → glsl-DsFJvtg5.js} +1 -1
- package/dist/web/assets/{graphql-D1m5Wen2.js → graphql-Dhlnmkn_.js} +1 -1
- package/dist/web/assets/group-chat-tab-BfcvkRX5.js +1 -0
- package/dist/web/assets/{hack-CqYmpO7u.js → hack-BKHbsJ7u.js} +1 -1
- package/dist/web/assets/{haml-BXzKWEag.js → haml-C1Ih25Y2.js} +1 -1
- package/dist/web/assets/{handlebars-DXnrtHtq.js → handlebars-CaFitvmC.js} +1 -1
- package/dist/web/assets/{html-DhgQJ-xL.js → html-BwpKIE6V.js} +1 -1
- package/dist/web/assets/{html-derivative-xZRft-YE.js → html-derivative-uuTEquZU.js} +1 -1
- package/dist/web/assets/{http-B53mSj14.js → http-Cv4Pg5cR.js} +1 -1
- package/dist/web/assets/{hurl-CsomVy04.js → hurl-DubkI6TG.js} +1 -1
- package/dist/web/assets/{hxml-LffjWwKA.js → hxml-B9OVZFAB.js} +1 -1
- package/dist/web/assets/{image-preview-CNA6XMNS.js → image-preview-DRbZ0FA2.js} +1 -1
- package/dist/web/assets/index-B1bzKRjJ.js +52 -0
- package/dist/web/assets/index-nP3uxXvR.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-c0W8wp5o.js +1 -0
- package/dist/web/assets/{jinja-CCHURl7V.js → jinja-BHb1jyl6.js} +1 -1
- package/dist/web/assets/jira-watcher-section-DZh-Z2MK.js +1 -0
- package/dist/web/assets/{jison-7qBd8dnR.js → jison-CtpfXDzp.js} +1 -1
- package/dist/web/assets/{julia-BY9pnTcy.js → julia-TYjgpazU.js} +1 -1
- package/dist/web/assets/{just-D7wRSNRr.js → just-D27bzZuW.js} +1 -1
- package/dist/web/assets/key-round-CQpDrDxg.js +1 -0
- package/dist/web/assets/keybindings-store-CFwgcnpZ.js +1 -0
- package/dist/web/assets/keyboard-shortcuts-section-JBtcJGM3.js +1 -0
- package/dist/web/assets/{latex-RgsCae55.js → latex-D_MzopTn.js} +1 -1
- package/dist/web/assets/{liquid-CnDFzvOZ.js → liquid-BG_FG0Pe.js} +1 -1
- package/dist/web/assets/{lua-0lj5W8sm.js → lua-Bl29oC2L.js} +1 -1
- package/dist/web/assets/{markdown-renderer-dbLIvU2M.js → markdown-renderer-Bo8GgGXl.js} +3 -3
- package/dist/web/assets/markdown-renderer-DY60UZNW.js +1 -0
- package/dist/web/assets/{marko-CQgqAhbV.js → marko-D-HI7dbm.js} +1 -1
- package/dist/web/assets/{mdc-BkIcTiIy.js → mdc-y444JQTQ.js} +1 -1
- package/dist/web/assets/{media-url-DDciwvLU.js → media-url-VzecHk9g.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-pjcHKorA.js → mobile-bottom-sheet-BD5b7KJF.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-C59rPw7Z.js +1 -0
- package/dist/web/assets/{nextflow-DvIW68DS.js → nextflow-C5sKcldC.js} +1 -1
- package/dist/web/assets/{nginx-O-hft0gG.js → nginx-bXnEC3wm.js} +1 -1
- package/dist/web/assets/{nim-BxrR4HwQ.js → nim-pevhf4WD.js} +1 -1
- package/dist/web/assets/notification-store-Bd6L0K_Q.js +1 -0
- package/dist/web/assets/notifications-settings-section-ARQlF-6E.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BoLRYuZT.js → number-overlay-editor-B7WINIW4.js} +1 -1
- package/dist/web/assets/{overview-panel-l3g1gT5e.js → overview-panel-mhN0qw6Q.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D_BoHr_M.js +1 -0
- package/dist/web/assets/panel-store-BRrnDO_i.js +1 -0
- package/dist/web/assets/pdf-preview-CRShdbBS.js +1 -0
- package/dist/web/assets/{perl-Da-uNqmL.js → perl-B1KUtMnW.js} +1 -1
- package/dist/web/assets/{php-JMu45mEx.js → php-BGmVZBAJ.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BaLfuEf8.js +1 -0
- package/dist/web/assets/{postgres-viewer-DstZwUai.js → postgres-viewer-BNT-4yYl.js} +3 -3
- package/dist/web/assets/power-CriQkeK5.js +1 -0
- package/dist/web/assets/ppmbot-settings-section-Bq4YAcFW.js +1 -0
- package/dist/web/assets/{process-table-kmQ2-8qJ.js → process-table-CjiXW0rg.js} +1 -1
- package/dist/web/assets/{project-store-DTUCS65m.js → project-store-BuY7rxn_.js} +1 -1
- package/dist/web/assets/proxy-settings-section-wRbiyQiA.js +3 -0
- package/dist/web/assets/{pug-CLzyQhia.js → pug-BfN8CpEH.js} +1 -1
- package/dist/web/assets/{qml-BHXfGB1l.js → qml-D6fRdLZt.js} +1 -1
- package/dist/web/assets/query-audit-section-C5RmhRSu.js +1 -0
- package/dist/web/assets/radar-KQ55EAFF-DBIZEaIU.js +1 -0
- package/dist/web/assets/{razor-BqetJLN3.js → razor-fg-9AScR.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-BF-75_03.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-C9KR9I9G.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BnKJicB2.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-awXsNw_R.js +1 -0
- package/dist/web/assets/{rst-iQdJZ0BP.js → rst-BZgx076b.js} +1 -1
- package/dist/web/assets/{ruby-CNwzZGdH.js → ruby-iUkWDcLo.js} +1 -1
- package/dist/web/assets/{run-in-terminal-CBXmVGWa.js → run-in-terminal-bKRwuO2z.js} +1 -1
- package/dist/web/assets/{sas-BpBIT2eR.js → sas-CLXO0lZ6.js} +1 -1
- package/dist/web/assets/schedules-settings-section-BOecKJzl.js +1 -0
- package/dist/web/assets/{scss-D1MotuEp.js → scss-DQqteGaG.js} +1 -1
- package/dist/web/assets/send-Bb0jtcR6.js +1 -0
- package/dist/web/assets/{send-to-chat-DJAdlvJ8.js → send-to-chat-S8LCNX7V.js} +1 -1
- package/dist/web/assets/separator-DqGg3f9c.js +1 -0
- package/dist/web/assets/settings-body-DGi5DXY6.js +2 -0
- package/dist/web/assets/settings-store-Brjd0QnO.js +2 -0
- package/dist/web/assets/settings-tab-GM9zTRul.js +1 -0
- package/dist/web/assets/settings-window-content-Dvhxo4_H.js +1 -0
- package/dist/web/assets/{shaderlab-BNqmcPQi.js → shaderlab-CeMTqagR.js} +1 -1
- package/dist/web/assets/{shellsession-BBLOVOO-.js → shellsession-BAkquZAV.js} +1 -1
- package/dist/web/assets/{soy-BpTGo9uy.js → soy-Bxm9Ez55.js} +1 -1
- package/dist/web/assets/{sparql-D7A3yKob.js → sparql-C1phwdLc.js} +1 -1
- package/dist/web/assets/sql-query-editor-BmkGFzcL.js +1 -0
- package/dist/web/assets/sqlite-viewer-DbziVQXQ.js +1 -0
- package/dist/web/assets/{stata-B7N56ptS.js → stata-DGNMQdEb.js} +1 -1
- package/dist/web/assets/{surrealql-CTmnD-lv.js → surrealql-ByVrV320.js} +1 -1
- package/dist/web/assets/{svelte-BppwY9TR.js → svelte-CNWjIMhl.js} +1 -1
- package/dist/web/assets/system-monitor-body-CbHW5aYm.js +2 -0
- package/dist/web/assets/system-monitor-tab-C7EZLMIo.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-B9Y98UW6.js → system-monitor-window-content-C_w4w81e.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-C1j8zr68.js → tab-host-window-content-BSP1SEXS.js} +1 -1
- package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-PZsP5ZwG.js} +1 -1
- package/dist/web/assets/team-member-sheet-C9N0iA1-.js +2 -0
- package/dist/web/assets/team-member-window-content-CM2QFv-s.js +1 -0
- package/dist/web/assets/{templ-CvTMKKra.js → templ-Cf0gQL4I.js} +1 -1
- package/dist/web/assets/{terminal-tab-B9I_HlES.js → terminal-tab-jWYELm8y.js} +3 -3
- package/dist/web/assets/{tex--c4POEUE.js → tex-BTIqPw_R.js} +1 -1
- package/dist/web/assets/tool-cards-jefdOWVJ.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-DFiG9UYW.js +1 -0
- package/dist/web/assets/{ts-tags--kx7D-Hw.js → ts-tags-B8dN_Yf6.js} +1 -1
- package/dist/web/assets/{twig-UG9uMHGd.js → twig-BgemonbJ.js} +1 -1
- package/dist/web/assets/use-accounts-data-MhMl0Y4Z.js +1 -0
- package/dist/web/assets/{use-blob-url-Bdxopwwc.js → use-blob-url-Gfi69PtP.js} +1 -1
- package/dist/web/assets/use-monaco-theme-BbAd7DXr.js +1 -0
- package/dist/web/assets/{use-open-team-member-D9EOB8-i.js → use-open-team-member-CruUTqzD.js} +1 -1
- package/dist/web/assets/use-remote-desktop-display-choice-DlfMIWq3.js +1 -0
- package/dist/web/assets/{use-websocket-xOOwBdwP.js → use-websocket-Bq8X_SUI.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-0Nt-ew76.js → vendor-mermaid-BKWscKt0.js} +1 -1
- package/dist/web/assets/video-preview-BGh3B0Lu.js +1 -0
- package/dist/web/assets/{vue-jjTomlbt.js → vue-D3D4lbXz.js} +1 -1
- package/dist/web/assets/{vue-html-8KEW6-I9.js → vue-html-DYEQFAiK.js} +1 -1
- package/dist/web/assets/{vue-vine-C5tohCkU.js → vue-vine-D0OPNscN.js} +1 -1
- package/dist/web/assets/{window-pip-registry-FdZdGF7h.js → window-pip-registry-BEN0QNSW.js} +1 -1
- package/dist/web/assets/window-store-DGKf9rPG.js +1 -0
- package/dist/web/assets/ws-auth-Dawx17PM.js +1 -0
- package/dist/web/assets/{xml-ByFFosqL.js → xml-DHaWgpya.js} +1 -1
- package/dist/web/assets/{xsl-CZUwiLkj.js → xsl-DVRN8JsA.js} +1 -1
- package/dist/web/index.html +32 -55
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +9 -5
- package/docs/lessons-learned.md +11 -0
- package/package.json +2 -2
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/src/providers/codex-app-server/codex-event-mapper.ts +70 -5
- package/src/providers/codex-app-server/codex-history.ts +24 -0
- package/src/providers/codex-app-server/codex-jsonrpc-client.ts +39 -2
- package/src/providers/codex-app-server/codex-protocol.ts +24 -0
- package/src/providers/codex-app-server/codex-provider.ts +159 -17
- package/src/providers/codex-app-server/codex-rollout-items.ts +149 -0
- package/src/providers/codex-app-server/codex-skill-parser.ts +63 -0
- package/src/providers/codex-app-server/codex-usage-fetch.ts +22 -20
- package/src/providers/codex-app-server/codex-usage-source.ts +37 -0
- package/src/server/routes/chat.ts +67 -9
- package/src/server/routes/codex-accounts.ts +9 -5
- package/src/server/ws/chat.ts +46 -1
- package/src/services/claude-usage-source.ts +40 -0
- package/src/services/claude-usage.service.ts +77 -146
- package/src/services/codex-account-login.ts +123 -55
- package/src/services/codex-account.service.ts +27 -4
- package/src/services/db.service.ts +161 -20
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-credential-path-guard.ts +35 -8
- package/src/services/fs-path-guard.service.ts +1 -0
- package/src/services/postgres.service.ts +144 -72
- package/src/services/provider-usage/index.ts +63 -0
- package/src/services/provider-usage/register-usage-sources.ts +18 -0
- package/src/services/provider-usage/usage-memory-cache.ts +71 -0
- package/src/services/provider-usage/usage-registry.ts +157 -0
- package/src/services/provider-usage/usage-scheduler.ts +117 -0
- package/src/services/provider-usage/usage-snapshot-store.ts +126 -0
- package/src/services/provider-usage/usage-source.ts +82 -0
- package/src/services/proxy-agent-anthropic-bridge.ts +93 -0
- package/src/services/proxy-agent-bridge.ts +100 -0
- package/src/services/proxy-agent-turn.ts +161 -0
- package/src/services/proxy-anthropic-format.ts +157 -0
- package/src/services/proxy-openai-format.ts +148 -0
- package/src/services/slash-discovery/builtin-commands.ts +13 -0
- package/src/services/slash-discovery/codex-skill-items.ts +57 -0
- package/src/services/slash-discovery/provider-skill-sigil.ts +19 -0
- package/src/services/slash-discovery/types.ts +19 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/types/chat.ts +7 -1
- package/src/web/app.tsx +1 -7
- package/src/web/components/chat/chat-history-bar.tsx +10 -1
- package/src/web/components/chat/chat-tab.tsx +17 -1
- package/src/web/components/chat/message-input.tsx +11 -4
- package/src/web/components/chat/message-list.tsx +14 -0
- package/src/web/components/chat/slash-command-picker.tsx +22 -3
- package/src/web/components/chat/tool-cards.tsx +47 -4
- package/src/web/components/chat/usage-badge.tsx +59 -426
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/layout/command-palette.tsx +2 -2
- package/src/web/components/layout/mobile-drawer.tsx +5 -4
- package/src/web/components/layout/mobile-nav.tsx +5 -1
- package/src/web/components/layout/nav-section-rail.tsx +2 -1
- package/src/web/components/layout/project-bottom-sheet.tsx +3 -9
- package/src/web/components/layout/sidebar.tsx +0 -2
- package/src/web/components/layout/tab-pop-out-menu-item.tsx +3 -2
- package/src/web/components/settings/accounts/account-add-dialog.tsx +155 -0
- package/src/web/components/settings/accounts/account-backup-format.ts +19 -0
- package/src/web/components/settings/accounts/account-bucket-row.tsx +55 -0
- package/src/web/components/settings/accounts/account-card.tsx +151 -0
- package/src/web/components/settings/accounts/account-delete-confirm.tsx +62 -0
- package/src/web/components/settings/accounts/account-export-dialog.tsx +154 -0
- package/src/web/components/settings/accounts/account-export-simulation.tsx +196 -0
- package/src/web/components/settings/accounts/account-import-dialog.tsx +72 -0
- package/src/web/components/settings/accounts/account-profile-panel.tsx +52 -0
- package/src/web/components/{chat → settings/accounts}/account-rotation-settings.tsx +1 -1
- package/src/web/components/settings/accounts/account-token-test-dialog.tsx +147 -0
- package/src/web/components/settings/accounts/account-usage-format.ts +92 -0
- package/src/web/components/{chat/usage-pattern-chart.tsx → settings/accounts/account-usage-pattern-chart.tsx} +1 -1
- package/src/web/components/settings/accounts/accounts-pane-header.tsx +113 -0
- package/src/web/components/settings/accounts/accounts-settings-section.tsx +80 -0
- package/src/web/components/settings/accounts/claude-accounts-section.tsx +169 -0
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +144 -0
- package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +130 -0
- package/src/web/components/settings/accounts/codex-backup-dialog.tsx +101 -0
- package/src/web/components/settings/accounts/codex-rotation-dialog.tsx +65 -0
- package/src/web/components/settings/accounts/use-accounts-data.ts +81 -0
- package/src/web/components/settings/accounts/use-codex-accounts.ts +151 -0
- package/src/web/components/settings/ai-settings-section.tsx +14 -4
- package/src/web/components/settings/appearance-settings-section.tsx +125 -0
- package/src/web/components/settings/general-settings-section.tsx +87 -0
- package/src/web/components/settings/jira-watcher-section.tsx +34 -0
- package/src/web/components/settings/notifications-settings-section.tsx +37 -0
- package/src/web/components/settings/open-settings.ts +42 -0
- package/src/web/components/settings/resolve-open-settings-action.ts +45 -0
- package/src/web/components/settings/settings-body.tsx +110 -0
- package/src/web/components/settings/settings-categories.ts +102 -0
- package/src/web/components/settings/settings-category-list.tsx +56 -0
- package/src/web/components/settings/settings-category-rail.tsx +62 -0
- package/src/web/components/settings/settings-section-content.tsx +43 -0
- package/src/web/components/settings/settings-tab.tsx +15 -329
- package/src/web/components/settings/settings-window-content.tsx +24 -0
- package/src/web/hooks/use-chat.ts +38 -1
- package/src/web/hooks/use-global-keybindings.ts +3 -4
- package/src/web/hooks/use-url-sync.ts +30 -0
- package/src/web/hooks/use-usage.ts +23 -11
- package/src/web/lib/sidebar-tabs/tab-registry.ts +9 -9
- package/src/web/lib/slash-items-cache.ts +27 -7
- package/src/web/stores/panel-utils.ts +11 -0
- package/src/web/stores/settings-store.ts +4 -2
- package/src/web/stores/window-panel-actions.ts +1 -3
- package/src/web/stores/window-panel-persistence.ts +9 -4
- package/dist/web/assets/adaptive-context-menu-BbuRVTLn.js +0 -1
- package/dist/web/assets/ai-resource-editor-BCCOUzox.js +0 -1
- package/dist/web/assets/ai-settings-section-BEM1z7kp.js +0 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +0 -1
- package/dist/web/assets/api-settings-CNtKkHqW.js +0 -1
- package/dist/web/assets/api-settings-Cubuqjsu.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +0 -1
- package/dist/web/assets/chat-tab-CmXGsJGc.js +0 -15
- package/dist/web/assets/clipboard-B-gwJ8DF.js +0 -1
- package/dist/web/assets/code-editor-DtHXzNZ1.js +0 -7
- package/dist/web/assets/context-menu-DfWZAxQy.js +0 -1
- package/dist/web/assets/database-viewer-GVaQVMdb.js +0 -1
- package/dist/web/assets/dialog-BSfuiTkJ.js +0 -1
- package/dist/web/assets/diff-viewer-BG5_fH1E.js +0 -4
- package/dist/web/assets/explorer-body-BPFnjxYj.js +0 -2
- package/dist/web/assets/explorer-window-content-Da1T9uM4.js +0 -1
- package/dist/web/assets/extension-store-Bhu--fPX.js +0 -1
- package/dist/web/assets/external-link-B6Hl0U4X.js +0 -1
- package/dist/web/assets/eye-off-CUdzgmX8.js +0 -1
- package/dist/web/assets/file-store-6HW2Lfl4.js +0 -1
- package/dist/web/assets/git-log-panel-DNC9fV0M.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +0 -1
- package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +0 -1
- package/dist/web/assets/index-DTqbMiVc.js +0 -52
- package/dist/web/assets/index-SFFv2-VM.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +0 -1
- package/dist/web/assets/input-D0ol5jW0.js +0 -1
- package/dist/web/assets/keybindings-store-BGkB72gM.js +0 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +0 -1
- package/dist/web/assets/lock-CNxUGpTk.js +0 -1
- package/dist/web/assets/markdown-renderer-DEAVpo0C.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +0 -1
- package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +0 -1
- package/dist/web/assets/notification-store-Dgbsw9p9.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +0 -1
- package/dist/web/assets/panel-store-UOV8wrfR.js +0 -1
- package/dist/web/assets/pdf-preview-B2CJ_EKv.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +0 -1
- package/dist/web/assets/play-DpEEBJTT.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +0 -1
- package/dist/web/assets/refresh-cw-BQU5g2CY.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-C1ST4-Fm.js +0 -1
- package/dist/web/assets/remote-desktop-readiness-gate-BH3BE5wA.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-BaZJQt_N.js +0 -1
- package/dist/web/assets/rotate-ccw-CpjRo90J.js +0 -1
- package/dist/web/assets/scroll-area-lc0ZHORr.js +0 -1
- package/dist/web/assets/send-C_sLFt56.js +0 -1
- package/dist/web/assets/settings-store-zbPkG7o-.js +0 -2
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +0 -1
- package/dist/web/assets/settings-tab-xyRpt2gq.js +0 -3
- package/dist/web/assets/shallow-CmPMAPte.js +0 -1
- package/dist/web/assets/sql-query-editor-B86NiZHI.js +0 -1
- package/dist/web/assets/sqlite-viewer-DB775F6C.js +0 -1
- package/dist/web/assets/switch-BmjKoIpR.js +0 -1
- package/dist/web/assets/system-monitor-body-BDxHQvy9.js +0 -2
- package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +0 -1
- package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +0 -2
- package/dist/web/assets/team-member-window-content-CUSuRHCB.js +0 -1
- package/dist/web/assets/tool-cards-D3KSzd2K.js +0 -8
- package/dist/web/assets/trash-2-By4VMbC4.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +0 -1
- package/dist/web/assets/triangle-alert-CUTURFbG.js +0 -1
- package/dist/web/assets/upload-BNlX8D07.js +0 -1
- package/dist/web/assets/use-monaco-theme-CVCye2kq.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-neUqyE3p.js +0 -1
- package/dist/web/assets/video-preview-DvpgBDhM.js +0 -1
- package/dist/web/assets/window-store-D0x_fxr0.js +0 -1
- package/src/web/components/chat/account-dialogs.tsx +0 -384
- package/src/web/components/settings/accounts-settings-section.tsx +0 -1090
- package/src/web/components/settings/codex-accounts-section.tsx +0 -198
- package/src/web/components/settings/telegram-settings-section.tsx +0 -113
- /package/dist/web/assets/{abap-C5DFF0s3.js → abap-h3QyH8MW.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B0qyRNbc.js → actionscript-3-mkxkq-FN.js} +0 -0
- /package/dist/web/assets/{ada-bAJzplug.js → ada-DhISK_EH.js} +0 -0
- /package/dist/web/assets/{andromeeda-DUCpISy6.js → andromeeda-DnlWWesX.js} +0 -0
- /package/dist/web/assets/{apache-BhAr84Z_.js → apache-P5AK3sfA.js} +0 -0
- /package/dist/web/assets/{apex-CRv8YnNq.js → apex-CJUPehg2.js} +0 -0
- /package/dist/web/assets/{api-client-BzWM7XPR.js → api-client-oY1tC3tM.js} +0 -0
- /package/dist/web/assets/{applescript-CD-Cw8bt.js → applescript-Dx6GGbay.js} +0 -0
- /package/dist/web/assets/{apply-theme-DJ794zSQ.js → apply-theme-DxTXOAcV.js} +0 -0
- /package/dist/web/assets/{ara-BUzeQYoG.js → ara-95Kx3qKb.js} +0 -0
- /package/dist/web/assets/{asciidoc-CT2hTg-v.js → asciidoc-Dp0RbIej.js} +0 -0
- /package/dist/web/assets/{asm-CAu35JeY.js → asm-C82coUjU.js} +0 -0
- /package/dist/web/assets/{aurora-x-BeFJoIA1.js → aurora-x-BFc3zeop.js} +0 -0
- /package/dist/web/assets/{awk-Vau-DrGN.js → awk-ChjC0icr.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CLe-OTsQ.js → ayu-dark-DYfUf_PB.js} +0 -0
- /package/dist/web/assets/{ayu-light-CJbQfu4M.js → ayu-light-Bh6O0plO.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-GWaKwdrx.js → ayu-mirage-DlNhHTGq.js} +0 -0
- /package/dist/web/assets/{ballerina-DWu0USI5.js → ballerina-Ceoll3Sk.js} +0 -0
- /package/dist/web/assets/{bat-_07Huw7E.js → bat-C7QS6Lfq.js} +0 -0
- /package/dist/web/assets/{beancount-EcvogrcF.js → beancount-5NMEAZgs.js} +0 -0
- /package/dist/web/assets/{berry-BBB87SY7.js → berry-BBL83Vbk.js} +0 -0
- /package/dist/web/assets/{bibtex-DbueS87A.js → bibtex-L7rPkPSG.js} +0 -0
- /package/dist/web/assets/{bicep-tcmXnUo4.js → bicep-BOwHIINh.js} +0 -0
- /package/dist/web/assets/{bird2-B2qwilIR.js → bird2-CWLsMom2.js} +0 -0
- /package/dist/web/assets/{c-Z58oivSt.js → c-CD4Ku5Cr.js} +0 -0
- /package/dist/web/assets/{c3-U40cGT9p.js → c3-CpMl-w3G.js} +0 -0
- /package/dist/web/assets/{cadence-BFcD-LyM.js → cadence-CvgF6ehN.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-CdCK2c2a.js → can-open-in-ppm-j3jh7fUo.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-RkC9AX6J.js → catppuccin-frappe-eo_dTm3F.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-Bsi4l-nO.js → catppuccin-latte-DoiMCU_7.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B8JIdTW-.js → catppuccin-macchiato-UQxsK2Vh.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-oH8wwrDj.js → catppuccin-mocha-J0i-8RNy.js} +0 -0
- /package/dist/web/assets/{clarity-C3WNeRCR.js → clarity-DXYM8-SA.js} +0 -0
- /package/dist/web/assets/{clojure-CFoAz7u9.js → clojure-DzamsL69.js} +0 -0
- /package/dist/web/assets/{cmake-Bcsqp9yR.js → cmake-CR6RP_T_.js} +0 -0
- /package/dist/web/assets/{codeowners-DC2Z7heP.js → codeowners-D1s6-IyY.js} +0 -0
- /package/dist/web/assets/{codeql-BfG68Al5.js → codeql-DAqNWMkS.js} +0 -0
- /package/dist/web/assets/{common-lisp-Z5P-lKQe.js → common-lisp-8e-KtitB.js} +0 -0
- /package/dist/web/assets/{coq-BJslqrnv.js → coq-UEq0WUcO.js} +0 -0
- /package/dist/web/assets/{csharp-DhhZTOTs.js → csharp-wxJ0TuCm.js} +0 -0
- /package/dist/web/assets/{css-DiFIpMfE.js → css-Cerub-yB.js} +0 -0
- /package/dist/web/assets/{csv-DViLbHxI.js → csv-Bh9_WD_h.js} +0 -0
- /package/dist/web/assets/{csv-parser-B2v8MoUy.js → csv-parser-BPIDt6ni.js} +0 -0
- /package/dist/web/assets/{cue-CoG3mg2I.js → cue-rzM1ztqU.js} +0 -0
- /package/dist/web/assets/{cypher-jTxC8NAB.js → cypher-DGaTQ8m0.js} +0 -0
- /package/dist/web/assets/{d-6hKIUAbU.js → d-DbkZGuhB.js} +0 -0
- /package/dist/web/assets/{dark-plus-D_-np2Sr.js → dark-plus-BO2bzZcA.js} +0 -0
- /package/dist/web/assets/{dart-CVpxLpVn.js → dart-BDhCoqVg.js} +0 -0
- /package/dist/web/assets/{data-grid-types-Dq_H1IPa.js → data-grid-types-CdAg-4W1.js} +0 -0
- /package/dist/web/assets/{dax-BPqJ8qIY.js → dax-Bh03stWh.js} +0 -0
- /package/dist/web/assets/{desktop-u_y5_Hr5.js → desktop-BOdr_fwq.js} +0 -0
- /package/dist/web/assets/{diff-Duj1XMrd.js → diff-Bb_gTsS2.js} +0 -0
- /package/dist/web/assets/{dist-CEZ4plgH.js → dist-BUMDu0av.js} +0 -0
- /package/dist/web/assets/{dist-aDSxTR_v.js → dist-DonmFWG8.js} +0 -0
- /package/dist/web/assets/{docker-8UGSCc7N.js → docker-BDsTF5ce.js} +0 -0
- /package/dist/web/assets/{dotenv-DVnj8kyR.js → dotenv-CbCWp9Ti.js} +0 -0
- /package/dist/web/assets/{dracula-D0cEmSg7.js → dracula-Cd1fxvoX.js} +0 -0
- /package/dist/web/assets/{dracula-soft-DfGS5Yfr.js → dracula-soft-B7eXkfXG.js} +0 -0
- /package/dist/web/assets/{dream-maker-qG8kMGTF.js → dream-maker-XlzuS21R.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-iVqyMLyT.js → emacs-lisp-CXV1Ki_7.js} +0 -0
- /package/dist/web/assets/{everforest-dark-Cruygd5U.js → everforest-dark-DuqR60Dg.js} +0 -0
- /package/dist/web/assets/{everforest-light-BiJa8RHL.js → everforest-light-q4BPnenD.js} +0 -0
- /package/dist/web/assets/{fennel-H6XSEIoz.js → fennel-B-e0TX2Q.js} +0 -0
- /package/dist/web/assets/{fish-ho_7CxX6.js → fish-Bzb6-TtF.js} +0 -0
- /package/dist/web/assets/{fluent-1l5ssJTF.js → fluent-B-szH2-f.js} +0 -0
- /package/dist/web/assets/{folder-symlink-Dx1hOk8X.js → folder-symlink-fQRoG_rV.js} +0 -0
- /package/dist/web/assets/{format-bytes-gQw5wzqa.js → format-bytes-CKXk9k2d.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-Bo1x233A.js → fortran-free-form-OS6SGYC0.js} +0 -0
- /package/dist/web/assets/{gdscript-DffFMp_k.js → gdscript-FYq4OP2d.js} +0 -0
- /package/dist/web/assets/{gdshader-CvnpAiAF.js → gdshader-BpG6P8rw.js} +0 -0
- /package/dist/web/assets/{genie-2eOvAEzW.js → genie-BS9RLAna.js} +0 -0
- /package/dist/web/assets/{gherkin-DL-5OUlV.js → gherkin-B_9FdtGs.js} +0 -0
- /package/dist/web/assets/{github-dark-DrTZprgG.js → github-dark-cfaIlnBV.js} +0 -0
- /package/dist/web/assets/{github-dark-default-_mDH8IMP.js → github-dark-default-B2Wv65He.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-C48hJuLn.js → github-dark-dimmed-CtM9X76s.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-BQ-zGGNe.js → github-dark-high-contrast-DLHyk0tG.js} +0 -0
- /package/dist/web/assets/{github-light-BYc90Yx8.js → github-light-DWEI10iG.js} +0 -0
- /package/dist/web/assets/{github-light-default-Daecj4J5.js → github-light-default-DcJkK1YN.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DoafCbsU.js → github-light-high-contrast-Bcx9iKDt.js} +0 -0
- /package/dist/web/assets/{gleam-hUWWjmnp.js → gleam-DVpiM6b2.js} +0 -0
- /package/dist/web/assets/{gn-7PJ3R70o.js → gn-B80DNc0i.js} +0 -0
- /package/dist/web/assets/{gnuplot-V-Pxck7b.js → gnuplot-D9i3MaT-.js} +0 -0
- /package/dist/web/assets/{go-ARF6DprD.js → go-CwCBf_Pt.js} +0 -0
- /package/dist/web/assets/{groovy-CKGBIPOz.js → groovy-D7mEfwmW.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-Cm9wqdI3.js → gruvbox-dark-hard-C4R4MOoH.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cmsd6dG8.js → gruvbox-dark-medium-CZnFC0s8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-BBGlGGCz.js → gruvbox-dark-soft-DFcJlEBl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-BCR7KmqG.js → gruvbox-light-hard-B6IF_1L-.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-CP32bga8.js → gruvbox-light-medium-C19AZJv2.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-qjxk8ps6.js → gruvbox-light-soft-BAnpDm9B.js} +0 -0
- /package/dist/web/assets/{haskell-C5DzwGRa.js → haskell-CsXSYXRG.js} +0 -0
- /package/dist/web/assets/{haxe-C8f_jCVv.js → haxe-B1uyiI9t.js} +0 -0
- /package/dist/web/assets/{hcl-DetJkf6E.js → hcl-DlYKn5q9.js} +0 -0
- /package/dist/web/assets/{hjson-DeZhwIdo.js → hjson-C_0VMGJJ.js} +0 -0
- /package/dist/web/assets/{hlsl-SMIDLRyX.js → hlsl-zt2PEzbg.js} +0 -0
- /package/dist/web/assets/{horizon-BMAVDxMx.js → horizon-6wQfyOK0.js} +0 -0
- /package/dist/web/assets/{horizon-bright-3BaV_VGD.js → horizon-bright-CqI2qTim.js} +0 -0
- /package/dist/web/assets/{houston-BWBbEPvb.js → houston-BljpHDoG.js} +0 -0
- /package/dist/web/assets/{hy-DcsmE-Nm.js → hy-XzuL55dw.js} +0 -0
- /package/dist/web/assets/{image-gallery-Bqb-wjNw.js → image-gallery-Cj8nd4xW.js} +0 -0
- /package/dist/web/assets/{imba-CnkgnYMf.js → imba-CqhhjBrP.js} +0 -0
- /package/dist/web/assets/{ini-BnvTdau7.js → ini-CiFDi6cY.js} +0 -0
- /package/dist/web/assets/{java-BLFACXat.js → java-OsQgTL5-.js} +0 -0
- /package/dist/web/assets/{javascript-BKcFWENk.js → javascript-qWAOScxl.js} +0 -0
- /package/dist/web/assets/{json-3AJtJADf.js → json-RiAKsaD1.js} +0 -0
- /package/dist/web/assets/{json5-DLyDVlwe.js → json5-BeCnS3H2.js} +0 -0
- /package/dist/web/assets/{jsonc-B5DgF1jW.js → jsonc-CbTW1Mka.js} +0 -0
- /package/dist/web/assets/{jsonl-CV_O71a2.js → jsonl-BLSi_669.js} +0 -0
- /package/dist/web/assets/{jsonnet-DZfPLgYz.js → jsonnet-C3xAggou.js} +0 -0
- /package/dist/web/assets/{jssm-Cr1zYOsY.js → jssm-CLdiagC9.js} +0 -0
- /package/dist/web/assets/{jsx-VN-JS2_q.js → jsx-lYE1lb50.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-D9CcT3xg.js → kanagawa-dragon-Cd1vcrpD.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-DLXsB8fr.js → kanagawa-lotus-Dw3QKtk9.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-weDs3Wjz.js → kanagawa-wave-DIq5Zv5G.js} +0 -0
- /package/dist/web/assets/{kdl-0XF56jnT.js → kdl-BfDV0825.js} +0 -0
- /package/dist/web/assets/{keyboard-DBBaoQSu.js → keyboard-DiOYrs6C.js} +0 -0
- /package/dist/web/assets/{kotlin-BPkH9vzk.js → kotlin-DONxR4wD.js} +0 -0
- /package/dist/web/assets/{kusto-DMVpADb9.js → kusto-BDP63Fh5.js} +0 -0
- /package/dist/web/assets/{laserwave-Cl6T2cGP.js → laserwave-BeBrWApL.js} +0 -0
- /package/dist/web/assets/{lean-BvwzQGl1.js → lean-DOZc0N1A.js} +0 -0
- /package/dist/web/assets/{less-DI_03xkC.js → less-C3H9NlDv.js} +0 -0
- /package/dist/web/assets/{lib-v9x_qXgI.js → lib-xrSJ4EZX.js} +0 -0
- /package/dist/web/assets/{libesm-dcbw4_18.js → libesm-DLBE59ul.js} +0 -0
- /package/dist/web/assets/{light-plus-hIiIsQN_.js → light-plus-C_kTL608.js} +0 -0
- /package/dist/web/assets/{llvm-B5hjjSWc.js → llvm-Dsp27U3P.js} +0 -0
- /package/dist/web/assets/{loader-circle-DouKgIXy.js → loader-circle-DPMrnLQ2.js} +0 -0
- /package/dist/web/assets/{log-D28raQiE.js → log-BywQaTCy.js} +0 -0
- /package/dist/web/assets/{logo-6Cdd4ASb.js → logo-BirJgciO.js} +0 -0
- /package/dist/web/assets/{luau-CK9nKpOX.js → luau-Btrz5kJB.js} +0 -0
- /package/dist/web/assets/{make-CVnxKCB6.js → make-Kl7nFQrS.js} +0 -0
- /package/dist/web/assets/{markdown-B9mMQzD5.js → markdown-qsC4P8S-.js} +0 -0
- /package/dist/web/assets/{material-theme-BZSs2o0j.js → material-theme-CC__ui3p.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BeAQBzQ_.js → material-theme-darker-D4FxG2FZ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BAe0IXcs.js → material-theme-lighter-zGqiWcxn.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CiPIf0Np.js → material-theme-ocean-CZIdTTB1.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJBb4_XN.js → material-theme-palenight-CEIs0GcK.js} +0 -0
- /package/dist/web/assets/{matlab-CY0dbp_s.js → matlab-Cf0aUjN9.js} +0 -0
- /package/dist/web/assets/{mdx-DU9zbF7h.js → mdx-CIsXjH86.js} +0 -0
- /package/dist/web/assets/{mermaid-UmmjGwdh.js → mermaid-B06WO9Qc.js} +0 -0
- /package/dist/web/assets/{min-dark-C59GkzOm.js → min-dark-z8fyp0FT.js} +0 -0
- /package/dist/web/assets/{min-light-B9QglrdQ.js → min-light-Dr-qesbW.js} +0 -0
- /package/dist/web/assets/{mipsasm-Dc6Jsr_D.js → mipsasm-CWHzYLcE.js} +0 -0
- /package/dist/web/assets/{mojo-D5ij-nGW.js → mojo-D9zXrKmv.js} +0 -0
- /package/dist/web/assets/{monitor-CxPV_fQI.js → monitor-C835ucWH.js} +0 -0
- /package/dist/web/assets/{monokai-QAtE_yCP.js → monokai-4b-AKOpX.js} +0 -0
- /package/dist/web/assets/{moonbit-BMtIdiTL.js → moonbit-Dl4dhOZJ.js} +0 -0
- /package/dist/web/assets/{move-BB_jMHYZ.js → move-DTYKJmXy.js} +0 -0
- /package/dist/web/assets/{narrat-BvvoKGVn.js → narrat-DnvXOS-o.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CqxtAsnF.js → nextflow-groovy-Di8QnEXg.js} +0 -0
- /package/dist/web/assets/{night-owl-CLoE9kjt.js → night-owl-Czh0yq-H.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BwIFT5Xd.js → night-owl-light-D_OpZqOR.js} +0 -0
- /package/dist/web/assets/{nix-BKlnVT9r.js → nix-2MvcjpK3.js} +0 -0
- /package/dist/web/assets/{nord-B7R1KRAK.js → nord-CG_r4cyk.js} +0 -0
- /package/dist/web/assets/{nushell-CzUme-NJ.js → nushell-DINskMcU.js} +0 -0
- /package/dist/web/assets/{objective-c-D-UKKiOc.js → objective-c-Dv7BIicR.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BucWn4Fx.js → objective-cpp-CpqsCxPb.js} +0 -0
- /package/dist/web/assets/{ocaml-EJCNXMGo.js → ocaml-DwTsYSmW.js} +0 -0
- /package/dist/web/assets/{odin-CEjnRU_3.js → odin-G-4O4k4t.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-Bv469NX5.js → one-dark-pro-13Pv5SjI.js} +0 -0
- /package/dist/web/assets/{one-light-BlzO5RWI.js → one-light-Bi2e86mS.js} +0 -0
- /package/dist/web/assets/{openscad-Czl6alLw.js → openscad-D0WsOTij.js} +0 -0
- /package/dist/web/assets/{pascal-BI9fLvG4.js → pascal-DmDStuXr.js} +0 -0
- /package/dist/web/assets/{pkl-ChQLsyjU.js → pkl-Bl6Qjtuo.js} +0 -0
- /package/dist/web/assets/{plastic-DGL3mtdL.js → plastic-UeSVSjTI.js} +0 -0
- /package/dist/web/assets/{plsql-BJDjqcWp.js → plsql-DreFC689.js} +0 -0
- /package/dist/web/assets/{po-DK5O3OpF.js → po-ZIjccxsX.js} +0 -0
- /package/dist/web/assets/{poimandres-CnuqZT4T.js → poimandres-CvQHJPrI.js} +0 -0
- /package/dist/web/assets/{polar-DCsUxSsQ.js → polar-BrMB0Kgh.js} +0 -0
- /package/dist/web/assets/{portal-container-context-D-495m5C.js → portal-container-context-a399Hd9F.js} +0 -0
- /package/dist/web/assets/{postcss-8UjdFmis.js → postcss-pgUsil1T.js} +0 -0
- /package/dist/web/assets/{powerquery-CAp0CGQj.js → powerquery-CTsZkCg_.js} +0 -0
- /package/dist/web/assets/{powershell-CbBsKZJP.js → powershell-CcwVuBAp.js} +0 -0
- /package/dist/web/assets/{prisma-BnNpXCJN.js → prisma-spjnAI5W.js} +0 -0
- /package/dist/web/assets/{prolog-CC-l9E8Y.js → prolog-ChCkZpDO.js} +0 -0
- /package/dist/web/assets/{proto-CV_1k2rL.js → proto-wVGqK4gB.js} +0 -0
- /package/dist/web/assets/{puppet-2D8HU2bv.js → puppet-CED7iwPQ.js} +0 -0
- /package/dist/web/assets/{purescript-BEJAORgU.js → purescript-u9lGiQKV.js} +0 -0
- /package/dist/web/assets/{python-BBdbb7nn.js → python-DeqTx6ef.js} +0 -0
- /package/dist/web/assets/{qmldir-D5ZYI4Ie.js → qmldir-BH1uoPrX.js} +0 -0
- /package/dist/web/assets/{qss-BOJVGyXE.js → qss-CUVKA_13.js} +0 -0
- /package/dist/web/assets/{r-B5dlEtkj.js → r-BqdPzqYH.js} +0 -0
- /package/dist/web/assets/{racket-BUAEygJ6.js → racket-C2eTgPN3.js} +0 -0
- /package/dist/web/assets/{raku-BG0Ss0ID.js → raku-uAay1sWx.js} +0 -0
- /package/dist/web/assets/{react-B549sK_T.js → react-X4jUntEf.js} +0 -0
- /package/dist/web/assets/{red-C5yyQbtG.js → red-DTZP1ZfC.js} +0 -0
- /package/dist/web/assets/{reg-DHoBWtLf.js → reg-Cdk3YsxY.js} +0 -0
- /package/dist/web/assets/{regexp-DDtJrlKv.js → regexp-Bpa2aS7K.js} +0 -0
- /package/dist/web/assets/{rel-CR6WJDRD.js → rel-bNsb1-3L.js} +0 -0
- /package/dist/web/assets/{resolve-theme-4fexHWl6.js → resolve-theme-DQk_YcsT.js} +0 -0
- /package/dist/web/assets/{riscv-2V9tC-nx.js → riscv-DSaubByW.js} +0 -0
- /package/dist/web/assets/{ron-QLTmqGUw.js → ron-DbQzYM6i.js} +0 -0
- /package/dist/web/assets/{rose-pine-be0__X3A.js → rose-pine-C7RsprTu.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-n_WQJwsW.js → rose-pine-dawn-VP976YzL.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-t2aLtzgB.js → rose-pine-moon-XHZNhOqd.js} +0 -0
- /package/dist/web/assets/{rosmsg-Ce1JD8vt.js → rosmsg-BVQlx0Lj.js} +0 -0
- /package/dist/web/assets/{rust-5fHH-ZoW.js → rust-VHaQ3rh3.js} +0 -0
- /package/dist/web/assets/{sass-DPmGFiOJ.js → sass-CO-kMkwD.js} +0 -0
- /package/dist/web/assets/{save-DYLiVzU8.js → save-CipGrG1k.js} +0 -0
- /package/dist/web/assets/{scala-BRMVFbZF.js → scala-cY0GXkiz.js} +0 -0
- /package/dist/web/assets/{scheme-BMSJWevF.js → scheme-D4LNZE0X.js} +0 -0
- /package/dist/web/assets/{sdbl-DTZ-pEHI.js → sdbl-Bh2Fd2cn.js} +0 -0
- /package/dist/web/assets/{shellscript-6-9yg5Cz.js → shellscript-KKclmCnt.js} +0 -0
- /package/dist/web/assets/{shield-check-CUI36aSc.js → shield-check-BsG3LV8i.js} +0 -0
- /package/dist/web/assets/{shield-off-GdkkVhb3.js → shield-off-7jKWpJBD.js} +0 -0
- /package/dist/web/assets/{slack-dark-DKJgiR7e.js → slack-dark-D6YQ-fqf.js} +0 -0
- /package/dist/web/assets/{slack-ochin-WjehVgyK.js → slack-ochin-CDf66CDj.js} +0 -0
- /package/dist/web/assets/{smalltalk-BChf4SN1.js → smalltalk-D2OH_QIl.js} +0 -0
- /package/dist/web/assets/{snazzy-light-C1WAwOx6.js → snazzy-light-CQeF8G1K.js} +0 -0
- /package/dist/web/assets/{solarized-dark-BxREZrGx.js → solarized-dark-LTPf1iLZ.js} +0 -0
- /package/dist/web/assets/{solarized-light-DG29B1pR.js → solarized-light-Ose1n88k.js} +0 -0
- /package/dist/web/assets/{solidity-bDbtGwdR.js → solidity-lFuOMHki.js} +0 -0
- /package/dist/web/assets/{splunk-Ceqx5njI.js → splunk-Bov-sB5S.js} +0 -0
- /package/dist/web/assets/{sql-OwMEKbz8.js → sql-BKyZ6wmk.js} +0 -0
- /package/dist/web/assets/{ssh-config-WAR3lu2k.js → ssh-config-CqJoMGMb.js} +0 -0
- /package/dist/web/assets/{stylus-Cjmoz3d8.js → stylus-Bf5pWRLI.js} +0 -0
- /package/dist/web/assets/{swift-S_ZSBG1q.js → swift-D5d1hn_u.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DiUeWMSh.js → synthwave-84-B4c6YAgO.js} +0 -0
- /package/dist/web/assets/{system-verilog-RRjqPjUV.js → system-verilog-D7fXyDsI.js} +0 -0
- /package/dist/web/assets/{systemd-BD3SkXoU.js → systemd-oNagzlBK.js} +0 -0
- /package/dist/web/assets/{table-D5Q4ap4W.js → table-CRJsmfeg.js} +0 -0
- /package/dist/web/assets/{talonscript-DWefISOm.js → talonscript-C6Nqm9Dm.js} +0 -0
- /package/dist/web/assets/{tasl-DCjSS-09.js → tasl-CaWy-cBX.js} +0 -0
- /package/dist/web/assets/{tcl-DTYqT_66.js → tcl-C435P5Mq.js} +0 -0
- /package/dist/web/assets/{terraform-CkbP5QnR.js → terraform-CVDxqdPf.js} +0 -0
- /package/dist/web/assets/{text-wrap-DBvQD3kc.js → text-wrap-oiekm5SB.js} +0 -0
- /package/dist/web/assets/{tokyo-night-qVSqO9xy.js → tokyo-night-Uhs17tQ3.js} +0 -0
- /package/dist/web/assets/{toml-D9F5Osqs.js → toml-j6fPOJ6P.js} +0 -0
- /package/dist/web/assets/{tsv-C5tpHoxm.js → tsv-h78O6xrL.js} +0 -0
- /package/dist/web/assets/{tsx-CMWkGtGm.js → tsx-Bol-vCrQ.js} +0 -0
- /package/dist/web/assets/{turtle-Cn4z3xOi.js → turtle-Dw8eFf04.js} +0 -0
- /package/dist/web/assets/{typescript-DSyAEWsn.js → typescript-Dtz7DPci.js} +0 -0
- /package/dist/web/assets/{typespec-B93Qap7y.js → typespec-BIOQ7dMP.js} +0 -0
- /package/dist/web/assets/{typst-DtlOmFlX.js → typst-CoaGMjzu.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-B9UZNwSm.js → use-is-mobile-CYW3FJFE.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-Sfb6CZ80.js → use-visual-viewport-rnvuooQZ.js} +0 -0
- /package/dist/web/assets/{utils-QHe_Q6UF.js → utils-C9OjCrou.js} +0 -0
- /package/dist/web/assets/{v-Dfzf-szS.js → v-BoqFdpJ7.js} +0 -0
- /package/dist/web/assets/{vala-CyiQZQ9f.js → vala-COBpNnCC.js} +0 -0
- /package/dist/web/assets/{vb-C7DGSldd.js → vb-DxI86znh.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BHKF_Nsf.js → vendor-xterm-BaMOj65g.js} +0 -0
- /package/dist/web/assets/{verilog-kF-hClY2.js → verilog-DcTAamuQ.js} +0 -0
- /package/dist/web/assets/{vesper-BmKcADuz.js → vesper-DO298JSv.js} +0 -0
- /package/dist/web/assets/{vhdl-CDnb16KJ.js → vhdl-Dq-bDVSN.js} +0 -0
- /package/dist/web/assets/{viml-B8cs6Ady.js → viml-zHTRWuQh.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BR6o9Gto.js → vitesse-black-jlYMJCBf.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dye14-KB.js → vitesse-dark-DS0sUlSZ.js} +0 -0
- /package/dist/web/assets/{vitesse-light-BLpswIxf.js → vitesse-light-HSeZ2YhG.js} +0 -0
- /package/dist/web/assets/{vyper-DU51Z97W.js → vyper-BQfKVO3M.js} +0 -0
- /package/dist/web/assets/{wasm-ChtccNfv.js → wasm-B1xp9bLp.js} +0 -0
- /package/dist/web/assets/{wasm-CVqlJAR2.js → wasm-B6mCuI8h.js} +0 -0
- /package/dist/web/assets/{wenyan-DhCa3hCn.js → wenyan-CfpS05sP.js} +0 -0
- /package/dist/web/assets/{wgsl-Bj1Ec4vv.js → wgsl-14GDsWRD.js} +0 -0
- /package/dist/web/assets/{wikitext-C90DHNa0.js → wikitext-Ccbq5EVX.js} +0 -0
- /package/dist/web/assets/{wit-DUgbOD7Z.js → wit-C19hb3R5.js} +0 -0
- /package/dist/web/assets/{wolfram-BgwUqD1I.js → wolfram-B-ZSwGwP.js} +0 -0
- /package/dist/web/assets/{x-DiOUR_mp.js → x-YDu4m2Iq.js} +0 -0
- /package/dist/web/assets/{yaml-DEQulj-8.js → yaml-mw_JUeio.js} +0 -0
- /package/dist/web/assets/{zenscript-C7Jp7TgT.js → zenscript-WbzDRjvF.js} +0 -0
- /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
|
-
}
|