@hienlh/ppm 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -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-LtSVxNNp.js +1 -0
- package/dist/web/assets/ai-resource-editor-BqFT1EUB.js +1 -0
- package/dist/web/assets/ai-settings-section-CY6spbEx.js +1 -0
- package/dist/web/assets/ai-settings-section-Po3K8j-g.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-L9Z-nej_.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-BIO-VhWT.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-B9lVjElM.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-BoRPwL3L.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-BpoJqZQi.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-I-9cER1L.js +1 -0
- package/dist/web/assets/diff-viewer-_xHZsxvr.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-DapwsOnp.js +2 -0
- package/dist/web/assets/explorer-window-content-DD6Z15pm.js +1 -0
- package/dist/web/assets/extension-manager-section-Cu9D9Udt.js +1 -0
- package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-Diz2OzVq.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-B1e7BgKQ.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-DWdHHRDy.js +1 -0
- package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-BOpxSbns.js} +1 -1
- package/dist/web/assets/git-log-panel-CMMsvU64.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-pKQuyYFj.js} +8 -8
- 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-U5u0Loii.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--KWVW-Ga.js +52 -0
- package/dist/web/assets/index-CvGx7wBQ.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-uA5oyB8s.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-Ccf0etMJ.js +1 -0
- package/dist/web/assets/keyboard-shortcuts-section-B7IxJHiI.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-CY1epGRc.js +1 -0
- package/dist/web/assets/{markdown-renderer-dbLIvU2M.js → markdown-renderer-DDcIGQEd.js} +3 -3
- 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-CqkwbCRl.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-BNSYk3E7.js +1 -0
- package/dist/web/assets/notifications-settings-section-v_628h4B.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-CI3q1MnH.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-jaR0FYJ_.js} +3 -3
- package/dist/web/assets/power-CriQkeK5.js +1 -0
- package/dist/web/assets/ppmbot-settings-section-BET4VmKb.js +1 -0
- package/dist/web/assets/{process-table-kmQ2-8qJ.js → process-table-D1r-Gupi.js} +1 -1
- package/dist/web/assets/{project-store-DTUCS65m.js → project-store-BuY7rxn_.js} +1 -1
- package/dist/web/assets/proxy-settings-section-0DvNh1B-.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-BA5rT6Sh.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-CPa3XElX.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-De_a4skd.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CPrDIv2d.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-CG1StfCE.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-B2f7V7Xr.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-omv04jbb.js +2 -0
- package/dist/web/assets/settings-store-Brjd0QnO.js +2 -0
- package/dist/web/assets/settings-tab-NdnBTqyj.js +1 -0
- package/dist/web/assets/settings-window-content-Chou1JGa.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-Dk9y3jqy.js +1 -0
- package/dist/web/assets/sqlite-viewer-4xFhkLYy.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-B_1iMVOI.js +2 -0
- package/dist/web/assets/system-monitor-tab-B2j9mWBW.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-B9Y98UW6.js → system-monitor-window-content-BOjq53uU.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-C1j8zr68.js → tab-host-window-content-CW_dlYlW.js} +1 -1
- package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-PZsP5ZwG.js} +1 -1
- package/dist/web/assets/team-member-sheet-CkR4qO7V.js +2 -0
- package/dist/web/assets/team-member-window-content-CmF5ia3n.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-HXZkicIc.js} +3 -3
- package/dist/web/assets/{tex--c4POEUE.js → tex-BTIqPw_R.js} +1 -1
- package/dist/web/assets/tool-cards-jA1BTLZj.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-BI_5qNvr.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-BcQqKVbE.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-DY5BWRTo.js +1 -0
- package/dist/web/assets/{use-websocket-xOOwBdwP.js → use-websocket-DetaY8OI.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-0Nt-ew76.js → vendor-mermaid-BKWscKt0.js} +1 -1
- package/dist/web/assets/video-preview-C0uH2228.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/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 +43 -447
- 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 +127 -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 +93 -0
- package/src/web/components/settings/accounts/accounts-settings-section.tsx +80 -0
- package/src/web/components/settings/accounts/claude-accounts-section.tsx +168 -0
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +137 -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
|
@@ -2,17 +2,19 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import {
|
|
5
|
-
insertLimitSnapshot,
|
|
6
5
|
getLatestLimitSnapshot,
|
|
7
6
|
getLatestSnapshotForAccount,
|
|
8
7
|
getAllLatestSnapshots,
|
|
9
|
-
cleanupOldLimitSnapshots,
|
|
10
|
-
touchSnapshotTimestamp,
|
|
11
8
|
type LimitSnapshotRow,
|
|
12
9
|
} from "./db.service.ts";
|
|
13
10
|
import { accountService } from "./account.service.ts";
|
|
14
11
|
import { decrypt } from "../lib/account-crypto.ts";
|
|
15
12
|
import { accountSelector } from "./account-selector.service.ts";
|
|
13
|
+
import { registerAllUsageSources } from "./provider-usage/register-usage-sources.ts";
|
|
14
|
+
import { startProviderUsagePolling, stopProviderUsagePolling, sweepUsageSource } from "./provider-usage/usage-scheduler.ts";
|
|
15
|
+
import { refreshUsage as refreshProviderUsage } from "./provider-usage/usage-registry.ts";
|
|
16
|
+
import { claudeUsageSource } from "./claude-usage-source.ts";
|
|
17
|
+
import { resetUsageRuntimeState } from "./provider-usage/index.ts";
|
|
16
18
|
|
|
17
19
|
export interface LimitBucket {
|
|
18
20
|
utilization: number;
|
|
@@ -43,20 +45,8 @@ const API_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
|
43
45
|
const API_BETA = "oauth-2025-04-20";
|
|
44
46
|
const USER_AGENT = "claude-code/1.0";
|
|
45
47
|
const FETCH_TIMEOUT = 10_000;
|
|
46
|
-
const POLL_INTERVAL = 300_000; // 5min
|
|
47
|
-
const ACCOUNT_STAGGER_MS = 1_000; // 1s between accounts
|
|
48
|
-
|
|
49
48
|
let inMemoryCostUsd = 0;
|
|
50
49
|
|
|
51
|
-
// Survive Bun --hot reloads: module-level vars reset on reload, globalThis persists.
|
|
52
|
-
// Without this, each hot-reload creates a NEW polling timer without clearing the old one,
|
|
53
|
-
// leading to N concurrent timers after N reloads (observed: 221 timers → 38k 429 errors/day).
|
|
54
|
-
const HOT_KEY = "__PPM_USAGE_POLL__" as const;
|
|
55
|
-
const hotState = ((globalThis as any)[HOT_KEY] ??= {
|
|
56
|
-
pollTimer: null as ReturnType<typeof setTimeout> | null,
|
|
57
|
-
inflightPoll: null as Promise<void> | null,
|
|
58
|
-
}) as { pollTimer: ReturnType<typeof setTimeout> | null; inflightPoll: Promise<void> | null };
|
|
59
|
-
|
|
60
50
|
// Per-token cooldown map: token prefix → earliest allowed fetch time
|
|
61
51
|
const tokenCooldowns = new Map<string, number>();
|
|
62
52
|
const MIN_COOLDOWN_MS = 60_000; // floor: at least 60s cooldown on 429
|
|
@@ -141,73 +131,61 @@ async function fetchUsageForToken(token: string): Promise<ClaudeUsage> {
|
|
|
141
131
|
// Persistence
|
|
142
132
|
// ---------------------------------------------------------------------------
|
|
143
133
|
|
|
144
|
-
function hasChanged(data: ClaudeUsage, last: LimitSnapshotRow | null): boolean {
|
|
145
|
-
if (!last) return true;
|
|
146
|
-
const d = (a: number | null | undefined, b: number | null) =>
|
|
147
|
-
a != null && (b == null || Math.abs(a - b) > 0.001);
|
|
148
|
-
if (d(data.session?.utilization, last.five_hour_util)) return true;
|
|
149
|
-
if (d(data.weekly?.utilization, last.weekly_util)) return true;
|
|
150
|
-
if (data.session?.resetsAt && data.session.resetsAt !== (last.five_hour_resets_at ?? "")) return true;
|
|
151
|
-
if (data.weekly?.resetsAt && data.weekly.resetsAt !== (last.weekly_resets_at ?? "")) return true;
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function persistIfChanged(data: ClaudeUsage, accountId: string | null): void {
|
|
156
|
-
const last = accountId ? getLatestSnapshotForAccount(accountId) : getLatestLimitSnapshot();
|
|
157
|
-
if (!hasChanged(data, last)) {
|
|
158
|
-
// Data unchanged but still update timestamp so "last fetched" is accurate
|
|
159
|
-
if (accountId) touchSnapshotTimestamp(accountId);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
insertLimitSnapshot({
|
|
163
|
-
account_id: accountId,
|
|
164
|
-
five_hour_util: data.session?.utilization ?? null,
|
|
165
|
-
five_hour_resets_at: data.session?.resetsAt ?? null,
|
|
166
|
-
weekly_util: data.weekly?.utilization ?? null,
|
|
167
|
-
weekly_resets_at: data.weekly?.resetsAt ?? null,
|
|
168
|
-
weekly_opus_util: data.weeklyOpus?.utilization ?? null,
|
|
169
|
-
weekly_opus_resets_at: data.weeklyOpus?.resetsAt ?? null,
|
|
170
|
-
weekly_sonnet_util: data.weeklySonnet?.utilization ?? null,
|
|
171
|
-
weekly_sonnet_resets_at: data.weeklySonnet?.resetsAt ?? null,
|
|
172
|
-
});
|
|
173
|
-
cleanupOldLimitSnapshots();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
134
|
// ---------------------------------------------------------------------------
|
|
177
135
|
// Multi-account polling
|
|
178
136
|
// ---------------------------------------------------------------------------
|
|
179
137
|
|
|
180
|
-
|
|
181
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Whether a background sweep should pass over this account without calling it.
|
|
140
|
+
*
|
|
141
|
+
* Not a failure: an expired login, an API key (no usage endpoint), or a token
|
|
142
|
+
* still inside its post-429 cooldown all mean "nothing to ask right now", and
|
|
143
|
+
* the last stored reading stays valid.
|
|
144
|
+
*/
|
|
145
|
+
export async function shouldSkipClaudeAccount(accountId: string): Promise<boolean> {
|
|
146
|
+
const acc = accountService.list().find((a) => a.id === accountId);
|
|
147
|
+
if (!acc) return true;
|
|
182
148
|
const nowS = Math.floor(Date.now() / 1000);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const cooldownKey = token.substring(0, 20);
|
|
196
|
-
const cooldownUntil = tokenCooldowns.get(cooldownKey);
|
|
197
|
-
if (cooldownUntil && Date.now() < cooldownUntil) {
|
|
198
|
-
const secs = Math.ceil((cooldownUntil - Date.now()) / 1000);
|
|
199
|
-
console.log(`[usage] ${acc.label ?? acc.id}: rate-limited, ${secs}s remaining`);
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const data = await fetchUsageForToken(token);
|
|
204
|
-
tokenCooldowns.delete(cooldownKey); // clear cooldown on success
|
|
205
|
-
persistIfChanged(data, acc.id);
|
|
206
|
-
} catch (e) {
|
|
207
|
-
console.error(`[usage] ${acc.label ?? acc.id}:`, (e as Error).message);
|
|
208
|
-
}
|
|
209
|
-
if (accounts.length > 1) await new Promise(r => setTimeout(r, ACCOUNT_STAGGER_MS));
|
|
149
|
+
// Disabled accounts still poll usage — disable only removes them from the chat
|
|
150
|
+
// rotation, it should not stop usage tracking. (GET usage doesn't consume quota.)
|
|
151
|
+
// Skip expired temporary accounts (no refresh token).
|
|
152
|
+
if (!accountService.hasRefreshToken(acc.id) && acc.expiresAt && acc.expiresAt < nowS) return true;
|
|
153
|
+
const withTokens = await accountService.ensureFreshToken(acc.id);
|
|
154
|
+
if (!withTokens) return true;
|
|
155
|
+
// Only OAuth tokens have a usage endpoint.
|
|
156
|
+
if (!withTokens.accessToken.startsWith("sk-ant-oat")) return true;
|
|
157
|
+
const cooldownUntil = tokenCooldowns.get(withTokens.accessToken.substring(0, 20));
|
|
158
|
+
if (cooldownUntil && Date.now() < cooldownUntil) {
|
|
159
|
+
console.log(`[usage] ${acc.label ?? acc.id}: rate-limited, ${Math.ceil((cooldownUntil - Date.now()) / 1000)}s remaining`);
|
|
160
|
+
return true;
|
|
210
161
|
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Read one Claude account's usage live. Throws on failure — the shared layer
|
|
167
|
+
* needs to tell a failure apart from an account with nothing to report.
|
|
168
|
+
*
|
|
169
|
+
* Persisting is deliberately NOT done here: that belongs to the shared layer,
|
|
170
|
+
* which stores every provider's snapshots through one path.
|
|
171
|
+
*/
|
|
172
|
+
export async function fetchClaudeAccountUsage(accountId: string): Promise<ClaudeUsage> {
|
|
173
|
+
const withTokens = await accountService.ensureFreshToken(accountId);
|
|
174
|
+
if (!withTokens) throw new Error(`account ${accountId} has no usable token`);
|
|
175
|
+
const token = withTokens.accessToken;
|
|
176
|
+
const data = await fetchUsageForToken(token);
|
|
177
|
+
tokenCooldowns.delete(token.substring(0, 20)); // cleared on success
|
|
178
|
+
return data;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Read the ambient Claude login (macOS Keychain or `~/.claude/.credentials.json`),
|
|
183
|
+
* for installs that never added an account to PPM.
|
|
184
|
+
*/
|
|
185
|
+
export async function fetchLegacyClaudeUsage(): Promise<ClaudeUsage> {
|
|
186
|
+
const token = getLegacyAccessToken();
|
|
187
|
+
if (!token) throw new Error("no ambient Claude login");
|
|
188
|
+
return fetchUsageForToken(token);
|
|
211
189
|
}
|
|
212
190
|
|
|
213
191
|
// Legacy: Keychain-based single-token fetch (no accounts in DB)
|
|
@@ -230,40 +208,6 @@ function getLegacyAccessToken(): string | null {
|
|
|
230
208
|
return token;
|
|
231
209
|
}
|
|
232
210
|
|
|
233
|
-
async function fetchLegacySingleAccount(): Promise<void> {
|
|
234
|
-
const token = getLegacyAccessToken();
|
|
235
|
-
if (!token) return;
|
|
236
|
-
try {
|
|
237
|
-
const data = await fetchUsageForToken(token);
|
|
238
|
-
persistIfChanged(data, null);
|
|
239
|
-
} catch {}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function pollOnceInternal(): Promise<void> {
|
|
243
|
-
try {
|
|
244
|
-
const hasAccounts = accountService.list().length > 0;
|
|
245
|
-
if (hasAccounts) {
|
|
246
|
-
await fetchAllAccountUsages();
|
|
247
|
-
} else {
|
|
248
|
-
await fetchLegacySingleAccount();
|
|
249
|
-
}
|
|
250
|
-
} catch (e) {
|
|
251
|
-
console.error("[usage] pollOnce error:", (e as Error).message);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Deduped: concurrent callers share a single in-flight fetch */
|
|
256
|
-
async function pollOnce(): Promise<void> {
|
|
257
|
-
if (hotState.inflightPoll) return hotState.inflightPoll;
|
|
258
|
-
const thisPoll = pollOnceInternal().finally(() => {
|
|
259
|
-
// Only clear if still the current poll — prevents a stale .finally() from
|
|
260
|
-
// clearing a newer poll after timeout handler force-nulled inflightPoll.
|
|
261
|
-
if (hotState.inflightPoll === thisPoll) hotState.inflightPoll = null;
|
|
262
|
-
});
|
|
263
|
-
hotState.inflightPoll = thisPoll;
|
|
264
|
-
return thisPoll;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
211
|
// ---------------------------------------------------------------------------
|
|
268
212
|
// Public API
|
|
269
213
|
// ---------------------------------------------------------------------------
|
|
@@ -323,27 +267,21 @@ export function getCachedUsage(preferredAccountId?: string): ClaudeUsage & { act
|
|
|
323
267
|
return snapshotToUsage(row);
|
|
324
268
|
}
|
|
325
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Start background usage refresh for EVERY provider, not only Claude.
|
|
272
|
+
*
|
|
273
|
+
* Kept under its historical name because that is what `server/index.ts` calls,
|
|
274
|
+
* but the timer, the stagger, the per-fetch timeout, and the persistence now
|
|
275
|
+
* live in the shared provider-usage layer. Claude reaches them through
|
|
276
|
+
* `claudeUsageSource` exactly as codex reaches them through its own source.
|
|
277
|
+
*/
|
|
326
278
|
export function startUsagePolling(): void {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const scheduleNext = () => {
|
|
330
|
-
hotState.pollTimer = setTimeout(async () => {
|
|
331
|
-
const timeout = new Promise<"timeout">(r => setTimeout(() => r("timeout"), POLL_TIMEOUT));
|
|
332
|
-
const result = await Promise.race([
|
|
333
|
-
pollOnce().then(() => "done" as const),
|
|
334
|
-
timeout,
|
|
335
|
-
]).catch(() => "error" as const);
|
|
336
|
-
// If the poll timed out, force-clear inflightPoll so next scheduled poll
|
|
337
|
-
// starts a fresh fetch instead of reusing the stale hanging promise.
|
|
338
|
-
if (result === "timeout") hotState.inflightPoll = null;
|
|
339
|
-
scheduleNext();
|
|
340
|
-
}, POLL_INTERVAL);
|
|
341
|
-
};
|
|
342
|
-
pollOnce().then(scheduleNext, scheduleNext);
|
|
279
|
+
registerAllUsageSources();
|
|
280
|
+
startProviderUsagePolling();
|
|
343
281
|
}
|
|
344
282
|
|
|
345
283
|
export function stopUsagePolling(): void {
|
|
346
|
-
|
|
284
|
+
stopProviderUsagePolling();
|
|
347
285
|
}
|
|
348
286
|
|
|
349
287
|
export function updateFromSdkEvent(_rateLimitType?: string, _utilization?: number, costUsd?: number): void {
|
|
@@ -351,35 +289,28 @@ export function updateFromSdkEvent(_rateLimitType?: string, _utilization?: numbe
|
|
|
351
289
|
}
|
|
352
290
|
|
|
353
291
|
export async function refreshUsageNow(): Promise<ClaudeUsage & { activeAccountId?: string; activeAccountLabel?: string }> {
|
|
354
|
-
|
|
292
|
+
// One sweep of Claude's accounts through the shared layer — same code path the
|
|
293
|
+
// background timer uses, so a manual refresh cannot drift from an automatic one.
|
|
294
|
+
registerAllUsageSources();
|
|
295
|
+
await sweepUsageSource(claudeUsageSource);
|
|
355
296
|
return getCachedUsage();
|
|
356
297
|
}
|
|
357
298
|
|
|
358
299
|
/** Fetch + persist usage for a single account (used right after an account is added). */
|
|
359
300
|
export async function refreshUsageForAccount(accountId: string): Promise<ClaudeUsage> {
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const cooldownUntil = tokenCooldowns.get(cooldownKey);
|
|
366
|
-
if (cooldownUntil && Date.now() < cooldownUntil) return getUsageForAccount(accountId);
|
|
367
|
-
try {
|
|
368
|
-
const data = await fetchUsageForToken(token);
|
|
369
|
-
tokenCooldowns.delete(cooldownKey);
|
|
370
|
-
persistIfChanged(data, accountId);
|
|
371
|
-
return data;
|
|
372
|
-
} catch (e) {
|
|
373
|
-
console.error(`[usage] refreshUsageForAccount ${accountId}:`, (e as Error).message);
|
|
374
|
-
return getUsageForAccount(accountId);
|
|
375
|
-
}
|
|
301
|
+
registerAllUsageSources();
|
|
302
|
+
if (await shouldSkipClaudeAccount(accountId)) return getUsageForAccount(accountId);
|
|
303
|
+
// The shared layer caches, stores, and swallows the error into the last known
|
|
304
|
+
// value, so there is nothing left for this wrapper to do but name the account.
|
|
305
|
+
return refreshProviderUsage("claude", accountId);
|
|
376
306
|
}
|
|
377
307
|
|
|
378
308
|
/** @internal Test-only: reset module-level state between tests */
|
|
379
309
|
export function _resetForTesting(): void {
|
|
380
310
|
inMemoryCostUsd = 0;
|
|
381
|
-
if (hotState.pollTimer) { clearTimeout(hotState.pollTimer); hotState.pollTimer = null; }
|
|
382
311
|
tokenCooldowns.clear();
|
|
383
|
-
hotState.inflightPoll = null;
|
|
384
312
|
tokenCache = null;
|
|
313
|
+
// The timer, in-flight sweeps, and the usage cache moved to the shared layer,
|
|
314
|
+
// so the reset has to reach them there or a stuck sweep survives the reset.
|
|
315
|
+
resetUsageRuntimeState();
|
|
385
316
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codex account login orchestration
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Codex account login orchestration. Logs codex into a per-account CODEX_HOME
|
|
3
|
+
* via a short-lived app-server, verifies with account/read, then persists the
|
|
4
|
+
* account. Two headless paths:
|
|
5
5
|
* - apiKey: instant (account/login/start stores the key; validated on first use).
|
|
6
|
-
* - chatgptDeviceCode: returns a user-code + URL;
|
|
7
|
-
* account/login/completed notification
|
|
6
|
+
* - chatgptDeviceCode: returns a user-code + URL; the app-server's
|
|
7
|
+
* account/login/completed notification finalizes the account in the
|
|
8
|
+
* background and the browser short-polls getDeviceLoginStatus for it.
|
|
9
|
+
*
|
|
10
|
+
* Device-code completion deliberately does NOT ride on the HTTP request that
|
|
11
|
+
* started it. Holding one request open for the whole authorization means any
|
|
12
|
+
* dropped socket (a proxy or tunnel in front of PPM) destroys the only path the
|
|
13
|
+
* result had — the login still succeeds server-side but the browser is told it
|
|
14
|
+
* failed, and a retry cannot recover the flow.
|
|
8
15
|
*/
|
|
9
16
|
import { randomUUID } from "node:crypto";
|
|
10
17
|
import { mkdirSync, rmSync } from "node:fs";
|
|
@@ -16,7 +23,18 @@ const CAPABILITIES = { experimentalApi: true, requestAttestation: false, optOutN
|
|
|
16
23
|
|
|
17
24
|
interface AccountRead { account: { type: string; email?: string; planType?: string | null } | null }
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
/** The slice of the app-server client the login flow uses. Narrowing it here lets
|
|
27
|
+
* a unit test drive the whole flow without spawning a real subprocess. */
|
|
28
|
+
export interface LoginClient {
|
|
29
|
+
start(opts?: { cwd?: string; codexHome?: string }): void;
|
|
30
|
+
onNotification(fn: (n: { method: string; params?: unknown }) => void): void;
|
|
31
|
+
onClose(fn: (code: number | null) => void): void;
|
|
32
|
+
request<T = unknown>(method: string, params?: unknown): Promise<T>;
|
|
33
|
+
notify(method: string, params?: unknown): void;
|
|
34
|
+
close(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function handshake(c: LoginClient): Promise<void> {
|
|
20
38
|
await c.request("initialize", { clientInfo: CLIENT_INFO, capabilities: CAPABILITIES });
|
|
21
39
|
c.notify("initialized");
|
|
22
40
|
}
|
|
@@ -45,76 +63,126 @@ export async function addApiKeyAccount(apiKey: string, label?: string): Promise<
|
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
// ── ChatGPT device-code login ──
|
|
67
|
+
|
|
68
|
+
/** Terminal states stay readable for a grace window, so a poll that loses its
|
|
69
|
+
* response can simply ask again instead of losing the outcome. */
|
|
70
|
+
export type DeviceLoginStatus =
|
|
71
|
+
| { state: "pending" }
|
|
72
|
+
| { state: "done"; account: CodexAccount }
|
|
73
|
+
| { state: "error"; error: string };
|
|
74
|
+
|
|
75
|
+
interface DeviceLogin {
|
|
76
|
+
client: LoginClient; home: string; label?: string;
|
|
77
|
+
status: DeviceLoginStatus;
|
|
51
78
|
timer: ReturnType<typeof setTimeout>;
|
|
79
|
+
/** True from the completion notification until the account is written. The
|
|
80
|
+
* home must survive this window even if the browser walks away. */
|
|
81
|
+
finalizing: boolean;
|
|
52
82
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
83
|
+
const deviceLogins = new Map<string, DeviceLogin>();
|
|
84
|
+
|
|
85
|
+
/** How long an unfinished authorization may sit before it is reaped. */
|
|
86
|
+
const DEVICE_LOGIN_TTL = 200_000;
|
|
87
|
+
/** How long a finished result stays readable for the polling browser. */
|
|
88
|
+
const RESULT_GRACE_MS = 120_000;
|
|
55
89
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
90
|
+
function unref(t: ReturnType<typeof setTimeout>): ReturnType<typeof setTimeout> {
|
|
91
|
+
(t as { unref?: () => void }).unref?.();
|
|
92
|
+
return t;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Forget a flow entirely: stop its app-server, optionally drop its home. */
|
|
96
|
+
function disposeDeviceLogin(id: string, rmHome: boolean): void {
|
|
97
|
+
const p = deviceLogins.get(id);
|
|
59
98
|
if (!p) return;
|
|
60
|
-
|
|
99
|
+
deviceLogins.delete(id);
|
|
61
100
|
clearTimeout(p.timer);
|
|
62
101
|
try { p.client.close(); } catch { /* ignore */ }
|
|
63
102
|
if (rmHome) { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
|
|
64
103
|
}
|
|
65
104
|
|
|
66
|
-
/**
|
|
67
|
-
|
|
105
|
+
/** Record the outcome and stop the app-server. First settle wins, so the close
|
|
106
|
+
* event that trails a success cannot overwrite it. The entry lingers for
|
|
107
|
+
* RESULT_GRACE_MS — that window is what makes a lost poll response harmless. */
|
|
108
|
+
function settle(id: string, status: Exclude<DeviceLoginStatus, { state: "pending" }>): void {
|
|
109
|
+
const p = deviceLogins.get(id);
|
|
110
|
+
if (!p || p.status.state !== "pending") return;
|
|
111
|
+
p.status = status;
|
|
112
|
+
clearTimeout(p.timer);
|
|
113
|
+
try { p.client.close(); } catch { /* ignore */ }
|
|
114
|
+
// A failure leaves no account owning the dir, so the half-built home goes now.
|
|
115
|
+
// A success must keep it: createCodexAccount adopted it as the account's home.
|
|
116
|
+
if (status.state === "error") { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
|
|
117
|
+
p.timer = unref(setTimeout(() => { deviceLogins.delete(id); }, RESULT_GRACE_MS));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Turn the app-server's completion notification into a persisted account. */
|
|
121
|
+
async function finalizeDeviceLogin(id: string, notif: { success?: boolean; error?: string | null }): Promise<void> {
|
|
122
|
+
const p = deviceLogins.get(id);
|
|
123
|
+
if (!p || p.status.state !== "pending") return;
|
|
124
|
+
if (!notif?.success) { settle(id, { state: "error", error: notif?.error || "login not completed" }); return; }
|
|
125
|
+
p.finalizing = true;
|
|
126
|
+
try {
|
|
127
|
+
const read = await p.client.request<AccountRead>("account/read", {});
|
|
128
|
+
if (!read?.account) throw new Error("login completed but account is empty");
|
|
129
|
+
const account = createCodexAccount({
|
|
130
|
+
id, label: p.label || read.account.email || "ChatGPT", type: "chatgpt",
|
|
131
|
+
planType: read.account.planType ?? null,
|
|
132
|
+
});
|
|
133
|
+
settle(id, { state: "done", account });
|
|
134
|
+
} catch (e) {
|
|
135
|
+
settle(id, { state: "error", error: (e as Error).message });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Begin a ChatGPT device-code login. Returns the code/URL the user enters in a
|
|
140
|
+
* browser; the caller then polls getDeviceLoginStatus. */
|
|
141
|
+
export async function startDeviceLogin(
|
|
142
|
+
label?: string,
|
|
143
|
+
makeClient: () => LoginClient = () => new CodexJsonRpcClient(),
|
|
144
|
+
): Promise<{ id: string; userCode: string; verificationUrl: string }> {
|
|
68
145
|
const id = randomUUID();
|
|
69
146
|
const home = codexAccountHome(id);
|
|
70
147
|
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
71
|
-
const client =
|
|
72
|
-
let resolveDone!: (v: { success?: boolean; error?: string | null }) => void;
|
|
73
|
-
const completed = new Promise<{ success?: boolean; error?: string | null }>((r) => { resolveDone = r; });
|
|
148
|
+
const client = makeClient();
|
|
74
149
|
client.onNotification((n) => {
|
|
75
|
-
if (n.method === "account/login/completed")
|
|
150
|
+
if (n.method === "account/login/completed") {
|
|
151
|
+
void finalizeDeviceLogin(id, (n.params ?? {}) as { success?: boolean; error?: string | null });
|
|
152
|
+
}
|
|
76
153
|
});
|
|
77
|
-
client.onClose(() =>
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
const timer = setTimeout(() =>
|
|
81
|
-
|
|
154
|
+
client.onClose(() => settle(id, { state: "error", error: "login process exited" }));
|
|
155
|
+
// Never cleared by a poll: an abandoned browser cannot leak the subprocess or
|
|
156
|
+
// the half-built home the way a request-scoped timer could.
|
|
157
|
+
const timer = unref(setTimeout(() => settle(id, { state: "error", error: "timed out" }), DEVICE_LOGIN_TTL));
|
|
158
|
+
// Registered before the first I/O so a notification (or an immediate exit)
|
|
159
|
+
// arriving mid-handshake still finds its entry.
|
|
160
|
+
deviceLogins.set(id, { client, home, label, status: { state: "pending" }, timer, finalizing: false });
|
|
82
161
|
try {
|
|
83
162
|
client.start({ codexHome: home });
|
|
84
163
|
await handshake(client);
|
|
85
|
-
const start = await client.request<{ userCode?: string; verificationUrl?: string }>(
|
|
86
|
-
|
|
164
|
+
const start = await client.request<{ userCode?: string; verificationUrl?: string }>(
|
|
165
|
+
"account/login/start", { type: "chatgptDeviceCode" },
|
|
166
|
+
);
|
|
87
167
|
return { id, userCode: start?.userCode ?? "", verificationUrl: start?.verificationUrl ?? "" };
|
|
88
168
|
} catch (e) {
|
|
89
|
-
|
|
90
|
-
try { client.close(); } catch { /* ignore */ }
|
|
91
|
-
try { rmSync(home, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
169
|
+
disposeDeviceLogin(id, true);
|
|
92
170
|
throw e;
|
|
93
171
|
}
|
|
94
172
|
}
|
|
95
173
|
|
|
96
|
-
/**
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Success: keep the home (it now holds auth.json) — createCodexAccount reuses it.
|
|
111
|
-
return createCodexAccount({
|
|
112
|
-
id, label: p.label || read.account.email || "ChatGPT", type: "chatgpt", planType: read.account.planType ?? null,
|
|
113
|
-
});
|
|
114
|
-
} catch (e) {
|
|
115
|
-
try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
116
|
-
throw e;
|
|
117
|
-
} finally {
|
|
118
|
-
try { p.client.close(); } catch { /* ignore */ }
|
|
119
|
-
}
|
|
174
|
+
/** Read a device-code flow's outcome. Idempotent — repeat polls are free and a
|
|
175
|
+
* dropped response costs one tick, not the flow. */
|
|
176
|
+
export function getDeviceLoginStatus(id: string): DeviceLoginStatus {
|
|
177
|
+
return deviceLogins.get(id)?.status ?? { state: "error", error: "no pending device login for that id" };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Abandon a flow the user cancelled. A login already being written is left
|
|
181
|
+
* alone — tearing it down here would delete the home mid-write and lose an
|
|
182
|
+
* authorization the user completed. A finished one keeps its home: an account
|
|
183
|
+
* owns it now. */
|
|
184
|
+
export function cancelDeviceLogin(id: string): void {
|
|
185
|
+
const p = deviceLogins.get(id);
|
|
186
|
+
if (!p || p.finalizing) return;
|
|
187
|
+
disposeDeviceLogin(id, p.status.state !== "done");
|
|
120
188
|
}
|
|
@@ -14,7 +14,7 @@ import { getDb, getSessionCodexAccount } from "./db.service.ts";
|
|
|
14
14
|
import { getPpmDir } from "./ppm-dir.ts";
|
|
15
15
|
import { configService } from "./config.service.ts";
|
|
16
16
|
import { encrypt, decrypt } from "../lib/account-crypto.ts";
|
|
17
|
-
import {
|
|
17
|
+
import { getOrFetchUsage } from "./provider-usage/usage-registry.ts";
|
|
18
18
|
import type { UsageInfo } from "../providers/provider.interface.ts";
|
|
19
19
|
|
|
20
20
|
export type CodexStrategy = "round-robin" | "fill-first" | "lowest-usage";
|
|
@@ -145,6 +145,27 @@ export function selectCodexAccount(opts?: { strategy?: CodexStrategy; usageOf?:
|
|
|
145
145
|
return pick;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* The account that WILL serve a session with no binding yet, when that is
|
|
150
|
+
* knowable without side effects — otherwise null.
|
|
151
|
+
*
|
|
152
|
+
* Exists so the chat toolbar can name the account before the first message
|
|
153
|
+
* instead of showing a blank where the account belongs. It deliberately does
|
|
154
|
+
* not call `selectCodexAccount`: round-robin advances a cursor, so asking it
|
|
155
|
+
* merely to draw a label would change which account the next real turn gets.
|
|
156
|
+
*
|
|
157
|
+
* Certain in exactly two cases — a single account, and `fill-first`, which
|
|
158
|
+
* always takes the oldest. Round-robin and lowest-usage across several
|
|
159
|
+
* accounts are genuinely undecided until the turn starts, and this returns
|
|
160
|
+
* null rather than guessing at one.
|
|
161
|
+
*/
|
|
162
|
+
export function peekCodexAccount(): CodexAccount | null {
|
|
163
|
+
const accts = listCodexAccounts();
|
|
164
|
+
if (accts.length === 0) return null;
|
|
165
|
+
if (accts.length === 1) return accts[0]!;
|
|
166
|
+
return getCodexStrategy() === "fill-first" ? accts[0]! : null;
|
|
167
|
+
}
|
|
168
|
+
|
|
148
169
|
/** Sticky account for a session → else strategy pick → else null (default ~/.codex). */
|
|
149
170
|
export async function resolveCodexAccountForSession(sessionId: string): Promise<CodexAccount | null> {
|
|
150
171
|
const sticky = getSessionCodexAccount(sessionId);
|
|
@@ -159,13 +180,15 @@ export async function resolveCodexAccountForSession(sessionId: string): Promise<
|
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
// ── Usage (per account) ──
|
|
183
|
+
// Both read through the shared provider-usage layer, so the accounts screen is
|
|
184
|
+
// served from the same cache and snapshot store as the chat toolbar and cannot
|
|
185
|
+
// spawn one app-server per listed account on every render.
|
|
162
186
|
export async function getCodexAccountUsage(id: string): Promise<UsageInfo> {
|
|
163
|
-
|
|
164
|
-
return a ? fetchCodexUsage(a.home) : {};
|
|
187
|
+
return getCodexAccount(id) ? getOrFetchUsage("codex", id) : {};
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
export async function getAllCodexUsages(): Promise<Record<string, UsageInfo>> {
|
|
168
191
|
const accts = listCodexAccounts();
|
|
169
|
-
const entries = await Promise.all(accts.map(async (a) => [a.id, await
|
|
192
|
+
const entries = await Promise.all(accts.map(async (a) => [a.id, await getOrFetchUsage("codex", a.id)] as const));
|
|
170
193
|
return Object.fromEntries(entries);
|
|
171
194
|
}
|