codex-multi-auth 2.3.0-beta.0 → 2.3.0-beta.2
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/.codex-plugin/plugin.json +1 -1
- package/README.md +4 -3
- package/config/schema/config.schema.json +268 -21
- package/dist/index.js +0 -1
- package/dist/lib/account-policy.js +0 -1
- package/dist/lib/accounts/rate-limits.js +0 -1
- package/dist/lib/accounts.d.ts +3 -8
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +2 -3
- package/dist/lib/audit.js +0 -1
- package/dist/lib/auth/auth.js +0 -1
- package/dist/lib/auth/browser.js +0 -1
- package/dist/lib/auth/device-auth.js +0 -1
- package/dist/lib/auth/index.js +0 -1
- package/dist/lib/auth/org-override.js +0 -1
- package/dist/lib/auth/server.js +0 -1
- package/dist/lib/auth/token-utils.js +0 -1
- package/dist/lib/auth-rate-limit.js +0 -1
- package/dist/lib/budget-guard.js +0 -1
- package/dist/lib/capability-policy.js +0 -1
- package/dist/lib/circuit-breaker.js +0 -1
- package/dist/lib/cli.js +1 -2
- package/dist/lib/codex-cli/index.js +0 -1
- package/dist/lib/codex-cli/observability.js +0 -1
- package/dist/lib/codex-cli/state.js +0 -1
- package/dist/lib/codex-cli/sync.d.ts +2 -2
- package/dist/lib/codex-cli/sync.d.ts.map +1 -1
- package/dist/lib/codex-cli/sync.js +1 -2
- package/dist/lib/codex-cli/writer.js +0 -1
- package/dist/lib/codex-manager/account-credentials.d.ts +13 -0
- package/dist/lib/codex-manager/account-credentials.d.ts.map +1 -0
- package/dist/lib/codex-manager/account-credentials.js +55 -0
- package/dist/lib/codex-manager/account-manager-commands.js +0 -1
- package/dist/lib/codex-manager/account-pool-write.d.ts +95 -0
- package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -0
- package/dist/lib/codex-manager/account-pool-write.js +165 -0
- package/dist/lib/codex-manager/account-pool-write.js.map +1 -0
- package/dist/lib/codex-manager/backend-category-entry.js +0 -1
- package/dist/lib/codex-manager/backend-category-helpers.js +0 -1
- package/dist/lib/codex-manager/backend-category-prompt.js +0 -1
- package/dist/lib/codex-manager/backend-settings-controller.js +0 -1
- package/dist/lib/codex-manager/backend-settings-entry.js +0 -1
- package/dist/lib/codex-manager/backend-settings-helpers.js +0 -1
- package/dist/lib/codex-manager/backend-settings-prompt.js +0 -1
- package/dist/lib/codex-manager/backend-settings-schema.js +0 -1
- package/dist/lib/codex-manager/behavior-settings-panel.d.ts +1 -1
- package/dist/lib/codex-manager/behavior-settings-panel.d.ts.map +1 -1
- package/dist/lib/codex-manager/behavior-settings-panel.js +0 -1
- package/dist/lib/codex-manager/commands/account.js +0 -1
- package/dist/lib/codex-manager/commands/best.d.ts +8 -0
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +64 -1
- package/dist/lib/codex-manager/commands/bridge.js +0 -1
- package/dist/lib/codex-manager/commands/budget.js +0 -1
- package/dist/lib/codex-manager/commands/check.js +0 -1
- package/dist/lib/codex-manager/commands/config-explain.js +0 -1
- package/dist/lib/codex-manager/commands/debug-bundle.js +0 -1
- package/dist/lib/codex-manager/commands/forecast.js +0 -1
- package/dist/lib/codex-manager/commands/init-config.js +0 -1
- package/dist/lib/codex-manager/commands/integrations.js +0 -1
- package/dist/lib/codex-manager/commands/models.js +0 -1
- package/dist/lib/codex-manager/commands/monitor.js +0 -1
- package/dist/lib/codex-manager/commands/report.js +0 -1
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.js +1 -4
- package/dist/lib/codex-manager/commands/status.js +0 -1
- package/dist/lib/codex-manager/commands/switch.js +0 -1
- package/dist/lib/codex-manager/commands/uninstall.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/uninstall.js +2 -33
- package/dist/lib/codex-manager/commands/unpin.js +0 -1
- package/dist/lib/codex-manager/commands/usage.js +0 -1
- package/dist/lib/codex-manager/commands/verify.js +0 -1
- package/dist/lib/codex-manager/commands/why-selected.js +0 -1
- package/dist/lib/codex-manager/commands/workspace.js +0 -1
- package/dist/lib/codex-manager/dashboard-display-panel.d.ts +1 -1
- package/dist/lib/codex-manager/dashboard-display-panel.d.ts.map +1 -1
- package/dist/lib/codex-manager/dashboard-display-panel.js +0 -1
- package/dist/lib/codex-manager/dashboard-formatters.js +0 -1
- package/dist/lib/codex-manager/dashboard-settings-controller.js +0 -1
- package/dist/lib/codex-manager/dashboard-settings-data.js +0 -1
- package/dist/lib/codex-manager/dashboard-settings-entry.js +0 -1
- package/dist/lib/codex-manager/experimental-settings-entry.js +0 -1
- package/dist/lib/codex-manager/experimental-settings-prompt.js +0 -1
- package/dist/lib/codex-manager/experimental-settings-schema.js +1 -2
- package/dist/lib/codex-manager/experimental-sync-target-entry.js +0 -1
- package/dist/lib/codex-manager/experimental-sync-target.js +0 -1
- package/dist/lib/codex-manager/forecast-report-commands.js +0 -1
- package/dist/lib/codex-manager/forecast-report-shared.d.ts +2 -2
- package/dist/lib/codex-manager/forecast-report-shared.d.ts.map +1 -1
- package/dist/lib/codex-manager/forecast-report-shared.js +5 -21
- package/dist/lib/codex-manager/formatters/account-formatters.d.ts +11 -0
- package/dist/lib/codex-manager/formatters/account-formatters.d.ts.map +1 -0
- package/dist/lib/codex-manager/formatters/account-formatters.js +79 -0
- package/dist/lib/codex-manager/formatters/index.d.ts +5 -0
- package/dist/lib/codex-manager/formatters/index.d.ts.map +1 -0
- package/dist/lib/codex-manager/formatters/index.js +4 -0
- package/dist/lib/codex-manager/formatters/model-formatters.d.ts +12 -0
- package/dist/lib/codex-manager/formatters/model-formatters.d.ts.map +1 -0
- package/dist/lib/codex-manager/formatters/model-formatters.js +23 -0
- package/dist/lib/codex-manager/formatters/quota-formatters.d.ts +11 -0
- package/dist/lib/codex-manager/formatters/quota-formatters.d.ts.map +1 -0
- package/dist/lib/codex-manager/formatters/quota-formatters.js +104 -0
- package/dist/lib/codex-manager/formatters/text-style.d.ts +14 -0
- package/dist/lib/codex-manager/formatters/text-style.d.ts.map +1 -0
- package/dist/lib/codex-manager/formatters/text-style.js +111 -0
- package/dist/lib/codex-manager/health-check.d.ts +14 -0
- package/dist/lib/codex-manager/health-check.d.ts.map +1 -0
- package/dist/lib/codex-manager/health-check.js +278 -0
- package/dist/lib/codex-manager/help.js +0 -1
- package/dist/lib/codex-manager/login-action-panel.d.ts +4 -0
- package/dist/lib/codex-manager/login-action-panel.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-action-panel.js +238 -0
- package/dist/lib/codex-manager/login-flow.d.ts +17 -0
- package/dist/lib/codex-manager/login-flow.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-flow.js +387 -0
- package/dist/lib/codex-manager/login-menu-actions.d.ts +20 -0
- package/dist/lib/codex-manager/login-menu-actions.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-menu-actions.js +303 -0
- package/dist/lib/codex-manager/login-menu-data.d.ts +19 -0
- package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-menu-data.js +378 -0
- package/dist/lib/codex-manager/login-oauth.d.ts +55 -0
- package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -0
- package/dist/lib/codex-manager/login-oauth.js +345 -0
- package/dist/lib/codex-manager/manual-callback.d.ts +33 -0
- package/dist/lib/codex-manager/manual-callback.d.ts.map +1 -0
- package/dist/lib/codex-manager/manual-callback.js +30 -0
- package/dist/lib/codex-manager/manual-callback.js.map +1 -0
- package/dist/lib/codex-manager/persist-selected-account.d.ts +24 -0
- package/dist/lib/codex-manager/persist-selected-account.d.ts.map +1 -0
- package/dist/lib/codex-manager/persist-selected-account.js +107 -0
- package/dist/lib/codex-manager/quota-cache-helpers.d.ts +18 -0
- package/dist/lib/codex-manager/quota-cache-helpers.d.ts.map +1 -0
- package/dist/lib/codex-manager/quota-cache-helpers.js +112 -0
- package/dist/lib/codex-manager/rate-limit-markers.js +0 -1
- package/dist/lib/codex-manager/repair-commands.js +0 -1
- package/dist/lib/codex-manager/settings-hub/backend.js +1 -2
- package/dist/lib/codex-manager/settings-hub/dashboard.js +1 -2
- package/dist/lib/codex-manager/settings-hub/experimental.js +1 -2
- package/dist/lib/codex-manager/settings-hub/index.js +1 -2
- package/dist/lib/codex-manager/settings-hub/shared.js +0 -1
- package/dist/lib/codex-manager/settings-hub-entry.js +0 -1
- package/dist/lib/codex-manager/settings-hub-menu.js +0 -1
- package/dist/lib/codex-manager/settings-hub-prompt.js +0 -1
- package/dist/lib/codex-manager/settings-hub.js +0 -1
- package/dist/lib/codex-manager/settings-panels.js +0 -1
- package/dist/lib/codex-manager/settings-persist-utils.js +0 -1
- package/dist/lib/codex-manager/settings-preview.js +0 -1
- package/dist/lib/codex-manager/settings-write-queue.js +0 -1
- package/dist/lib/codex-manager/statusline-order.js +0 -1
- package/dist/lib/codex-manager/statusline-settings-panel.d.ts +1 -1
- package/dist/lib/codex-manager/statusline-settings-panel.d.ts.map +1 -1
- package/dist/lib/codex-manager/statusline-settings-panel.js +0 -1
- package/dist/lib/codex-manager/theme-settings-panel.d.ts +1 -1
- package/dist/lib/codex-manager/theme-settings-panel.d.ts.map +1 -1
- package/dist/lib/codex-manager/theme-settings-panel.js +0 -1
- package/dist/lib/codex-manager/unified-settings-controller.js +0 -1
- package/dist/lib/codex-manager/unified-settings-entry.js +0 -1
- package/dist/lib/codex-manager.d.ts +2 -4
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +219 -2578
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config-schema.d.ts +32 -0
- package/dist/lib/config-schema.d.ts.map +1 -0
- package/dist/lib/config-schema.js +96 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +60 -104
- package/dist/lib/constants.js +0 -1
- package/dist/lib/context-overflow.js +0 -1
- package/dist/lib/dashboard-settings.js +0 -1
- package/dist/lib/entitlement-cache.js +0 -1
- package/dist/lib/env-parsing.js +0 -1
- package/dist/lib/errors.js +0 -1
- package/dist/lib/forecast.js +0 -1
- package/dist/lib/fs-retry.d.ts +46 -0
- package/dist/lib/fs-retry.d.ts.map +1 -1
- package/dist/lib/fs-retry.js +74 -6
- package/dist/lib/health.js +0 -1
- package/dist/lib/index.js +0 -1
- package/dist/lib/integration-generators.js +0 -1
- package/dist/lib/live-account-sync.js +0 -1
- package/dist/lib/local-bridge.js +0 -1
- package/dist/lib/local-client-tokens.js +0 -1
- package/dist/lib/logger.js +0 -1
- package/dist/lib/model-capability-matrix.js +0 -1
- package/dist/lib/named-backup-export.js +0 -1
- package/dist/lib/oc-chatgpt-import-adapter.js +0 -1
- package/dist/lib/oc-chatgpt-orchestrator.js +0 -1
- package/dist/lib/oc-chatgpt-target-detection.js +0 -1
- package/dist/lib/parallel-probe.js +0 -1
- package/dist/lib/policy/runtime-policy.js +0 -1
- package/dist/lib/preemptive-quota-scheduler.js +0 -1
- package/dist/lib/proactive-refresh.js +0 -1
- package/dist/lib/prompts/codex-host-bridge.js +0 -1
- package/dist/lib/prompts/codex.d.ts +3 -11
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +4 -12
- package/dist/lib/prompts/fetch-utils.js +0 -1
- package/dist/lib/prompts/host-codex-prompt.js +0 -1
- package/dist/lib/quota-cache.d.ts.map +1 -1
- package/dist/lib/quota-cache.js +15 -43
- package/dist/lib/quota-probe.js +0 -1
- package/dist/lib/quota-readiness.js +0 -1
- package/dist/lib/recovery/constants.js +0 -1
- package/dist/lib/recovery/index.js +0 -1
- package/dist/lib/recovery/storage.d.ts.map +1 -1
- package/dist/lib/recovery/storage.js +30 -51
- package/dist/lib/recovery/storage.js.map +1 -1
- package/dist/lib/recovery/types.js +0 -1
- package/dist/lib/recovery.js +0 -1
- package/dist/lib/refresh-guardian.js +0 -1
- package/dist/lib/refresh-lease.d.ts.map +1 -1
- package/dist/lib/refresh-lease.js +1 -4
- package/dist/lib/refresh-queue.js +0 -1
- package/dist/lib/request/error-classification.d.ts +75 -0
- package/dist/lib/request/error-classification.d.ts.map +1 -0
- package/dist/lib/request/error-classification.js +295 -0
- package/dist/lib/request/failover-config.js +0 -1
- package/dist/lib/request/failure-policy.js +0 -1
- package/dist/lib/request/fetch-helpers.d.ts +10 -114
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +7 -589
- package/dist/lib/request/headers.d.ts +33 -0
- package/dist/lib/request/headers.d.ts.map +1 -0
- package/dist/lib/request/headers.js +64 -0
- package/dist/lib/request/helpers/input-utils.js +0 -1
- package/dist/lib/request/helpers/model-map.d.ts +10 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +11 -1
- package/dist/lib/request/helpers/tool-utils.js +0 -1
- package/dist/lib/request/index.js +0 -1
- package/dist/lib/request/rate-limit-backoff.js +0 -1
- package/dist/lib/request/rate-limit-decision.d.ts +28 -0
- package/dist/lib/request/rate-limit-decision.d.ts.map +1 -0
- package/dist/lib/request/rate-limit-decision.js +191 -0
- package/dist/lib/request/request-attempt-budget.js +0 -1
- package/dist/lib/request/request-init.js +0 -1
- package/dist/lib/request/request-resilience.js +0 -1
- package/dist/lib/request/request-transformer.js +0 -1
- package/dist/lib/request/response-compaction.js +0 -1
- package/dist/lib/request/response-handler.js +0 -1
- package/dist/lib/request/response-metadata.js +0 -1
- package/dist/lib/request/stream-failover-runtime.d.ts +8 -0
- package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -0
- package/dist/lib/request/stream-failover-runtime.js +149 -0
- package/dist/lib/request/stream-failover.js +0 -1
- package/dist/lib/request/token-refresh.d.ts +30 -0
- package/dist/lib/request/token-refresh.d.ts.map +1 -0
- package/dist/lib/request/token-refresh.js +116 -0
- package/dist/lib/request/url-rewriting.d.ts +24 -0
- package/dist/lib/request/url-rewriting.d.ts.map +1 -0
- package/dist/lib/request/url-rewriting.js +153 -0
- package/dist/lib/request/wait-utils.js +0 -1
- package/dist/lib/rotation.d.ts +1 -1
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +0 -1
- package/dist/lib/routing-mutex.js +0 -1
- package/dist/lib/routing-profiles.js +0 -1
- package/dist/lib/runtime/account-check-helpers.js +0 -1
- package/dist/lib/runtime/account-check-types.js +0 -1
- package/dist/lib/runtime/account-check.js +0 -1
- package/dist/lib/runtime/account-health-check.js +0 -1
- package/dist/lib/runtime/account-manager-cache-entry.js +0 -1
- package/dist/lib/runtime/account-manager-cache.js +0 -1
- package/dist/lib/runtime/account-pool.js +0 -1
- package/dist/lib/runtime/account-scope.js +0 -1
- package/dist/lib/runtime/account-select-event.js +0 -1
- package/dist/lib/runtime/account-selection.js +0 -1
- package/dist/lib/runtime/account-state.js +0 -1
- package/dist/lib/runtime/account-status.js +0 -1
- package/dist/lib/runtime/account-storage-scope-entry.js +0 -1
- package/dist/lib/runtime/app-bind.js +0 -1
- package/dist/lib/runtime/auth-facade.js +0 -1
- package/dist/lib/runtime/browser-oauth-flow.js +0 -1
- package/dist/lib/runtime/capability-boost.js +0 -1
- package/dist/lib/runtime/config-toml.js +0 -1
- package/dist/lib/runtime/event-handler.js +0 -1
- package/dist/lib/runtime/first-run.d.ts +80 -0
- package/dist/lib/runtime/first-run.d.ts.map +1 -0
- package/dist/lib/runtime/first-run.js +327 -0
- package/dist/lib/runtime/flagged-verify-types.js +0 -1
- package/dist/lib/runtime/hydrate-emails.js +0 -1
- package/dist/lib/runtime/live-sync-entry.js +0 -1
- package/dist/lib/runtime/live-sync.js +0 -1
- package/dist/lib/runtime/loader-setup.js +0 -1
- package/dist/lib/runtime/login-menu-accounts.js +0 -1
- package/dist/lib/runtime/manual-oauth-flow.js +0 -1
- package/dist/lib/runtime/oauth-browser-flow.js +0 -1
- package/dist/lib/runtime/preemptive-quota.js +0 -1
- package/dist/lib/runtime/quota-headers.js +0 -1
- package/dist/lib/runtime/quota-probe.js +0 -1
- package/dist/lib/runtime/quota-settings.js +0 -1
- package/dist/lib/runtime/refresh-guardian-entry.js +0 -1
- package/dist/lib/runtime/refresh-guardian.js +0 -1
- package/dist/lib/runtime/request-init.js +0 -1
- package/dist/lib/runtime/rotation-account-selection.d.ts +38 -0
- package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -0
- package/dist/lib/runtime/rotation-account-selection.js +172 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts +54 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -0
- package/dist/lib/runtime/rotation-proxy-state.js +56 -0
- package/dist/lib/runtime/rotation-server-types.d.ts +57 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -0
- package/dist/lib/runtime/rotation-server-types.js +1 -0
- package/dist/lib/runtime/rotation-storage-meta.d.ts +40 -0
- package/dist/lib/runtime/rotation-storage-meta.d.ts.map +1 -0
- package/dist/lib/runtime/rotation-storage-meta.js +143 -0
- package/dist/lib/runtime/rotation-token-refresh.d.ts +25 -0
- package/dist/lib/runtime/rotation-token-refresh.d.ts.map +1 -0
- package/dist/lib/runtime/rotation-token-refresh.js +99 -0
- package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-current-account.js +1 -4
- package/dist/lib/runtime/runtime-observability.js +0 -1
- package/dist/lib/runtime/runtime-services.js +0 -1
- package/dist/lib/runtime/session-affinity-entry.js +0 -1
- package/dist/lib/runtime/session-affinity.js +0 -1
- package/dist/lib/runtime/session-recovery.js +0 -1
- package/dist/lib/runtime/status-marker.js +0 -1
- package/dist/lib/runtime/storage-scope.js +0 -1
- package/dist/lib/runtime/toast.js +0 -1
- package/dist/lib/runtime/ui-runtime-entry.js +0 -1
- package/dist/lib/runtime/ui-runtime.js +0 -1
- package/dist/lib/runtime/verify-flagged.js +0 -1
- package/dist/lib/runtime-constants.js +0 -1
- package/dist/lib/runtime-paths.js +0 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +7 -124
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +614 -1353
- package/dist/lib/schemas.d.ts +3 -3
- package/dist/lib/schemas.js +1 -2
- package/dist/lib/session-affinity.js +0 -1
- package/dist/lib/shutdown.js +0 -1
- package/dist/lib/storage/account-clear-entry.js +0 -1
- package/dist/lib/storage/account-clear.js +0 -1
- package/dist/lib/storage/account-match-utils.js +0 -1
- package/dist/lib/storage/account-persistence.d.ts +1 -1
- package/dist/lib/storage/account-persistence.d.ts.map +1 -1
- package/dist/lib/storage/account-persistence.js +0 -1
- package/dist/lib/storage/account-port.d.ts +1 -1
- package/dist/lib/storage/account-port.d.ts.map +1 -1
- package/dist/lib/storage/account-port.js +0 -1
- package/dist/lib/storage/account-save-entry.d.ts +1 -1
- package/dist/lib/storage/account-save-entry.d.ts.map +1 -1
- package/dist/lib/storage/account-save-entry.js +0 -1
- package/dist/lib/storage/account-save.d.ts +1 -1
- package/dist/lib/storage/account-save.d.ts.map +1 -1
- package/dist/lib/storage/account-save.js +0 -1
- package/dist/lib/storage/account-snapshot.js +0 -1
- package/dist/lib/storage/backup-metadata-builder.d.ts +1 -1
- package/dist/lib/storage/backup-metadata-builder.d.ts.map +1 -1
- package/dist/lib/storage/backup-metadata-builder.js +0 -1
- package/dist/lib/storage/backup-metadata.d.ts +13 -0
- package/dist/lib/storage/backup-metadata.d.ts.map +1 -1
- package/dist/lib/storage/backup-metadata.js +0 -1
- package/dist/lib/storage/backup-paths.js +0 -1
- package/dist/lib/storage/backup-restore.d.ts +1 -1
- package/dist/lib/storage/backup-restore.d.ts.map +1 -1
- package/dist/lib/storage/backup-restore.js +0 -1
- package/dist/lib/storage/cache-artifacts.js +0 -1
- package/dist/lib/storage/error-hints.js +0 -1
- package/dist/lib/storage/file-paths.js +0 -1
- package/dist/lib/storage/fixture-guards.d.ts +1 -1
- package/dist/lib/storage/fixture-guards.d.ts.map +1 -1
- package/dist/lib/storage/fixture-guards.js +0 -1
- package/dist/lib/storage/flagged-entry.js +0 -1
- package/dist/lib/storage/flagged-load-entry.d.ts +1 -1
- package/dist/lib/storage/flagged-load-entry.d.ts.map +1 -1
- package/dist/lib/storage/flagged-load-entry.js +0 -1
- package/dist/lib/storage/flagged-save-entry.d.ts +1 -1
- package/dist/lib/storage/flagged-save-entry.d.ts.map +1 -1
- package/dist/lib/storage/flagged-save-entry.js +0 -1
- package/dist/lib/storage/flagged-storage-file.d.ts +1 -1
- package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage-file.js +0 -1
- package/dist/lib/storage/flagged-storage-io.d.ts +1 -1
- package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage-io.js +0 -1
- package/dist/lib/storage/flagged-storage.d.ts +1 -1
- package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage.js +15 -2
- package/dist/lib/storage/flagged-storage.js.map +1 -1
- package/dist/lib/storage/gitignore.js +0 -1
- package/dist/lib/storage/hash.js +0 -1
- package/dist/lib/storage/health.js +0 -1
- package/dist/lib/storage/identity.js +0 -1
- package/dist/lib/storage/import-export.d.ts +1 -1
- package/dist/lib/storage/import-export.d.ts.map +1 -1
- package/dist/lib/storage/import-export.js +0 -1
- package/dist/lib/storage/metadata-section.js +0 -1
- package/dist/lib/storage/migrations.d.ts +5 -48
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js +5 -2
- package/dist/lib/storage/named-backup-entry.js +0 -1
- package/dist/lib/storage/named-backups-entry.d.ts +1 -1
- package/dist/lib/storage/named-backups-entry.d.ts.map +1 -1
- package/dist/lib/storage/named-backups-entry.js +0 -1
- package/dist/lib/storage/named-backups.js +0 -1
- package/dist/lib/storage/path-state.js +0 -1
- package/dist/lib/storage/paths.js +0 -1
- package/dist/lib/storage/project-migration.d.ts +1 -1
- package/dist/lib/storage/project-migration.d.ts.map +1 -1
- package/dist/lib/storage/project-migration.js +0 -1
- package/dist/lib/storage/public-types.d.ts +70 -0
- package/dist/lib/storage/public-types.d.ts.map +1 -0
- package/dist/lib/storage/public-types.js +6 -0
- package/dist/lib/storage/record-utils.js +0 -1
- package/dist/lib/storage/restore-assessment.d.ts +1 -1
- package/dist/lib/storage/restore-assessment.d.ts.map +1 -1
- package/dist/lib/storage/restore-assessment.js +0 -1
- package/dist/lib/storage/restore-backup-entry.d.ts +1 -1
- package/dist/lib/storage/restore-backup-entry.d.ts.map +1 -1
- package/dist/lib/storage/restore-backup-entry.js +0 -1
- package/dist/lib/storage/restore-metadata.d.ts +1 -1
- package/dist/lib/storage/restore-metadata.d.ts.map +1 -1
- package/dist/lib/storage/restore-metadata.js +0 -1
- package/dist/lib/storage/restore.d.ts +1 -1
- package/dist/lib/storage/restore.d.ts.map +1 -1
- package/dist/lib/storage/restore.js +0 -1
- package/dist/lib/storage/save-retry.d.ts +10 -0
- package/dist/lib/storage/save-retry.d.ts.map +1 -0
- package/dist/lib/storage/save-retry.js +26 -0
- package/dist/lib/storage/snapshot-inspectors.d.ts +1 -1
- package/dist/lib/storage/snapshot-inspectors.d.ts.map +1 -1
- package/dist/lib/storage/snapshot-inspectors.js +0 -1
- package/dist/lib/storage/storage-parser.d.ts +1 -1
- package/dist/lib/storage/storage-parser.d.ts.map +1 -1
- package/dist/lib/storage/storage-parser.js +0 -1
- package/dist/lib/storage/transactions.d.ts +1 -1
- package/dist/lib/storage/transactions.d.ts.map +1 -1
- package/dist/lib/storage/transactions.js +0 -1
- package/dist/lib/storage.d.ts +3 -24
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +17 -32
- package/dist/lib/table-formatter.js +0 -1
- package/dist/lib/tools/hashline-tools.js +0 -1
- package/dist/lib/types.js +0 -1
- package/dist/lib/ui/ansi.js +0 -1
- package/dist/lib/ui/auth-menu-builder.d.ts +89 -0
- package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -0
- package/dist/lib/ui/auth-menu-builder.js +350 -0
- package/dist/lib/ui/auth-menu.d.ts +2 -76
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +3 -349
- package/dist/lib/ui/confirm.js +0 -1
- package/dist/lib/ui/copy.d.ts +2 -0
- package/dist/lib/ui/copy.d.ts.map +1 -1
- package/dist/lib/ui/copy.js +2 -0
- package/dist/lib/ui/copy.js.map +1 -1
- package/dist/lib/ui/display-width.js +0 -1
- package/dist/lib/ui/format.js +0 -1
- package/dist/lib/ui/index.d.ts +10 -0
- package/dist/lib/ui/index.d.ts.map +1 -0
- package/dist/lib/ui/index.js +9 -0
- package/dist/lib/ui/runtime.js +0 -1
- package/dist/lib/ui/select.js +0 -1
- package/dist/lib/ui/theme.js +0 -1
- package/dist/lib/ui/ui-copy.d.ts +159 -0
- package/dist/lib/ui/ui-copy.d.ts.map +1 -0
- package/dist/lib/ui/ui-copy.js +163 -0
- package/dist/lib/unified-settings.js +0 -1
- package/dist/lib/update-notice.js +0 -1
- package/dist/lib/usage/index.js +0 -1
- package/dist/lib/usage/ledger.js +0 -1
- package/dist/lib/usage/pricing.js +0 -1
- package/dist/lib/usage/redaction.js +0 -1
- package/dist/lib/usage/types.js +0 -1
- package/dist/lib/utils.js +0 -1
- package/package.json +8 -8
- package/scripts/postinstall.js +34 -265
- package/scripts/preuninstall.js +2 -2
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createInterface } from "node:readline/promises";
|
|
3
|
-
import { extractAccountEmail, extractAccountId, formatAccountLabel, formatWaitTime, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./accounts.js";
|
|
4
|
-
import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, redactOAuthUrlForLog, REDIRECT_URI, } from "./auth/auth.js";
|
|
5
|
-
import { runDeviceAuthFlow } from "./auth/device-auth.js";
|
|
6
|
-
import { resolveOrgOverride } from "./auth/org-override.js";
|
|
7
|
-
import { copyTextToClipboard, isBrowserLaunchSuppressed, openBrowserUrl, } from "./auth/browser.js";
|
|
8
|
-
import { startLocalOAuthServer } from "./auth/server.js";
|
|
9
|
-
import { promptAddAnotherAccount, promptLoginMode, } from "./cli.js";
|
|
1
|
+
import { extractAccountEmail, extractAccountId, formatAccountLabel, formatWaitTime, sanitizeEmail, } from "./accounts.js";
|
|
10
2
|
import { loadCodexCliState } from "./codex-cli/state.js";
|
|
11
3
|
import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
|
|
12
|
-
import { runBestCommand, } from "./codex-manager/commands/best.js";
|
|
4
|
+
import { parseBestArgs, printBestUsage, runBestCommand, } from "./codex-manager/commands/best.js";
|
|
5
|
+
import { applyTokenAccountIdentity, hasLikelyInvalidRefreshToken, hasUsableAccessToken, resolveStoredAccountIdentity, } from "./codex-manager/account-credentials.js";
|
|
6
|
+
import { runHealthCheck } from "./codex-manager/health-check.js";
|
|
7
|
+
import { runAuthLogin } from "./codex-manager/login-flow.js";
|
|
8
|
+
import { persistAndSyncSelectedAccount } from "./codex-manager/persist-selected-account.js";
|
|
9
|
+
import { cloneQuotaCacheData, pruneUnsafeQuotaEmailCacheEntry, updateQuotaCacheForAccount, } from "./codex-manager/quota-cache-helpers.js";
|
|
13
10
|
import { runAccountCommand } from "./codex-manager/commands/account.js";
|
|
14
11
|
import { ACCOUNT_MANAGER_COMMANDS } from "./codex-manager/account-manager-commands.js";
|
|
12
|
+
import { ensureFirstRunSetup } from "./runtime/first-run.js";
|
|
15
13
|
import { runBudgetCommand } from "./codex-manager/commands/budget.js";
|
|
16
14
|
import { runBridgeCommand } from "./codex-manager/commands/bridge.js";
|
|
17
15
|
import { runCheckCommand } from "./codex-manager/commands/check.js";
|
|
@@ -19,7 +17,6 @@ import { runIntegrationsCommand } from "./codex-manager/commands/integrations.js
|
|
|
19
17
|
import { runModelsCommand } from "./codex-manager/commands/models.js";
|
|
20
18
|
import { runMonitorCommand } from "./codex-manager/commands/monitor.js";
|
|
21
19
|
import { runConfigExplainCommand } from "./codex-manager/commands/config-explain.js";
|
|
22
|
-
import { saveAccountsWithRetry } from "./codex-manager/forecast-report-shared.js";
|
|
23
20
|
import { runDebugBundleCommand } from "./codex-manager/commands/debug-bundle.js";
|
|
24
21
|
import { parseWhySelectedArgs, printWhySelectedUsage, runWhySelectedCommand, } from "./codex-manager/commands/why-selected.js";
|
|
25
22
|
import { parseVerifyArgs, printVerifyUsage, runVerifyCommand, } from "./codex-manager/commands/verify.js";
|
|
@@ -37,146 +34,24 @@ import { runSwitchCommand } from "./codex-manager/commands/switch.js";
|
|
|
37
34
|
import { runUnpinCommand } from "./codex-manager/commands/unpin.js";
|
|
38
35
|
import { runWorkspaceCommand } from "./codex-manager/commands/workspace.js";
|
|
39
36
|
import { runUsageCommand } from "./codex-manager/commands/usage.js";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
37
|
+
import { printUsage } from "./codex-manager/help.js";
|
|
38
|
+
import { availabilityTone, formatBackupSavedAt, formatCompactQuotaSnapshot, formatRateLimitEntry, formatResultSummary, normalizeFailureDetail, riskTone, styleAccountDetailText, stylePromptText, styleQuotaSummary, } from "./codex-manager/formatters/index.js";
|
|
39
|
+
import { applyUiThemeFromDashboardSettings } from "./codex-manager/settings-hub.js";
|
|
42
40
|
import { getCodexRuntimeRotationProxy, getPluginConfigExplainReport, loadPluginConfig, savePluginConfig, } from "./config.js";
|
|
43
41
|
import { bindCodexAppRuntimeRotation, getAppBindStatus, unbindCodexAppRuntimeRotation, } from "./runtime/app-bind.js";
|
|
44
|
-
import { ACCOUNT_LIMITS } from "./constants.js";
|
|
45
42
|
import { DEFAULT_DASHBOARD_DISPLAY_SETTINGS, loadDashboardDisplaySettings, } from "./dashboard-settings.js";
|
|
46
43
|
import { evaluateForecastAccounts, recommendForecastAccount, summarizeForecast, } from "./forecast.js";
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
|
|
53
|
-
import { isDisplayCurrentAccount, readAppRuntimeHelperAccountSignal, resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "./runtime/runtime-current-account.js";
|
|
54
|
-
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, CODEX_UNAVAILABLE_PROBE_NOTE, } from "./quota-probe.js";
|
|
55
|
-
import { isCodexUnavailableError } from "./errors.js";
|
|
44
|
+
import { resolveActiveIndex } from "./runtime/account-status.js";
|
|
45
|
+
import { buildQuotaEmailFallbackState } from "./quota-readiness.js";
|
|
46
|
+
import { loadQuotaCache, saveQuotaCache } from "./quota-cache.js";
|
|
47
|
+
import { readAppRuntimeHelperAccountSignal } from "./runtime/runtime-current-account.js";
|
|
48
|
+
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
|
|
56
49
|
import { queuedRefresh } from "./refresh-queue.js";
|
|
57
|
-
import { clearAccounts, findMatchingAccountIndex,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
import { getUiRuntimeOptions } from "./ui/runtime.js";
|
|
63
|
-
import { select } from "./ui/select.js";
|
|
64
|
-
const log = createLogger("codex-manager");
|
|
65
|
-
function stylePromptText(text, tone) {
|
|
66
|
-
if (!output.isTTY)
|
|
67
|
-
return text;
|
|
68
|
-
const ui = getUiRuntimeOptions();
|
|
69
|
-
if (ui.v2Enabled) {
|
|
70
|
-
if (tone === "muted") {
|
|
71
|
-
return `${ui.theme.colors.dim}${paintUiText(ui, text, "muted")}${ui.theme.colors.reset}`;
|
|
72
|
-
}
|
|
73
|
-
const mapped = tone === "accent" ? "primary" : tone;
|
|
74
|
-
return paintUiText(ui, text, mapped);
|
|
75
|
-
}
|
|
76
|
-
const legacyCode = tone === "accent"
|
|
77
|
-
? ANSI.green
|
|
78
|
-
: tone === "success"
|
|
79
|
-
? ANSI.green
|
|
80
|
-
: tone === "warning"
|
|
81
|
-
? ANSI.yellow
|
|
82
|
-
: tone === "danger"
|
|
83
|
-
? ANSI.red
|
|
84
|
-
: ANSI.dim;
|
|
85
|
-
return `${legacyCode}${text}${ANSI.reset}`;
|
|
86
|
-
}
|
|
87
|
-
function inspectRequestedModel(requestedModel) {
|
|
88
|
-
const normalized = resolveNormalizedModel(requestedModel);
|
|
89
|
-
const profile = getModelProfile(normalized);
|
|
90
|
-
return {
|
|
91
|
-
requested: requestedModel,
|
|
92
|
-
normalized,
|
|
93
|
-
remapped: requestedModel !== normalized,
|
|
94
|
-
promptFamily: profile.promptFamily,
|
|
95
|
-
capabilities: getModelCapabilities(normalized),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function formatModelInspection(model) {
|
|
99
|
-
const route = model.remapped
|
|
100
|
-
? `${model.requested} -> ${model.normalized}`
|
|
101
|
-
: model.normalized;
|
|
102
|
-
return [
|
|
103
|
-
route,
|
|
104
|
-
`prompt family ${model.promptFamily}`,
|
|
105
|
-
`tool search ${model.capabilities.toolSearch ? "yes" : "no"}`,
|
|
106
|
-
`computer use ${model.capabilities.computerUse ? "yes" : "no"}`,
|
|
107
|
-
].join(" | ");
|
|
108
|
-
}
|
|
109
|
-
function collapseWhitespace(value) {
|
|
110
|
-
return value.replace(/\s+/g, " ").trim();
|
|
111
|
-
}
|
|
112
|
-
function formatReasonLabel(reason) {
|
|
113
|
-
if (!reason)
|
|
114
|
-
return undefined;
|
|
115
|
-
const normalized = collapseWhitespace(reason.replace(/_/g, " "));
|
|
116
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
117
|
-
}
|
|
118
|
-
function extractErrorMessageFromPayload(payload) {
|
|
119
|
-
if (!payload || typeof payload !== "object")
|
|
120
|
-
return undefined;
|
|
121
|
-
const record = payload;
|
|
122
|
-
const directMessage = typeof record.message === "string"
|
|
123
|
-
? collapseWhitespace(record.message)
|
|
124
|
-
: "";
|
|
125
|
-
const directCode = typeof record.code === "string" ? collapseWhitespace(record.code) : "";
|
|
126
|
-
if (directMessage) {
|
|
127
|
-
if (directCode &&
|
|
128
|
-
!directMessage.toLowerCase().includes(directCode.toLowerCase())) {
|
|
129
|
-
return `${directMessage} [${directCode}]`;
|
|
130
|
-
}
|
|
131
|
-
return directMessage;
|
|
132
|
-
}
|
|
133
|
-
const nested = record.error;
|
|
134
|
-
if (nested && typeof nested === "object") {
|
|
135
|
-
return extractErrorMessageFromPayload(nested);
|
|
136
|
-
}
|
|
137
|
-
return undefined;
|
|
138
|
-
}
|
|
139
|
-
function parseStructuredErrorMessage(raw) {
|
|
140
|
-
const trimmed = raw.trim();
|
|
141
|
-
if (!trimmed)
|
|
142
|
-
return undefined;
|
|
143
|
-
const candidates = new Set([trimmed]);
|
|
144
|
-
const firstBrace = trimmed.indexOf("{");
|
|
145
|
-
const lastBrace = trimmed.lastIndexOf("}");
|
|
146
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
147
|
-
candidates.add(trimmed.slice(firstBrace, lastBrace + 1));
|
|
148
|
-
}
|
|
149
|
-
for (const candidate of candidates) {
|
|
150
|
-
try {
|
|
151
|
-
const parsed = JSON.parse(candidate);
|
|
152
|
-
const message = extractErrorMessageFromPayload(parsed);
|
|
153
|
-
if (message)
|
|
154
|
-
return message;
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
// ignore non-JSON candidates
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
function normalizeFailureDetail(message, reason) {
|
|
163
|
-
const reasonLabel = formatReasonLabel(reason);
|
|
164
|
-
const raw = message?.trim() || reasonLabel || "refresh failed";
|
|
165
|
-
const structured = parseStructuredErrorMessage(raw);
|
|
166
|
-
const normalized = collapseWhitespace(structured ?? raw);
|
|
167
|
-
const bounded = normalized.length > 260 ? `${normalized.slice(0, 257)}...` : normalized;
|
|
168
|
-
return bounded.length > 0 ? bounded : "refresh failed";
|
|
169
|
-
}
|
|
170
|
-
function joinStyledSegments(parts) {
|
|
171
|
-
if (parts.length === 0)
|
|
172
|
-
return "";
|
|
173
|
-
const separator = stylePromptText(" | ", "muted");
|
|
174
|
-
return parts.join(separator);
|
|
175
|
-
}
|
|
176
|
-
function formatResultSummary(segments) {
|
|
177
|
-
const rendered = segments.map((segment) => stylePromptText(segment.text, segment.tone));
|
|
178
|
-
return `${stylePromptText("Result:", "accent")} ${joinStyledSegments(rendered)}`;
|
|
179
|
-
}
|
|
50
|
+
import { clearAccounts, findMatchingAccountIndex, inspectStorageHealth, getLastAccountsSaveTimestamp, getStoragePath, loadAccounts, loadFlaggedAccounts, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "./storage.js";
|
|
51
|
+
// Formatter implementations moved to lib/codex-manager/formatters/ (audit
|
|
52
|
+
// roadmap §4.1.1 phase 1). Re-exported here so existing consumers importing
|
|
53
|
+
// from lib/codex-manager.js keep working unchanged.
|
|
54
|
+
export { formatBackupSavedAt, styleAccountDetailText };
|
|
180
55
|
function createRepairCommandDeps() {
|
|
181
56
|
return {
|
|
182
57
|
stylePromptText,
|
|
@@ -195,112 +70,6 @@ function createRepairCommandDeps() {
|
|
|
195
70
|
applyTokenAccountIdentity,
|
|
196
71
|
};
|
|
197
72
|
}
|
|
198
|
-
function isOAuthCancellation(result) {
|
|
199
|
-
const message = (result.message ?? result.reason ?? "").trim().toLowerCase();
|
|
200
|
-
return message.includes("cancelled") || message.includes("canceled");
|
|
201
|
-
}
|
|
202
|
-
function styleQuotaSummary(summary) {
|
|
203
|
-
const normalized = collapseWhitespace(summary);
|
|
204
|
-
if (!normalized)
|
|
205
|
-
return stylePromptText(summary, "muted");
|
|
206
|
-
const segments = normalized
|
|
207
|
-
.split("|")
|
|
208
|
-
.map((segment) => segment.trim())
|
|
209
|
-
.filter(Boolean);
|
|
210
|
-
if (segments.length === 0)
|
|
211
|
-
return stylePromptText(normalized, "muted");
|
|
212
|
-
const rendered = segments.map((segment) => {
|
|
213
|
-
if (/rate-limited/i.test(segment)) {
|
|
214
|
-
return stylePromptText(segment, "danger");
|
|
215
|
-
}
|
|
216
|
-
const match = segment.match(/^([0-9a-zA-Z]+)\s+(\d{1,3})%$/);
|
|
217
|
-
if (!match) {
|
|
218
|
-
return stylePromptText(segment, "muted");
|
|
219
|
-
}
|
|
220
|
-
const windowLabel = match[1] ?? "";
|
|
221
|
-
const leftPercent = Math.max(0, Math.min(100, Number.parseInt(match[2] ?? "", 10)));
|
|
222
|
-
if (!Number.isFinite(leftPercent)) {
|
|
223
|
-
return stylePromptText(segment, "muted");
|
|
224
|
-
}
|
|
225
|
-
const tone = quotaToneFromLeftPercent(leftPercent);
|
|
226
|
-
return `${stylePromptText(windowLabel, "muted")} ${stylePromptText(`${leftPercent}%`, tone)}`;
|
|
227
|
-
});
|
|
228
|
-
return joinStyledSegments(rendered);
|
|
229
|
-
}
|
|
230
|
-
// Exported for unit tests: tone precedence (danger before warning) is
|
|
231
|
-
// security/UX-relevant — a failure whose text contains "unavailable" must
|
|
232
|
-
// render red, not be downgraded to yellow. See test/codex-manager-detail-tone.test.ts.
|
|
233
|
-
export function styleAccountDetailText(detail, fallbackTone = "muted") {
|
|
234
|
-
const compact = collapseWhitespace(detail);
|
|
235
|
-
if (!compact)
|
|
236
|
-
return stylePromptText("", fallbackTone);
|
|
237
|
-
const quotaMatch = compact.match(/^(.*?)\(([^()]*\d{1,3}%[^()]*)\)(.*)$/);
|
|
238
|
-
if (quotaMatch) {
|
|
239
|
-
const prefix = (quotaMatch[1] ?? "").trim();
|
|
240
|
-
const quota = (quotaMatch[2] ?? "").trim();
|
|
241
|
-
const suffix = (quotaMatch[3] ?? "").trim();
|
|
242
|
-
// danger wins across the WHOLE detail: a failure keyword anywhere — even
|
|
243
|
-
// trapped inside the (…%) quota segment, e.g.
|
|
244
|
-
// "signed in and working (live check failed: … 0%)" — must keep the prefix
|
|
245
|
-
// red, never let a "working"/"ok" prefix render green over a real failure.
|
|
246
|
-
const detailHasFailure = /failed|error|rate-limited/i.test(compact);
|
|
247
|
-
const prefixTone = detailHasFailure
|
|
248
|
-
? "danger"
|
|
249
|
-
: /\b(ok|working|succeeded|valid)\b/i.test(prefix)
|
|
250
|
-
? "success"
|
|
251
|
-
: fallbackTone;
|
|
252
|
-
const suffixTone =
|
|
253
|
-
// danger wins first: a real failure whose text happens to contain
|
|
254
|
-
// "unavailable"/"not available" (e.g. a 5xx "service not available")
|
|
255
|
-
// must render red, not be downgraded to a yellow warning.
|
|
256
|
-
/failed|error/i.test(suffix)
|
|
257
|
-
? "danger"
|
|
258
|
-
: /re-login|stale|warning|retry|fallback|unavailable|not available/i.test(suffix)
|
|
259
|
-
? "warning"
|
|
260
|
-
: "muted";
|
|
261
|
-
const chunks = [];
|
|
262
|
-
if (prefix)
|
|
263
|
-
chunks.push(stylePromptText(prefix, prefixTone));
|
|
264
|
-
chunks.push(`(${styleQuotaSummary(quota)})`);
|
|
265
|
-
if (suffix)
|
|
266
|
-
chunks.push(stylePromptText(suffix, suffixTone));
|
|
267
|
-
return chunks.join(" ");
|
|
268
|
-
}
|
|
269
|
-
if (/rate-limited/i.test(compact))
|
|
270
|
-
return stylePromptText(compact, "danger");
|
|
271
|
-
if (/failed|error/i.test(compact))
|
|
272
|
-
return stylePromptText(compact, "danger");
|
|
273
|
-
if (/re-login|stale|warning|fallback|unavailable|not available/i.test(compact))
|
|
274
|
-
return stylePromptText(compact, "warning");
|
|
275
|
-
if (/\b(ok|working|succeeded|valid)\b/i.test(compact))
|
|
276
|
-
return stylePromptText(compact, "success");
|
|
277
|
-
return stylePromptText(compact, fallbackTone);
|
|
278
|
-
}
|
|
279
|
-
function riskTone(level) {
|
|
280
|
-
if (level === "low")
|
|
281
|
-
return "success";
|
|
282
|
-
if (level === "medium")
|
|
283
|
-
return "warning";
|
|
284
|
-
return "danger";
|
|
285
|
-
}
|
|
286
|
-
function availabilityTone(availability) {
|
|
287
|
-
if (availability === "ready")
|
|
288
|
-
return "success";
|
|
289
|
-
if (availability === "delayed")
|
|
290
|
-
return "warning";
|
|
291
|
-
return "danger";
|
|
292
|
-
}
|
|
293
|
-
function formatQuotaSnapshotForDashboard(snapshot, settings) {
|
|
294
|
-
if (!settings.showQuotaDetails)
|
|
295
|
-
return "live session OK";
|
|
296
|
-
return `live session OK (${formatCompactQuotaSnapshot(snapshot)})`;
|
|
297
|
-
}
|
|
298
|
-
function isAbortError(error) {
|
|
299
|
-
if (!(error instanceof Error))
|
|
300
|
-
return false;
|
|
301
|
-
const maybe = error;
|
|
302
|
-
return maybe.name === "AbortError" || maybe.code === "ABORT_ERR";
|
|
303
|
-
}
|
|
304
73
|
const IMPLEMENTED_FEATURES = [
|
|
305
74
|
{ id: 1, name: "Multi-account OAuth login dashboard" },
|
|
306
75
|
{ id: 2, name: "Account add/update dedupe by token/id/email" },
|
|
@@ -312,1599 +81,38 @@ const IMPLEMENTED_FEATURES = [
|
|
|
312
81
|
{ id: 8, name: "Flagged account restore flow" },
|
|
313
82
|
{ id: 9, name: "Best account forecast engine" },
|
|
314
83
|
{ id: 10, name: "Forecast live quota probing" },
|
|
315
|
-
{ id: 11, name: "Auto-fix command (safe mode)" },
|
|
316
|
-
{ id: 12, name: "Doctor diagnostics command" },
|
|
317
|
-
{ id: 13, name: "JSON outputs for machine automation" },
|
|
318
|
-
{ id: 14, name: "Report generation command" },
|
|
319
|
-
{ id: 15, name: "Storage v3 normalization and migration" },
|
|
320
|
-
{ id: 16, name: "Storage backup and recovery journal" },
|
|
321
|
-
{ id: 17, name: "Project-scoped and global storage paths" },
|
|
322
|
-
{ id: 18, name: "Quota cache storage" },
|
|
323
|
-
{ id: 19, name: "Live account sync watcher" },
|
|
324
|
-
{ id: 20, name: "Session affinity store" },
|
|
325
|
-
{ id: 21, name: "Refresh queue dedupe (in-process)" },
|
|
326
|
-
{ id: 22, name: "Refresh lease dedupe (cross-process)" },
|
|
327
|
-
{ id: 23, name: "Token rotation mapping in refresh queue" },
|
|
328
|
-
{ id: 24, name: "Refresh guardian (proactive refresh)" },
|
|
329
|
-
{ id: 25, name: "Preemptive quota scheduler" },
|
|
330
|
-
{ id: 26, name: "Entitlement cache for unsupported models" },
|
|
331
|
-
{ id: 27, name: "Capability policy scoring store" },
|
|
332
|
-
{ id: 28, name: "Failure policy evaluation module" },
|
|
333
|
-
{ id: 29, name: "Streaming failover pipeline" },
|
|
334
|
-
{ id: 30, name: "Rate-limit backoff and cooldown handling" },
|
|
335
|
-
{ id: 31, name: "Host request transformer bridge" },
|
|
336
|
-
{ id: 32, name: "Prompt template sync with cache" },
|
|
337
|
-
{ id: 33, name: "Codex CLI active-account state sync" },
|
|
338
|
-
{ id: 34, name: "TUI quick-switch hotkeys (1-9)" },
|
|
339
|
-
{ id: 35, name: "TUI search and help toggles" },
|
|
340
|
-
{ id: 36, name: "TUI account detail hotkeys (S/R/E/D)" },
|
|
341
|
-
{ id: 37, name: "TUI settings hub (list/summary/behavior/theme)" },
|
|
342
|
-
{ id: 38, name: "Dashboard display customization" },
|
|
343
|
-
{ id: 39, name: "Unified color/theme runtime (v2 UI)" },
|
|
344
|
-
{ id: 40, name: "OAuth browser-first flow with manual callback fallback" },
|
|
345
|
-
{ id: 41, name: "Auto-switch to best account command" },
|
|
346
|
-
];
|
|
347
|
-
function formatRateLimitEntry(account, now, family = "codex") {
|
|
348
|
-
return formatAccountRateLimitEntry(account, now, formatWaitTime, family);
|
|
349
|
-
}
|
|
350
|
-
function quotaCacheEntryToSnapshot(entry) {
|
|
351
|
-
return {
|
|
352
|
-
status: entry.status,
|
|
353
|
-
planType: entry.planType,
|
|
354
|
-
model: entry.model,
|
|
355
|
-
primary: {
|
|
356
|
-
usedPercent: entry.primary.usedPercent,
|
|
357
|
-
windowMinutes: entry.primary.windowMinutes,
|
|
358
|
-
resetAtMs: entry.primary.resetAtMs,
|
|
359
|
-
},
|
|
360
|
-
secondary: {
|
|
361
|
-
usedPercent: entry.secondary.usedPercent,
|
|
362
|
-
windowMinutes: entry.secondary.windowMinutes,
|
|
363
|
-
resetAtMs: entry.secondary.resetAtMs,
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function formatCompactQuotaWindowLabel(windowMinutes) {
|
|
368
|
-
if (!windowMinutes || !Number.isFinite(windowMinutes) || windowMinutes <= 0) {
|
|
369
|
-
return "quota";
|
|
370
|
-
}
|
|
371
|
-
if (windowMinutes % 1440 === 0)
|
|
372
|
-
return `${windowMinutes / 1440}d`;
|
|
373
|
-
if (windowMinutes % 60 === 0)
|
|
374
|
-
return `${windowMinutes / 60}h`;
|
|
375
|
-
return `${windowMinutes}m`;
|
|
376
|
-
}
|
|
377
|
-
function formatCompactQuotaPart(windowMinutes, usedPercent) {
|
|
378
|
-
const label = formatCompactQuotaWindowLabel(windowMinutes);
|
|
379
|
-
if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
const left = quotaLeftPercentFromUsed(usedPercent);
|
|
383
|
-
return `${label} ${left}%`;
|
|
384
|
-
}
|
|
385
|
-
function formatCompactQuotaSnapshot(snapshot, now = Date.now()) {
|
|
386
|
-
const parts = [
|
|
387
|
-
formatCompactQuotaPart(snapshot.primary.windowMinutes, snapshot.primary.usedPercent),
|
|
388
|
-
formatCompactQuotaPart(snapshot.secondary.windowMinutes, snapshot.secondary.usedPercent),
|
|
389
|
-
].filter((value) => typeof value === "string" && value.length > 0);
|
|
390
|
-
if (snapshot.status === 429) {
|
|
391
|
-
parts.push("rate-limited");
|
|
392
|
-
}
|
|
393
|
-
if (isQuotaCacheEntryExhausted(snapshot, now)) {
|
|
394
|
-
parts.push("quota-exhausted");
|
|
395
|
-
}
|
|
396
|
-
if (parts.length > 0) {
|
|
397
|
-
return parts.join(" | ");
|
|
398
|
-
}
|
|
399
|
-
return formatQuotaSnapshotLine(snapshot);
|
|
400
|
-
}
|
|
401
|
-
function formatAccountQuotaSummary(entry, now = Date.now()) {
|
|
402
|
-
const parts = [
|
|
403
|
-
formatCompactQuotaPart(entry.primary.windowMinutes, entry.primary.usedPercent),
|
|
404
|
-
formatCompactQuotaPart(entry.secondary.windowMinutes, entry.secondary.usedPercent),
|
|
405
|
-
].filter((value) => typeof value === "string" && value.length > 0);
|
|
406
|
-
if (entry.status === 429) {
|
|
407
|
-
parts.push("rate-limited");
|
|
408
|
-
}
|
|
409
|
-
if (isQuotaCacheEntryExhausted(entry, now)) {
|
|
410
|
-
parts.push("quota-exhausted");
|
|
411
|
-
}
|
|
412
|
-
if (parts.length > 0) {
|
|
413
|
-
return parts.join(" | ");
|
|
414
|
-
}
|
|
415
|
-
return formatQuotaSnapshotLine(quotaCacheEntryToSnapshot(entry));
|
|
416
|
-
}
|
|
417
|
-
function getQuotaCacheEntryForAccount(cache, account, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
418
|
-
const accountId = normalizeQuotaAccountId(account.accountId);
|
|
419
|
-
if (accountId &&
|
|
420
|
-
hasUniqueQuotaAccountId(accounts, account) &&
|
|
421
|
-
cache.byAccountId[accountId]) {
|
|
422
|
-
return cache.byAccountId[accountId] ?? null;
|
|
423
|
-
}
|
|
424
|
-
const email = normalizeQuotaEmail(account.email);
|
|
425
|
-
if (email &&
|
|
426
|
-
hasSafeQuotaEmailFallback(emailFallbackState, account) &&
|
|
427
|
-
cache.byEmail[email]) {
|
|
428
|
-
return cache.byEmail[email] ?? null;
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
function getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
433
|
-
const cachedEntry = cache
|
|
434
|
-
? getQuotaCacheEntryForAccount(cache, account, accounts, emailFallbackState)
|
|
435
|
-
: null;
|
|
436
|
-
const persistedResetAt = getRateLimitResetTimeForFamily(account, now, "codex");
|
|
437
|
-
if (typeof persistedResetAt !== "number") {
|
|
438
|
-
return cachedEntry;
|
|
439
|
-
}
|
|
440
|
-
const cachedPrimaryResetAt = cachedEntry?.primary.resetAtMs ?? 0;
|
|
441
|
-
const cachedSecondaryResetAt = cachedEntry?.secondary.resetAtMs ?? 0;
|
|
442
|
-
if (cachedEntry?.status === 429 &&
|
|
443
|
-
Math.max(cachedPrimaryResetAt, cachedSecondaryResetAt) >= persistedResetAt) {
|
|
444
|
-
return cachedEntry;
|
|
445
|
-
}
|
|
446
|
-
return {
|
|
447
|
-
updatedAt: cachedEntry?.updatedAt ?? now,
|
|
448
|
-
status: 429,
|
|
449
|
-
model: cachedEntry?.model ?? DEFAULT_MODEL,
|
|
450
|
-
planType: cachedEntry?.planType,
|
|
451
|
-
primary: {
|
|
452
|
-
...cachedEntry?.primary,
|
|
453
|
-
resetAtMs: Math.max(cachedPrimaryResetAt, persistedResetAt),
|
|
454
|
-
},
|
|
455
|
-
secondary: cachedEntry?.secondary ?? {},
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function updateQuotaCacheForAccount(cache, account, snapshot, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
459
|
-
const nextEntry = {
|
|
460
|
-
updatedAt: Date.now(),
|
|
461
|
-
status: snapshot.status,
|
|
462
|
-
model: snapshot.model,
|
|
463
|
-
planType: snapshot.planType,
|
|
464
|
-
primary: {
|
|
465
|
-
usedPercent: snapshot.primary.usedPercent,
|
|
466
|
-
windowMinutes: snapshot.primary.windowMinutes,
|
|
467
|
-
resetAtMs: snapshot.primary.resetAtMs,
|
|
468
|
-
},
|
|
469
|
-
secondary: {
|
|
470
|
-
usedPercent: snapshot.secondary.usedPercent,
|
|
471
|
-
windowMinutes: snapshot.secondary.windowMinutes,
|
|
472
|
-
resetAtMs: snapshot.secondary.resetAtMs,
|
|
473
|
-
},
|
|
474
|
-
};
|
|
475
|
-
let changed = false;
|
|
476
|
-
const accountId = normalizeQuotaAccountId(account.accountId);
|
|
477
|
-
const hasUniqueAccountId = accountId !== null && hasUniqueQuotaAccountId(accounts, account);
|
|
478
|
-
if (hasUniqueAccountId) {
|
|
479
|
-
cache.byAccountId[accountId] = nextEntry;
|
|
480
|
-
changed = true;
|
|
481
|
-
}
|
|
482
|
-
const email = normalizeQuotaEmail(account.email);
|
|
483
|
-
if (email &&
|
|
484
|
-
hasSafeQuotaEmailFallback(emailFallbackState, account) &&
|
|
485
|
-
!hasUniqueAccountId) {
|
|
486
|
-
cache.byEmail[email] = nextEntry;
|
|
487
|
-
changed = true;
|
|
488
|
-
}
|
|
489
|
-
else if (email && cache.byEmail[email]) {
|
|
490
|
-
delete cache.byEmail[email];
|
|
491
|
-
changed = true;
|
|
492
|
-
}
|
|
493
|
-
return changed;
|
|
494
|
-
}
|
|
495
|
-
function cloneQuotaCacheData(cache) {
|
|
496
|
-
// Shallow spreading is safe because quota cache entries are always replaced,
|
|
497
|
-
// never mutated in-place.
|
|
498
|
-
return {
|
|
499
|
-
byAccountId: { ...cache.byAccountId },
|
|
500
|
-
byEmail: { ...cache.byEmail },
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
function pruneUnsafeQuotaEmailCacheEntry(cache, email, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
504
|
-
const normalizedEmail = normalizeQuotaEmail(email);
|
|
505
|
-
if (!normalizedEmail || !cache.byEmail[normalizedEmail]) {
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
const hasSafeFallbackAccount = accounts.some((account) => normalizeQuotaEmail(account.email) === normalizedEmail &&
|
|
509
|
-
hasSafeQuotaEmailFallback(emailFallbackState, account));
|
|
510
|
-
if (hasSafeFallbackAccount) {
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
delete cache.byEmail[normalizedEmail];
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
const DEFAULT_MENU_QUOTA_REFRESH_TTL_MS = 5 * 60_000;
|
|
517
|
-
const DEFAULT_LIVE_PROBE_MODEL = DEFAULT_MODEL;
|
|
518
|
-
const MENU_QUOTA_REFRESH_MODEL = DEFAULT_LIVE_PROBE_MODEL;
|
|
519
|
-
function resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, accounts, emailFallbackState = buildQuotaEmailFallbackState(accounts)) {
|
|
520
|
-
if (account.enabled === false)
|
|
521
|
-
return null;
|
|
522
|
-
if (!hasUsableAccessToken(account, now))
|
|
523
|
-
return null;
|
|
524
|
-
const existing = getPersistedQuotaViewForAccount(cache, account, accounts, now, emailFallbackState);
|
|
525
|
-
if (existing &&
|
|
526
|
-
typeof existing.updatedAt === "number" &&
|
|
527
|
-
Number.isFinite(existing.updatedAt) &&
|
|
528
|
-
now - existing.updatedAt < maxAgeMs) {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
// Menu auto-refresh is cache-backed, so only probe when the result can be
|
|
532
|
-
// written behind a safe lookup key for later reuse.
|
|
533
|
-
const canStore = (normalizeQuotaAccountId(account.accountId) !== null &&
|
|
534
|
-
hasUniqueQuotaAccountId(accounts, account)) ||
|
|
535
|
-
hasSafeQuotaEmailFallback(emailFallbackState, account);
|
|
536
|
-
if (!canStore)
|
|
537
|
-
return null;
|
|
538
|
-
const accessToken = account.accessToken;
|
|
539
|
-
const accountId = accessToken
|
|
540
|
-
? (account.accountId ?? extractAccountId(accessToken))
|
|
541
|
-
: account.accountId;
|
|
542
|
-
if (!accountId || !accessToken)
|
|
543
|
-
return null;
|
|
544
|
-
return { accountId, accessToken };
|
|
545
|
-
}
|
|
546
|
-
function collectMenuQuotaRefreshTargets(storage, cache, maxAgeMs, now = Date.now(), emailFallbackState = buildQuotaEmailFallbackState(storage.accounts)) {
|
|
547
|
-
const targets = [];
|
|
548
|
-
for (const account of storage.accounts) {
|
|
549
|
-
const probeInput = resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, storage.accounts, emailFallbackState);
|
|
550
|
-
if (!probeInput)
|
|
551
|
-
continue;
|
|
552
|
-
targets.push({
|
|
553
|
-
account,
|
|
554
|
-
accountId: probeInput.accountId,
|
|
555
|
-
accessToken: probeInput.accessToken,
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
return targets;
|
|
559
|
-
}
|
|
560
|
-
function countMenuQuotaRefreshTargets(storage, cache, maxAgeMs, now = Date.now()) {
|
|
561
|
-
const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
|
|
562
|
-
let count = 0;
|
|
563
|
-
for (const account of storage.accounts) {
|
|
564
|
-
if (resolveMenuQuotaProbeInput(account, cache, maxAgeMs, now, storage.accounts, emailFallbackState)) {
|
|
565
|
-
count += 1;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return count;
|
|
569
|
-
}
|
|
570
|
-
async function refreshQuotaCacheForMenu(storage, cache, maxAgeMs, onProgress) {
|
|
571
|
-
if (storage.accounts.length === 0) {
|
|
572
|
-
return cache;
|
|
573
|
-
}
|
|
574
|
-
const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
|
|
575
|
-
const nextCache = cloneQuotaCacheData(cache);
|
|
576
|
-
const now = Date.now();
|
|
577
|
-
const targets = collectMenuQuotaRefreshTargets(storage, nextCache, maxAgeMs, now, emailFallbackState);
|
|
578
|
-
const total = targets.length;
|
|
579
|
-
let processed = 0;
|
|
580
|
-
onProgress?.(processed, total);
|
|
581
|
-
let changed = false;
|
|
582
|
-
for (const target of targets) {
|
|
583
|
-
processed += 1;
|
|
584
|
-
onProgress?.(processed, total);
|
|
585
|
-
try {
|
|
586
|
-
const snapshot = await fetchCodexQuotaSnapshot({
|
|
587
|
-
accountId: target.accountId,
|
|
588
|
-
accessToken: target.accessToken,
|
|
589
|
-
model: MENU_QUOTA_REFRESH_MODEL,
|
|
590
|
-
});
|
|
591
|
-
changed =
|
|
592
|
-
updateQuotaCacheForAccount(nextCache, target.account, snapshot, storage.accounts, emailFallbackState) || changed;
|
|
593
|
-
}
|
|
594
|
-
catch {
|
|
595
|
-
// Keep existing cached values if probing fails.
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (changed) {
|
|
599
|
-
await saveQuotaCache(nextCache);
|
|
600
|
-
}
|
|
601
|
-
return nextCache;
|
|
602
|
-
}
|
|
603
|
-
const ACCESS_TOKEN_FRESH_WINDOW_MS = 5 * 60 * 1000;
|
|
604
|
-
function hasUsableAccessToken(account, now) {
|
|
605
|
-
if (!account.accessToken)
|
|
606
|
-
return false;
|
|
607
|
-
if (typeof account.expiresAt !== "number" ||
|
|
608
|
-
!Number.isFinite(account.expiresAt))
|
|
609
|
-
return false;
|
|
610
|
-
return account.expiresAt - now > ACCESS_TOKEN_FRESH_WINDOW_MS;
|
|
611
|
-
}
|
|
612
|
-
function hasLikelyInvalidRefreshToken(refreshToken) {
|
|
613
|
-
if (!refreshToken)
|
|
614
|
-
return true;
|
|
615
|
-
const trimmed = refreshToken.trim();
|
|
616
|
-
if (trimmed.length < 20)
|
|
617
|
-
return true;
|
|
618
|
-
return trimmed.startsWith("token-");
|
|
619
|
-
}
|
|
620
|
-
function mapAccountStatus(account, isCurrentAccount, now, persistedQuotaEntry) {
|
|
621
|
-
if (account.enabled === false)
|
|
622
|
-
return "disabled";
|
|
623
|
-
if (typeof account.coolingDownUntil === "number" &&
|
|
624
|
-
account.coolingDownUntil > now) {
|
|
625
|
-
return "cooldown";
|
|
626
|
-
}
|
|
627
|
-
if (persistedQuotaEntry && isQuotaCacheEntryExhausted(persistedQuotaEntry, now)) {
|
|
628
|
-
return "quota-exhausted";
|
|
629
|
-
}
|
|
630
|
-
if (persistedQuotaEntry?.status === 429)
|
|
631
|
-
return "rate-limited";
|
|
632
|
-
const rateLimit = formatRateLimitEntry(account, now, "codex");
|
|
633
|
-
if (rateLimit)
|
|
634
|
-
return "rate-limited";
|
|
635
|
-
if (isCurrentAccount)
|
|
636
|
-
return "active";
|
|
637
|
-
return "ok";
|
|
638
|
-
}
|
|
639
|
-
function parseLeftPercentFromQuotaSummary(summary, windowLabel) {
|
|
640
|
-
if (!summary)
|
|
641
|
-
return -1;
|
|
642
|
-
const match = summary.match(new RegExp(`(?:^|\\|)\\s*${windowLabel}\\s+(\\d{1,3})%`, "i"));
|
|
643
|
-
const value = Number.parseInt(match?.[1] ?? "", 10);
|
|
644
|
-
if (!Number.isFinite(value))
|
|
645
|
-
return -1;
|
|
646
|
-
return Math.max(0, Math.min(100, value));
|
|
647
|
-
}
|
|
648
|
-
function readQuotaLeftPercent(account, windowLabel) {
|
|
649
|
-
const direct = windowLabel === "5h"
|
|
650
|
-
? account.quota5hLeftPercent
|
|
651
|
-
: account.quota7dLeftPercent;
|
|
652
|
-
if (typeof direct === "number" && Number.isFinite(direct)) {
|
|
653
|
-
return Math.max(0, Math.min(100, Math.round(direct)));
|
|
654
|
-
}
|
|
655
|
-
return parseLeftPercentFromQuotaSummary(account.quotaSummary, windowLabel);
|
|
656
|
-
}
|
|
657
|
-
function readQuotaFloorPercent(account) {
|
|
658
|
-
return Math.min(readQuotaLeftPercent(account, "5h"), readQuotaLeftPercent(account, "7d"));
|
|
659
|
-
}
|
|
660
|
-
function accountStatusSortBucket(status) {
|
|
661
|
-
switch (status) {
|
|
662
|
-
case "active":
|
|
663
|
-
case "ok":
|
|
664
|
-
return 0;
|
|
665
|
-
case "unknown":
|
|
666
|
-
return 1;
|
|
667
|
-
case "quota-exhausted":
|
|
668
|
-
case "cooldown":
|
|
669
|
-
case "rate-limited":
|
|
670
|
-
return 2;
|
|
671
|
-
case "disabled":
|
|
672
|
-
case "error":
|
|
673
|
-
case "flagged":
|
|
674
|
-
return 3;
|
|
675
|
-
default:
|
|
676
|
-
return 1;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
function accountReadinessSortBucket(account) {
|
|
680
|
-
const statusBucket = accountStatusSortBucket(account.status);
|
|
681
|
-
if (statusBucket >= 2)
|
|
682
|
-
return statusBucket;
|
|
683
|
-
return account.quotaRateLimited || account.quotaExhausted ? 2 : statusBucket;
|
|
684
|
-
}
|
|
685
|
-
function compareReadyFirstAccounts(left, right) {
|
|
686
|
-
const bucketDelta = accountReadinessSortBucket(left) - accountReadinessSortBucket(right);
|
|
687
|
-
if (bucketDelta !== 0)
|
|
688
|
-
return bucketDelta;
|
|
689
|
-
const leftFloor = readQuotaFloorPercent(left);
|
|
690
|
-
const rightFloor = readQuotaFloorPercent(right);
|
|
691
|
-
if (leftFloor !== rightFloor)
|
|
692
|
-
return rightFloor - leftFloor;
|
|
693
|
-
const left5h = readQuotaLeftPercent(left, "5h");
|
|
694
|
-
const right5h = readQuotaLeftPercent(right, "5h");
|
|
695
|
-
if (left5h !== right5h)
|
|
696
|
-
return right5h - left5h;
|
|
697
|
-
const left7d = readQuotaLeftPercent(left, "7d");
|
|
698
|
-
const right7d = readQuotaLeftPercent(right, "7d");
|
|
699
|
-
if (left7d !== right7d)
|
|
700
|
-
return right7d - left7d;
|
|
701
|
-
const leftLastUsed = left.lastUsed ?? 0;
|
|
702
|
-
const rightLastUsed = right.lastUsed ?? 0;
|
|
703
|
-
if (leftLastUsed !== rightLastUsed)
|
|
704
|
-
return rightLastUsed - leftLastUsed;
|
|
705
|
-
const leftSource = left.sourceIndex ?? left.index;
|
|
706
|
-
const rightSource = right.sourceIndex ?? right.index;
|
|
707
|
-
return leftSource - rightSource;
|
|
708
|
-
}
|
|
709
|
-
function applyAccountMenuOrdering(accounts, displaySettings) {
|
|
710
|
-
const sortEnabled = displaySettings.menuSortEnabled ??
|
|
711
|
-
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
|
|
712
|
-
true;
|
|
713
|
-
const sortMode = displaySettings.menuSortMode ??
|
|
714
|
-
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
|
|
715
|
-
"ready-first";
|
|
716
|
-
if (!sortEnabled || sortMode !== "ready-first") {
|
|
717
|
-
return [...accounts];
|
|
718
|
-
}
|
|
719
|
-
const sorted = [...accounts].sort(compareReadyFirstAccounts);
|
|
720
|
-
const pinCurrent = displaySettings.menuSortPinCurrent ??
|
|
721
|
-
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortPinCurrent ??
|
|
722
|
-
false;
|
|
723
|
-
if (pinCurrent) {
|
|
724
|
-
const currentIndex = sorted.findIndex((account) => account.isCurrentAccount);
|
|
725
|
-
if (currentIndex > 0) {
|
|
726
|
-
const current = sorted.splice(currentIndex, 1)[0];
|
|
727
|
-
const first = sorted[0];
|
|
728
|
-
if (current && first && compareReadyFirstAccounts(current, first) <= 0) {
|
|
729
|
-
sorted.unshift(current);
|
|
730
|
-
}
|
|
731
|
-
else if (current) {
|
|
732
|
-
sorted.splice(currentIndex, 0, current);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return sorted;
|
|
737
|
-
}
|
|
738
|
-
function toExistingAccountInfo(storage, quotaCache, displaySettings, runtimeCurrent = null) {
|
|
739
|
-
const now = Date.now();
|
|
740
|
-
const activeIndex = resolveActiveIndex(storage, "codex");
|
|
741
|
-
const layoutMode = resolveMenuLayoutMode(displaySettings);
|
|
742
|
-
const emailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
|
|
743
|
-
const baseAccounts = storage.accounts.map((account, index) => {
|
|
744
|
-
const entry = getPersistedQuotaViewForAccount(quotaCache, account, storage.accounts, now, emailFallbackState);
|
|
745
|
-
const currentMarkers = resolveAccountCurrentMarkers(index, activeIndex, runtimeCurrent);
|
|
746
|
-
const isCurrentAccount = isDisplayCurrentAccount(index, activeIndex, runtimeCurrent);
|
|
747
|
-
const quotaExhausted = entry ? isQuotaCacheEntryExhausted(entry, now) : false;
|
|
748
|
-
return {
|
|
749
|
-
index,
|
|
750
|
-
sourceIndex: index,
|
|
751
|
-
accountId: account.accountId,
|
|
752
|
-
accountLabel: account.accountLabel,
|
|
753
|
-
email: account.email,
|
|
754
|
-
addedAt: account.addedAt,
|
|
755
|
-
lastUsed: account.lastUsed,
|
|
756
|
-
status: mapAccountStatus(account, isCurrentAccount, now, entry),
|
|
757
|
-
quotaSummary: (displaySettings.menuShowQuotaSummary ?? true) && entry
|
|
758
|
-
? formatAccountQuotaSummary(entry, now)
|
|
759
|
-
: undefined,
|
|
760
|
-
quota5hLeftPercent: quotaLeftPercentFromUsed(entry?.primary.usedPercent),
|
|
761
|
-
quota5hResetAtMs: entry?.primary.resetAtMs,
|
|
762
|
-
quota7dLeftPercent: quotaLeftPercentFromUsed(entry?.secondary.usedPercent),
|
|
763
|
-
quota7dResetAtMs: entry?.secondary.resetAtMs,
|
|
764
|
-
quotaRateLimited: entry?.status === 429,
|
|
765
|
-
quotaExhausted,
|
|
766
|
-
isCurrentAccount,
|
|
767
|
-
isDefaultAccount: index === activeIndex,
|
|
768
|
-
isRuntimeCurrentAccount: runtimeCurrent?.index === index,
|
|
769
|
-
currentMarkers,
|
|
770
|
-
enabled: account.enabled !== false,
|
|
771
|
-
showStatusBadge: displaySettings.menuShowStatusBadge ?? true,
|
|
772
|
-
showCurrentBadge: displaySettings.menuShowCurrentBadge ?? true,
|
|
773
|
-
showLastUsed: displaySettings.menuShowLastUsed ?? true,
|
|
774
|
-
showQuotaCooldown: displaySettings.menuShowQuotaCooldown ?? true,
|
|
775
|
-
showHintsForUnselectedRows: layoutMode === "expanded-rows",
|
|
776
|
-
highlightCurrentRow: displaySettings.menuHighlightCurrentRow ?? true,
|
|
777
|
-
focusStyle: displaySettings.menuFocusStyle ?? "row-invert",
|
|
778
|
-
statuslineFields: displaySettings.menuStatuslineFields ?? [
|
|
779
|
-
"last-used",
|
|
780
|
-
"limits",
|
|
781
|
-
"status",
|
|
782
|
-
],
|
|
783
|
-
};
|
|
784
|
-
});
|
|
785
|
-
const orderedAccounts = applyAccountMenuOrdering(baseAccounts, displaySettings);
|
|
786
|
-
const quickSwitchUsesVisibleRows = displaySettings.menuSortQuickSwitchVisibleRow ?? true;
|
|
787
|
-
return orderedAccounts.map((account, displayIndex) => ({
|
|
788
|
-
...account,
|
|
789
|
-
index: displayIndex,
|
|
790
|
-
quickSwitchNumber: quickSwitchUsesVisibleRows
|
|
791
|
-
? displayIndex + 1
|
|
792
|
-
: (account.sourceIndex ?? displayIndex) + 1,
|
|
793
|
-
}));
|
|
794
|
-
}
|
|
795
|
-
async function loadRuntimeCurrentSelectionForStorage(storage, now = Date.now()) {
|
|
796
|
-
const [runtimeSnapshot, appBindStatus] = await Promise.all([
|
|
797
|
-
loadPersistedRuntimeObservabilitySnapshot().catch(() => null),
|
|
798
|
-
getAppBindStatus()
|
|
799
|
-
.then((status) => (status.running ? status.router : null))
|
|
800
|
-
.catch(() => null),
|
|
801
|
-
]);
|
|
802
|
-
return resolveRuntimeCurrentAccount(storage, {
|
|
803
|
-
runtimeSnapshot,
|
|
804
|
-
appBindStatus,
|
|
805
|
-
appHelperStatus: readAppRuntimeHelperAccountSignal(),
|
|
806
|
-
}, { now });
|
|
807
|
-
}
|
|
808
|
-
function activeAccountMatchesCodexCliState(account, state) {
|
|
809
|
-
if (!state)
|
|
810
|
-
return true;
|
|
811
|
-
const accountId = account.accountId?.trim();
|
|
812
|
-
const activeAccountId = state.activeAccountId?.trim();
|
|
813
|
-
if (accountId && activeAccountId) {
|
|
814
|
-
return accountId === activeAccountId;
|
|
815
|
-
}
|
|
816
|
-
const email = sanitizeEmail(account.email);
|
|
817
|
-
const activeEmail = sanitizeEmail(state.activeEmail);
|
|
818
|
-
if (email && activeEmail) {
|
|
819
|
-
return email === activeEmail;
|
|
820
|
-
}
|
|
821
|
-
return false;
|
|
822
|
-
}
|
|
823
|
-
async function syncCodexCliActiveSelectionIfDrifted(storage) {
|
|
824
|
-
const activeIndex = resolveActiveIndex(storage, "codex");
|
|
825
|
-
if (activeIndex < 0 || activeIndex >= storage.accounts.length) {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
const account = storage.accounts[activeIndex];
|
|
829
|
-
if (!account) {
|
|
830
|
-
return false;
|
|
831
|
-
}
|
|
832
|
-
try {
|
|
833
|
-
const cliState = await loadCodexCliState({ forceRefresh: true });
|
|
834
|
-
if (!cliState || activeAccountMatchesCodexCliState(account, cliState)) {
|
|
835
|
-
return false;
|
|
836
|
-
}
|
|
837
|
-
return setCodexCliActiveSelection({
|
|
838
|
-
accountId: account.accountId,
|
|
839
|
-
email: account.email,
|
|
840
|
-
accessToken: account.accessToken,
|
|
841
|
-
refreshToken: account.refreshToken,
|
|
842
|
-
expiresAt: account.expiresAt,
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
return false;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Resolve the account-id selection for freshly-minted tokens.
|
|
851
|
-
*
|
|
852
|
-
* The org-override precedence (explicit `login --org` wins over the ambient
|
|
853
|
-
* CODEX_AUTH_ACCOUNT_ID env, for this call only) lives in the internal
|
|
854
|
-
* lib/auth/org-override.ts module so it can be unit-tested without exporting this
|
|
855
|
-
* CLI-internal function. Threading the org as a parameter avoids mutating
|
|
856
|
-
* process.env for the duration of a login, which raced on concurrent re-entry.
|
|
857
|
-
*/
|
|
858
|
-
function resolveAccountSelection(tokens, orgOverride) {
|
|
859
|
-
const override = resolveOrgOverride(orgOverride);
|
|
860
|
-
if (override) {
|
|
861
|
-
return {
|
|
862
|
-
...tokens,
|
|
863
|
-
accountIdOverride: override,
|
|
864
|
-
accountIdSource: "manual",
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
const candidates = getAccountIdCandidates(tokens.access, tokens.idToken);
|
|
868
|
-
if (candidates.length === 0) {
|
|
869
|
-
return tokens;
|
|
870
|
-
}
|
|
871
|
-
if (candidates.length === 1) {
|
|
872
|
-
const [candidate] = candidates;
|
|
873
|
-
if (candidate) {
|
|
874
|
-
return {
|
|
875
|
-
...tokens,
|
|
876
|
-
accountIdOverride: candidate.accountId,
|
|
877
|
-
accountIdSource: candidate.source,
|
|
878
|
-
accountLabel: candidate.label,
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
const best = selectBestAccountCandidate(candidates);
|
|
883
|
-
if (!best) {
|
|
884
|
-
return tokens;
|
|
885
|
-
}
|
|
886
|
-
return {
|
|
887
|
-
...tokens,
|
|
888
|
-
accountIdOverride: best.accountId,
|
|
889
|
-
accountIdSource: best.source ?? "token",
|
|
890
|
-
accountLabel: best.label,
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
function resolveStoredAccountIdentity(storedAccountId, storedAccountIdSource, tokenAccountId) {
|
|
894
|
-
const accountId = resolveRequestAccountId(storedAccountId, storedAccountIdSource, tokenAccountId);
|
|
895
|
-
if (!accountId) {
|
|
896
|
-
return {};
|
|
897
|
-
}
|
|
898
|
-
if (!shouldUpdateAccountIdFromToken(storedAccountIdSource, storedAccountId)) {
|
|
899
|
-
return {
|
|
900
|
-
accountId,
|
|
901
|
-
accountIdSource: storedAccountIdSource,
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
return {
|
|
905
|
-
accountId,
|
|
906
|
-
accountIdSource: accountId === tokenAccountId ? "token" : storedAccountIdSource,
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
function applyTokenAccountIdentity(account, tokenAccountId) {
|
|
910
|
-
const nextIdentity = resolveStoredAccountIdentity(account.accountId, account.accountIdSource, tokenAccountId);
|
|
911
|
-
if (!nextIdentity.accountId) {
|
|
912
|
-
return false;
|
|
913
|
-
}
|
|
914
|
-
if (nextIdentity.accountId === account.accountId &&
|
|
915
|
-
nextIdentity.accountIdSource === account.accountIdSource) {
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
account.accountId = nextIdentity.accountId;
|
|
919
|
-
account.accountIdSource = nextIdentity.accountIdSource;
|
|
920
|
-
return true;
|
|
921
|
-
}
|
|
922
|
-
async function promptManualCallback(state, options = {}) {
|
|
923
|
-
const useInteractivePrompt = input.isTTY && output.isTTY;
|
|
924
|
-
if (!useInteractivePrompt && !options.allowNonTty) {
|
|
925
|
-
return null;
|
|
926
|
-
}
|
|
927
|
-
const rl = createInterface({ input, output });
|
|
928
|
-
try {
|
|
929
|
-
if (useInteractivePrompt) {
|
|
930
|
-
console.log("");
|
|
931
|
-
console.log(stylePromptText(UI_COPY.oauth.pastePrompt, "accent"));
|
|
932
|
-
}
|
|
933
|
-
const answer = useInteractivePrompt
|
|
934
|
-
? await rl.question("◆ ")
|
|
935
|
-
: await new Promise((resolve, reject) => {
|
|
936
|
-
if (input.readableEnded || input.destroyed) {
|
|
937
|
-
resolve(null);
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
let settled = false;
|
|
941
|
-
const handleInputClosed = () => {
|
|
942
|
-
if (settled)
|
|
943
|
-
return;
|
|
944
|
-
settled = true;
|
|
945
|
-
input.off("end", handleInputClosed);
|
|
946
|
-
input.off("close", handleInputClosed);
|
|
947
|
-
resolve(null);
|
|
948
|
-
};
|
|
949
|
-
const finish = (value) => {
|
|
950
|
-
if (settled)
|
|
951
|
-
return;
|
|
952
|
-
settled = true;
|
|
953
|
-
input.off("end", handleInputClosed);
|
|
954
|
-
input.off("close", handleInputClosed);
|
|
955
|
-
resolve(value);
|
|
956
|
-
};
|
|
957
|
-
const fail = (error) => {
|
|
958
|
-
if (settled)
|
|
959
|
-
return;
|
|
960
|
-
settled = true;
|
|
961
|
-
input.off("end", handleInputClosed);
|
|
962
|
-
input.off("close", handleInputClosed);
|
|
963
|
-
reject(error);
|
|
964
|
-
};
|
|
965
|
-
rl.question("")
|
|
966
|
-
.then((value) => finish(value))
|
|
967
|
-
.catch((error) => {
|
|
968
|
-
if (isAbortError(error) || isReadlineClosedError(error)) {
|
|
969
|
-
handleInputClosed();
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
fail(error);
|
|
973
|
-
});
|
|
974
|
-
input.once("end", handleInputClosed);
|
|
975
|
-
input.once("close", handleInputClosed);
|
|
976
|
-
});
|
|
977
|
-
if (answer === null) {
|
|
978
|
-
return null;
|
|
979
|
-
}
|
|
980
|
-
if (answer.includes("\u001b")) {
|
|
981
|
-
return null;
|
|
982
|
-
}
|
|
983
|
-
const normalized = answer.trim().toLowerCase();
|
|
984
|
-
if (normalized.length === 0 ||
|
|
985
|
-
normalized === "q" ||
|
|
986
|
-
normalized === "quit" ||
|
|
987
|
-
normalized === "cancel" ||
|
|
988
|
-
normalized === "back") {
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
const parsed = parseAuthorizationInput(answer);
|
|
992
|
-
if (!parsed.code || !parsed.state)
|
|
993
|
-
return null;
|
|
994
|
-
if (parsed.state !== state)
|
|
995
|
-
return null;
|
|
996
|
-
return parsed.code;
|
|
997
|
-
}
|
|
998
|
-
catch (error) {
|
|
999
|
-
if (isAbortError(error) || isReadlineClosedError(error)) {
|
|
1000
|
-
return null;
|
|
1001
|
-
}
|
|
1002
|
-
throw error;
|
|
1003
|
-
}
|
|
1004
|
-
finally {
|
|
1005
|
-
rl.close();
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
function isReadlineClosedError(error) {
|
|
1009
|
-
if (!(error instanceof Error)) {
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
const errorCode = typeof error === "object" && error !== null && "code" in error
|
|
1013
|
-
? String(error.code)
|
|
1014
|
-
: "";
|
|
1015
|
-
return (errorCode === "ERR_USE_AFTER_CLOSE" ||
|
|
1016
|
-
/readline was closed/i.test(error.message));
|
|
1017
|
-
}
|
|
1018
|
-
export function formatBackupSavedAt(mtimeMs) {
|
|
1019
|
-
return new Date(mtimeMs).toLocaleString(undefined, {
|
|
1020
|
-
month: "short",
|
|
1021
|
-
day: "numeric",
|
|
1022
|
-
year: "numeric",
|
|
1023
|
-
hour: "numeric",
|
|
1024
|
-
minute: "2-digit",
|
|
1025
|
-
});
|
|
1026
|
-
}
|
|
1027
|
-
async function promptOAuthSignInMode(backupOption, backupDiscoveryWarning = null) {
|
|
1028
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1029
|
-
return "browser";
|
|
1030
|
-
}
|
|
1031
|
-
const ui = getUiRuntimeOptions();
|
|
1032
|
-
const items = [
|
|
1033
|
-
{
|
|
1034
|
-
label: UI_COPY.oauth.signInHeading,
|
|
1035
|
-
value: "cancel",
|
|
1036
|
-
kind: "heading",
|
|
1037
|
-
},
|
|
1038
|
-
{ label: UI_COPY.oauth.openBrowser, value: "browser", color: "green" },
|
|
1039
|
-
{ label: UI_COPY.oauth.manualMode, value: "manual", color: "yellow" },
|
|
1040
|
-
...(backupOption
|
|
1041
|
-
? [
|
|
1042
|
-
{ separator: true, label: "", value: "cancel" },
|
|
1043
|
-
{
|
|
1044
|
-
label: UI_COPY.oauth.restoreHeading,
|
|
1045
|
-
value: "cancel",
|
|
1046
|
-
kind: "heading",
|
|
1047
|
-
},
|
|
1048
|
-
{
|
|
1049
|
-
label: UI_COPY.oauth.restoreSavedBackup,
|
|
1050
|
-
value: "restore-backup",
|
|
1051
|
-
hint: UI_COPY.oauth.loadLastBackupHint(backupOption.fileName, backupOption.accountCount, formatBackupSavedAt(backupOption.mtimeMs)),
|
|
1052
|
-
color: "cyan",
|
|
1053
|
-
},
|
|
1054
|
-
]
|
|
1055
|
-
: []),
|
|
1056
|
-
{ separator: true, label: "", value: "cancel" },
|
|
1057
|
-
{ label: UI_COPY.oauth.back, value: "cancel", color: "red" },
|
|
1058
|
-
];
|
|
1059
|
-
const selected = await select(items, {
|
|
1060
|
-
message: UI_COPY.oauth.chooseModeTitle,
|
|
1061
|
-
subtitle: backupDiscoveryWarning
|
|
1062
|
-
? `${UI_COPY.oauth.chooseModeSubtitle} ${backupDiscoveryWarning}`
|
|
1063
|
-
: UI_COPY.oauth.chooseModeSubtitle,
|
|
1064
|
-
help: backupOption
|
|
1065
|
-
? UI_COPY.oauth.chooseModeHelpWithBackup
|
|
1066
|
-
: UI_COPY.oauth.chooseModeHelp,
|
|
1067
|
-
clearScreen: true,
|
|
1068
|
-
theme: ui.theme,
|
|
1069
|
-
selectedEmphasis: "minimal",
|
|
1070
|
-
allowEscape: false,
|
|
1071
|
-
onInput: (raw) => {
|
|
1072
|
-
const lower = raw.toLowerCase();
|
|
1073
|
-
if (lower === "q")
|
|
1074
|
-
return "cancel";
|
|
1075
|
-
if (lower === "1")
|
|
1076
|
-
return "browser";
|
|
1077
|
-
if (lower === "2")
|
|
1078
|
-
return "manual";
|
|
1079
|
-
if (lower === "3" && backupOption)
|
|
1080
|
-
return "restore-backup";
|
|
1081
|
-
return undefined;
|
|
1082
|
-
},
|
|
1083
|
-
});
|
|
1084
|
-
return selected ?? "cancel";
|
|
1085
|
-
}
|
|
1086
|
-
async function promptBackupRestoreMode(latestBackup) {
|
|
1087
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1088
|
-
return "latest";
|
|
1089
|
-
}
|
|
1090
|
-
const ui = getUiRuntimeOptions();
|
|
1091
|
-
const items = [
|
|
1092
|
-
{
|
|
1093
|
-
label: UI_COPY.oauth.loadLastBackup,
|
|
1094
|
-
value: "latest",
|
|
1095
|
-
hint: `${UI_COPY.oauth.restoreBackupLatestHint}\n${UI_COPY.oauth.manualBackupHint(latestBackup.accountCount, formatBackupSavedAt(latestBackup.mtimeMs))}`,
|
|
1096
|
-
color: "cyan",
|
|
1097
|
-
},
|
|
1098
|
-
{
|
|
1099
|
-
label: UI_COPY.oauth.chooseBackupManually,
|
|
1100
|
-
value: "manual",
|
|
1101
|
-
color: "yellow",
|
|
1102
|
-
},
|
|
1103
|
-
{ label: UI_COPY.oauth.back, value: "back", color: "red" },
|
|
1104
|
-
];
|
|
1105
|
-
const selected = await select(items, {
|
|
1106
|
-
message: UI_COPY.oauth.restoreBackupTitle,
|
|
1107
|
-
subtitle: UI_COPY.oauth.restoreBackupSubtitle,
|
|
1108
|
-
help: UI_COPY.oauth.restoreBackupHelp,
|
|
1109
|
-
clearScreen: true,
|
|
1110
|
-
theme: ui.theme,
|
|
1111
|
-
selectedEmphasis: "minimal",
|
|
1112
|
-
allowEscape: false,
|
|
1113
|
-
onInput: (raw) => {
|
|
1114
|
-
const lower = raw.toLowerCase();
|
|
1115
|
-
if (lower === "q")
|
|
1116
|
-
return "back";
|
|
1117
|
-
if (lower === "1")
|
|
1118
|
-
return "latest";
|
|
1119
|
-
if (lower === "2")
|
|
1120
|
-
return "manual";
|
|
1121
|
-
return undefined;
|
|
1122
|
-
},
|
|
1123
|
-
});
|
|
1124
|
-
return selected ?? "back";
|
|
1125
|
-
}
|
|
1126
|
-
async function promptManualBackupSelection(backups) {
|
|
1127
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1128
|
-
return backups[0] ?? null;
|
|
1129
|
-
}
|
|
1130
|
-
const ui = getUiRuntimeOptions();
|
|
1131
|
-
const items = backups.map((backup) => ({
|
|
1132
|
-
label: backup.fileName,
|
|
1133
|
-
value: backup,
|
|
1134
|
-
hint: UI_COPY.oauth.manualBackupHint(backup.accountCount, formatBackupSavedAt(backup.mtimeMs)),
|
|
1135
|
-
color: "cyan",
|
|
1136
|
-
}));
|
|
1137
|
-
items.push({ label: UI_COPY.oauth.back, value: null, color: "red" });
|
|
1138
|
-
const selected = await select(items, {
|
|
1139
|
-
message: UI_COPY.oauth.manualBackupTitle,
|
|
1140
|
-
subtitle: UI_COPY.oauth.manualBackupSubtitle,
|
|
1141
|
-
help: UI_COPY.oauth.manualBackupHelp,
|
|
1142
|
-
clearScreen: true,
|
|
1143
|
-
theme: ui.theme,
|
|
1144
|
-
selectedEmphasis: "minimal",
|
|
1145
|
-
allowEscape: false,
|
|
1146
|
-
onInput: (raw) => {
|
|
1147
|
-
if (raw.toLowerCase() === "q")
|
|
1148
|
-
return null;
|
|
1149
|
-
return undefined;
|
|
1150
|
-
},
|
|
1151
|
-
});
|
|
1152
|
-
return selected;
|
|
1153
|
-
}
|
|
1154
|
-
async function waitForMenuReturn(options = {}) {
|
|
1155
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
const promptText = options.promptText ?? UI_COPY.returnFlow.continuePrompt;
|
|
1159
|
-
const autoReturnMs = options.autoReturnMs ?? 0;
|
|
1160
|
-
const pauseOnAnyKey = options.pauseOnAnyKey ?? true;
|
|
1161
|
-
try {
|
|
1162
|
-
let chunk;
|
|
1163
|
-
do {
|
|
1164
|
-
chunk = input.read();
|
|
1165
|
-
} while (chunk !== null);
|
|
1166
|
-
}
|
|
1167
|
-
catch {
|
|
1168
|
-
// best effort buffer drain
|
|
1169
|
-
}
|
|
1170
|
-
const writeInlineStatus = (message) => {
|
|
1171
|
-
output.write(`\r${ANSI.clearLine}${stylePromptText(message, "muted")}`);
|
|
1172
|
-
};
|
|
1173
|
-
const clearInlineStatus = () => {
|
|
1174
|
-
output.write(`\r${ANSI.clearLine}`);
|
|
1175
|
-
};
|
|
1176
|
-
if (autoReturnMs > 0) {
|
|
1177
|
-
if (!pauseOnAnyKey) {
|
|
1178
|
-
await new Promise((resolve) => setTimeout(resolve, autoReturnMs));
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
const wasRaw = input.isRaw ?? false;
|
|
1182
|
-
const endAt = Date.now() + autoReturnMs;
|
|
1183
|
-
let lastShownSeconds = null;
|
|
1184
|
-
const renderCountdown = () => {
|
|
1185
|
-
const remainingMs = Math.max(0, endAt - Date.now());
|
|
1186
|
-
const remainingSeconds = Math.max(1, Math.ceil(remainingMs / 1000));
|
|
1187
|
-
if (lastShownSeconds === remainingSeconds)
|
|
1188
|
-
return;
|
|
1189
|
-
lastShownSeconds = remainingSeconds;
|
|
1190
|
-
writeInlineStatus(UI_COPY.returnFlow.autoReturn(remainingSeconds));
|
|
1191
|
-
};
|
|
1192
|
-
renderCountdown();
|
|
1193
|
-
const pinned = await new Promise((resolve) => {
|
|
1194
|
-
let done = false;
|
|
1195
|
-
const interval = setInterval(renderCountdown, 80);
|
|
1196
|
-
let timeout = setTimeout(() => {
|
|
1197
|
-
timeout = null;
|
|
1198
|
-
if (!done) {
|
|
1199
|
-
done = true;
|
|
1200
|
-
cleanup();
|
|
1201
|
-
resolve(false);
|
|
1202
|
-
}
|
|
1203
|
-
}, autoReturnMs);
|
|
1204
|
-
const onData = () => {
|
|
1205
|
-
if (done)
|
|
1206
|
-
return;
|
|
1207
|
-
done = true;
|
|
1208
|
-
cleanup();
|
|
1209
|
-
resolve(true);
|
|
1210
|
-
};
|
|
1211
|
-
const cleanup = () => {
|
|
1212
|
-
clearInterval(interval);
|
|
1213
|
-
if (timeout) {
|
|
1214
|
-
clearTimeout(timeout);
|
|
1215
|
-
timeout = null;
|
|
1216
|
-
}
|
|
1217
|
-
input.removeListener("data", onData);
|
|
1218
|
-
try {
|
|
1219
|
-
input.setRawMode(wasRaw);
|
|
1220
|
-
}
|
|
1221
|
-
catch {
|
|
1222
|
-
// best effort restore
|
|
1223
|
-
}
|
|
1224
|
-
};
|
|
1225
|
-
try {
|
|
1226
|
-
input.setRawMode(true);
|
|
1227
|
-
}
|
|
1228
|
-
catch {
|
|
1229
|
-
// if raw mode fails, keep countdown behavior
|
|
1230
|
-
}
|
|
1231
|
-
input.on("data", onData);
|
|
1232
|
-
input.resume();
|
|
1233
|
-
});
|
|
1234
|
-
clearInlineStatus();
|
|
1235
|
-
if (!pinned) {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
const paused = stylePromptText(UI_COPY.returnFlow.paused, "muted");
|
|
1239
|
-
writeInlineStatus(paused);
|
|
1240
|
-
await new Promise((resolve) => {
|
|
1241
|
-
const wasRaw = input.isRaw ?? false;
|
|
1242
|
-
const onData = () => {
|
|
1243
|
-
cleanup();
|
|
1244
|
-
resolve();
|
|
1245
|
-
};
|
|
1246
|
-
const cleanup = () => {
|
|
1247
|
-
input.removeListener("data", onData);
|
|
1248
|
-
try {
|
|
1249
|
-
input.setRawMode(wasRaw);
|
|
1250
|
-
}
|
|
1251
|
-
catch {
|
|
1252
|
-
// best effort restore
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1255
|
-
try {
|
|
1256
|
-
input.setRawMode(true);
|
|
1257
|
-
}
|
|
1258
|
-
catch {
|
|
1259
|
-
// best effort fallback
|
|
1260
|
-
}
|
|
1261
|
-
input.on("data", onData);
|
|
1262
|
-
input.resume();
|
|
1263
|
-
});
|
|
1264
|
-
clearInlineStatus();
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
const rl = createInterface({ input, output });
|
|
1268
|
-
try {
|
|
1269
|
-
const question = promptText.length > 0 ? `${stylePromptText(promptText, "muted")} ` : "";
|
|
1270
|
-
output.write(`\r${ANSI.clearLine}`);
|
|
1271
|
-
await rl.question(question);
|
|
1272
|
-
}
|
|
1273
|
-
catch (error) {
|
|
1274
|
-
if (!isAbortError(error)) {
|
|
1275
|
-
throw error;
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
finally {
|
|
1279
|
-
rl.close();
|
|
1280
|
-
clearInlineStatus();
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
function stringifyLogArgs(args) {
|
|
1284
|
-
return args
|
|
1285
|
-
.map((value) => {
|
|
1286
|
-
if (typeof value === "string")
|
|
1287
|
-
return value;
|
|
1288
|
-
try {
|
|
1289
|
-
return JSON.stringify(value);
|
|
1290
|
-
}
|
|
1291
|
-
catch {
|
|
1292
|
-
return String(value);
|
|
1293
|
-
}
|
|
1294
|
-
})
|
|
1295
|
-
.join(" ");
|
|
1296
|
-
}
|
|
1297
|
-
async function runActionPanel(title, stage, action, settings) {
|
|
1298
|
-
if (!input.isTTY || !output.isTTY) {
|
|
1299
|
-
await action();
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
const spinnerFrames = ["-", "\\", "|", "/"];
|
|
1303
|
-
let frame = 0;
|
|
1304
|
-
let running = true;
|
|
1305
|
-
let failed = null;
|
|
1306
|
-
const captured = [];
|
|
1307
|
-
const maxVisibleLines = Math.max(8, (output.rows ?? 24) - 8);
|
|
1308
|
-
const previousLog = console.log;
|
|
1309
|
-
const previousWarn = console.warn;
|
|
1310
|
-
const previousError = console.error;
|
|
1311
|
-
const capture = (prefix, args) => {
|
|
1312
|
-
const line = stringifyLogArgs(args).trim();
|
|
1313
|
-
if (!line)
|
|
1314
|
-
return;
|
|
1315
|
-
captured.push(prefix ? `${prefix}${line}` : line);
|
|
1316
|
-
if (captured.length > 400) {
|
|
1317
|
-
captured.splice(0, captured.length - 400);
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
const render = () => {
|
|
1321
|
-
output.write(ANSI.clearScreen + ANSI.moveTo(1, 1));
|
|
1322
|
-
const spinner = running
|
|
1323
|
-
? `${spinnerFrames[frame % spinnerFrames.length] ?? "-"} `
|
|
1324
|
-
: failed
|
|
1325
|
-
? "x "
|
|
1326
|
-
: "+ ";
|
|
1327
|
-
const stageText = running
|
|
1328
|
-
? `${spinner}${stage}`
|
|
1329
|
-
: failed
|
|
1330
|
-
? UI_COPY.returnFlow.failed
|
|
1331
|
-
: UI_COPY.returnFlow.done;
|
|
1332
|
-
previousLog(stylePromptText(title, "accent"));
|
|
1333
|
-
previousLog(stylePromptText(stageText, failed ? "danger" : running ? "accent" : "success"));
|
|
1334
|
-
previousLog("");
|
|
1335
|
-
const lines = captured.slice(-maxVisibleLines);
|
|
1336
|
-
for (const line of lines) {
|
|
1337
|
-
previousLog(line);
|
|
1338
|
-
}
|
|
1339
|
-
const remainingLines = Math.max(0, maxVisibleLines - lines.length);
|
|
1340
|
-
for (let i = 0; i < remainingLines; i += 1) {
|
|
1341
|
-
previousLog("");
|
|
1342
|
-
}
|
|
1343
|
-
previousLog("");
|
|
1344
|
-
if (running)
|
|
1345
|
-
previousLog(stylePromptText(UI_COPY.returnFlow.working, "muted"));
|
|
1346
|
-
frame += 1;
|
|
1347
|
-
};
|
|
1348
|
-
console.log = (...args) => {
|
|
1349
|
-
capture("", args);
|
|
1350
|
-
};
|
|
1351
|
-
console.warn = (...args) => {
|
|
1352
|
-
capture("! ", args);
|
|
1353
|
-
};
|
|
1354
|
-
console.error = (...args) => {
|
|
1355
|
-
capture("x ", args);
|
|
1356
|
-
};
|
|
1357
|
-
output.write(ANSI.altScreenOn + ANSI.hide);
|
|
1358
|
-
let timer = null;
|
|
1359
|
-
try {
|
|
1360
|
-
render();
|
|
1361
|
-
timer = setInterval(() => {
|
|
1362
|
-
if (!running)
|
|
1363
|
-
return;
|
|
1364
|
-
render();
|
|
1365
|
-
}, 120);
|
|
1366
|
-
await action();
|
|
1367
|
-
}
|
|
1368
|
-
catch (error) {
|
|
1369
|
-
failed = error;
|
|
1370
|
-
capture("x ", [error instanceof Error ? error.message : String(error)]);
|
|
1371
|
-
}
|
|
1372
|
-
finally {
|
|
1373
|
-
running = false;
|
|
1374
|
-
if (timer) {
|
|
1375
|
-
clearInterval(timer);
|
|
1376
|
-
timer = null;
|
|
1377
|
-
}
|
|
1378
|
-
render();
|
|
1379
|
-
console.log = previousLog;
|
|
1380
|
-
console.warn = previousWarn;
|
|
1381
|
-
console.error = previousError;
|
|
1382
|
-
}
|
|
1383
|
-
if (failed) {
|
|
1384
|
-
await waitForMenuReturn({
|
|
1385
|
-
promptText: UI_COPY.returnFlow.actionFailedPrompt,
|
|
1386
|
-
});
|
|
1387
|
-
}
|
|
1388
|
-
else {
|
|
1389
|
-
await waitForMenuReturn({
|
|
1390
|
-
autoReturnMs: settings?.actionAutoReturnMs ?? 2_000,
|
|
1391
|
-
pauseOnAnyKey: settings?.actionPauseOnKey ?? true,
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
output.write(ANSI.altScreenOff + ANSI.show + ANSI.clearScreen + ANSI.moveTo(1, 1));
|
|
1395
|
-
if (failed) {
|
|
1396
|
-
throw failed;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
async function runOAuthFlow(forceNewLogin, signInMode) {
|
|
1400
|
-
const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
|
|
1401
|
-
let code = null;
|
|
1402
|
-
let oauthServer = null;
|
|
1403
|
-
try {
|
|
1404
|
-
if (signInMode === "browser") {
|
|
1405
|
-
try {
|
|
1406
|
-
oauthServer = await startLocalOAuthServer({ state });
|
|
1407
|
-
}
|
|
1408
|
-
catch (serverError) {
|
|
1409
|
-
log.warn("Local OAuth callback server unavailable; falling back to manual callback entry.", serverError instanceof Error
|
|
1410
|
-
? {
|
|
1411
|
-
message: serverError.message,
|
|
1412
|
-
stack: serverError.stack,
|
|
1413
|
-
code: typeof serverError === "object" &&
|
|
1414
|
-
serverError !== null &&
|
|
1415
|
-
"code" in serverError
|
|
1416
|
-
? String(serverError.code)
|
|
1417
|
-
: undefined,
|
|
1418
|
-
}
|
|
1419
|
-
: { error: String(serverError) });
|
|
1420
|
-
oauthServer = null;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
// Display the OAuth URL with sensitive query parameters (state,
|
|
1424
|
-
// code, code_challenge, code_verifier) redacted so they do not leak
|
|
1425
|
-
// into shell history, screen captures, CI transcripts, or clipboard
|
|
1426
|
-
// managers. The full URL is still handed to the browser opener and
|
|
1427
|
-
// the clipboard so sign-in continues to work end-to-end.
|
|
1428
|
-
const displayUrl = redactOAuthUrlForLog(url);
|
|
1429
|
-
if (signInMode === "browser") {
|
|
1430
|
-
const opened = openBrowserUrl(url);
|
|
1431
|
-
if (opened) {
|
|
1432
|
-
console.log(stylePromptText(UI_COPY.oauth.browserOpened, "success"));
|
|
1433
|
-
}
|
|
1434
|
-
else {
|
|
1435
|
-
console.log(stylePromptText(UI_COPY.oauth.browserOpenFail, "warning"));
|
|
1436
|
-
console.log(`${stylePromptText(UI_COPY.oauth.goTo, "accent")} ${displayUrl}`);
|
|
1437
|
-
const copied = copyTextToClipboard(url);
|
|
1438
|
-
console.log(stylePromptText(copied ? UI_COPY.oauth.copyOk : UI_COPY.oauth.copyFail, copied ? "success" : "warning"));
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
else {
|
|
1442
|
-
console.log(`${stylePromptText(UI_COPY.oauth.goTo, "accent")} ${displayUrl}`);
|
|
1443
|
-
const copied = copyTextToClipboard(url);
|
|
1444
|
-
console.log(stylePromptText(copied ? UI_COPY.oauth.copyOk : UI_COPY.oauth.copyFail, copied ? "success" : "warning"));
|
|
1445
|
-
}
|
|
1446
|
-
const waitingForCallback = signInMode === "browser" && oauthServer?.ready === true;
|
|
1447
|
-
if (waitingForCallback && oauthServer) {
|
|
1448
|
-
console.log(stylePromptText(UI_COPY.oauth.waitingCallback, "muted"));
|
|
1449
|
-
const callbackResult = await oauthServer.waitForCode(state);
|
|
1450
|
-
code = callbackResult?.code ?? null;
|
|
1451
|
-
}
|
|
1452
|
-
if (!code) {
|
|
1453
|
-
console.log(stylePromptText(waitingForCallback
|
|
1454
|
-
? UI_COPY.oauth.callbackMissed
|
|
1455
|
-
: signInMode === "manual"
|
|
1456
|
-
? UI_COPY.oauth.callbackBypassed
|
|
1457
|
-
: UI_COPY.oauth.callbackUnavailable, "warning"));
|
|
1458
|
-
code = await promptManualCallback(state, {
|
|
1459
|
-
allowNonTty: signInMode === "manual",
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
finally {
|
|
1464
|
-
oauthServer?.close();
|
|
1465
|
-
}
|
|
1466
|
-
if (!code) {
|
|
1467
|
-
return {
|
|
1468
|
-
type: "failed",
|
|
1469
|
-
reason: "unknown",
|
|
1470
|
-
message: UI_COPY.oauth.cancelled,
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
return exchangeAuthorizationCode(code, pkce.verifier, REDIRECT_URI);
|
|
1474
|
-
}
|
|
1475
|
-
async function runSignInFlow(forceNewLogin, signInMode, options = {}) {
|
|
1476
|
-
if (signInMode === "device") {
|
|
1477
|
-
// OpenAI owns the device-code account picker; there is no force-new-login
|
|
1478
|
-
// equivalent to pass through for this mode.
|
|
1479
|
-
// TODO: Thread a manager-level AbortSignal when login cancellation exists.
|
|
1480
|
-
return runDeviceAuthFlow({
|
|
1481
|
-
log: console.log,
|
|
1482
|
-
timeoutMs: options.timeoutMs,
|
|
1483
|
-
// CLI invocations rely on top-level await in scripts/codex-multi-auth.js;
|
|
1484
|
-
// without keepAlive the polling timers unref and Node exits before the
|
|
1485
|
-
// user can complete the browser step (issue #477).
|
|
1486
|
-
keepAlive: true,
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
return runOAuthFlow(forceNewLogin, signInMode);
|
|
1490
|
-
}
|
|
1491
|
-
async function persistAccountPool(results, replaceAll) {
|
|
1492
|
-
if (results.length === 0)
|
|
1493
|
-
return;
|
|
1494
|
-
await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
1495
|
-
const stored = replaceAll ? null : loadedStorage;
|
|
1496
|
-
const now = Date.now();
|
|
1497
|
-
const accounts = stored?.accounts ? [...stored.accounts] : [];
|
|
1498
|
-
let selectedAccountIndex = null;
|
|
1499
|
-
for (const result of results) {
|
|
1500
|
-
const tokenAccountId = extractAccountId(result.access);
|
|
1501
|
-
const accountId = resolveRequestAccountId(result.accountIdOverride, result.accountIdSource, tokenAccountId);
|
|
1502
|
-
const accountIdSource = accountId
|
|
1503
|
-
? (result.accountIdSource ??
|
|
1504
|
-
(result.accountIdOverride ? "manual" : "token"))
|
|
1505
|
-
: undefined;
|
|
1506
|
-
const accountLabel = result.accountLabel;
|
|
1507
|
-
const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
|
|
1508
|
-
const existingIndex = findMatchingAccountIndex(accounts, {
|
|
1509
|
-
accountId,
|
|
1510
|
-
email: accountEmail,
|
|
1511
|
-
refreshToken: result.refresh,
|
|
1512
|
-
}, {
|
|
1513
|
-
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
1514
|
-
});
|
|
1515
|
-
if (existingIndex === undefined) {
|
|
1516
|
-
const newIndex = accounts.length;
|
|
1517
|
-
accounts.push({
|
|
1518
|
-
accountId,
|
|
1519
|
-
accountIdSource,
|
|
1520
|
-
accountLabel,
|
|
1521
|
-
email: accountEmail,
|
|
1522
|
-
refreshToken: result.refresh,
|
|
1523
|
-
accessToken: result.access,
|
|
1524
|
-
expiresAt: result.expires,
|
|
1525
|
-
enabled: true,
|
|
1526
|
-
addedAt: now,
|
|
1527
|
-
lastUsed: now,
|
|
1528
|
-
});
|
|
1529
|
-
selectedAccountIndex = newIndex;
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
const existing = accounts[existingIndex];
|
|
1533
|
-
if (!existing)
|
|
1534
|
-
continue;
|
|
1535
|
-
const nextEmail = accountEmail ?? existing.email;
|
|
1536
|
-
const nextAccountId = accountId ?? existing.accountId;
|
|
1537
|
-
const nextAccountIdSource = accountId
|
|
1538
|
-
? (accountIdSource ?? existing.accountIdSource)
|
|
1539
|
-
: existing.accountIdSource;
|
|
1540
|
-
accounts[existingIndex] = {
|
|
1541
|
-
...existing,
|
|
1542
|
-
accountId: nextAccountId,
|
|
1543
|
-
accountIdSource: nextAccountIdSource,
|
|
1544
|
-
accountLabel: accountLabel ?? existing.accountLabel,
|
|
1545
|
-
email: nextEmail,
|
|
1546
|
-
refreshToken: result.refresh,
|
|
1547
|
-
accessToken: result.access,
|
|
1548
|
-
expiresAt: result.expires,
|
|
1549
|
-
enabled: true,
|
|
1550
|
-
lastUsed: now,
|
|
1551
|
-
};
|
|
1552
|
-
selectedAccountIndex = existingIndex;
|
|
1553
|
-
}
|
|
1554
|
-
const fallbackActiveIndex = accounts.length === 0
|
|
1555
|
-
? 0
|
|
1556
|
-
: Math.max(0, Math.min(stored?.activeIndex ?? 0, accounts.length - 1));
|
|
1557
|
-
const nextActiveIndex = accounts.length === 0
|
|
1558
|
-
? 0
|
|
1559
|
-
: selectedAccountIndex === null
|
|
1560
|
-
? fallbackActiveIndex
|
|
1561
|
-
: Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
|
|
1562
|
-
const activeIndexByFamily = {};
|
|
1563
|
-
for (const family of MODEL_FAMILIES) {
|
|
1564
|
-
activeIndexByFamily[family] = nextActiveIndex;
|
|
1565
|
-
}
|
|
1566
|
-
await persist({
|
|
1567
|
-
version: 3,
|
|
1568
|
-
accounts,
|
|
1569
|
-
activeIndex: nextActiveIndex,
|
|
1570
|
-
activeIndexByFamily,
|
|
1571
|
-
});
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
async function syncSelectionToCodex(tokens) {
|
|
1575
|
-
const tokenAccountId = extractAccountId(tokens.access);
|
|
1576
|
-
const accountId = resolveRequestAccountId(tokens.accountIdOverride, tokens.accountIdSource, tokenAccountId);
|
|
1577
|
-
const email = sanitizeEmail(extractAccountEmail(tokens.access, tokens.idToken));
|
|
1578
|
-
await setCodexCliActiveSelection({
|
|
1579
|
-
accountId,
|
|
1580
|
-
email,
|
|
1581
|
-
accessToken: tokens.access,
|
|
1582
|
-
refreshToken: tokens.refresh,
|
|
1583
|
-
expiresAt: tokens.expires,
|
|
1584
|
-
idToken: tokens.idToken,
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
async function runHealthCheck(options = {}) {
|
|
1588
|
-
const forceRefresh = options.forceRefresh === true;
|
|
1589
|
-
const liveProbe = options.liveProbe === true;
|
|
1590
|
-
const probeModel = options.model?.trim() || DEFAULT_LIVE_PROBE_MODEL;
|
|
1591
|
-
const modelInspection = inspectRequestedModel(probeModel);
|
|
1592
|
-
const display = options.display ?? DEFAULT_DASHBOARD_DISPLAY_SETTINGS;
|
|
1593
|
-
const quotaCache = liveProbe ? await loadQuotaCache() : null;
|
|
1594
|
-
const workingQuotaCache = quotaCache ? cloneQuotaCacheData(quotaCache) : null;
|
|
1595
|
-
let quotaCacheChanged = false;
|
|
1596
|
-
setStoragePath(null);
|
|
1597
|
-
const storage = await loadAccounts();
|
|
1598
|
-
if (!storage || storage.accounts.length === 0) {
|
|
1599
|
-
console.log("No accounts configured.");
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
let quotaEmailFallbackState = liveProbe && quotaCache
|
|
1603
|
-
? buildQuotaEmailFallbackState(storage.accounts)
|
|
1604
|
-
: null;
|
|
1605
|
-
let changed = false;
|
|
1606
|
-
let ok = 0;
|
|
1607
|
-
let failed = 0;
|
|
1608
|
-
let warnings = 0;
|
|
1609
|
-
let codexAvailable = 0;
|
|
1610
|
-
let signedInOnly = 0;
|
|
1611
|
-
const activeIndex = resolveActiveIndex(storage, "codex");
|
|
1612
|
-
let activeAccountRefreshed = false;
|
|
1613
|
-
const now = Date.now();
|
|
1614
|
-
console.log(stylePromptText(forceRefresh
|
|
1615
|
-
? `Checking ${storage.accounts.length} account(s) with full refresh test...`
|
|
1616
|
-
: `Checking ${storage.accounts.length} account(s) with quick check${liveProbe ? " + live check" : ""}...`, "accent"));
|
|
1617
|
-
if (liveProbe) {
|
|
1618
|
-
console.log(stylePromptText(`Model probe: ${formatModelInspection(modelInspection)}`, "muted"));
|
|
1619
|
-
}
|
|
1620
|
-
for (let i = 0; i < storage.accounts.length; i += 1) {
|
|
1621
|
-
const account = storage.accounts[i];
|
|
1622
|
-
if (!account)
|
|
1623
|
-
continue;
|
|
1624
|
-
const label = formatAccountLabel(account, i);
|
|
1625
|
-
const labelText = stylePromptText(label, "accent");
|
|
1626
|
-
const sessionLikelyValid = hasUsableAccessToken(account, now);
|
|
1627
|
-
if (!forceRefresh && sessionLikelyValid) {
|
|
1628
|
-
if (account.enabled === false) {
|
|
1629
|
-
account.enabled = true;
|
|
1630
|
-
changed = true;
|
|
1631
|
-
}
|
|
1632
|
-
if (i === activeIndex) {
|
|
1633
|
-
activeAccountRefreshed = true;
|
|
1634
|
-
}
|
|
1635
|
-
let healthDetail = "signed in and working";
|
|
1636
|
-
let healthTone = "success";
|
|
1637
|
-
if (liveProbe) {
|
|
1638
|
-
const currentAccessToken = account.accessToken;
|
|
1639
|
-
const probeAccountId = currentAccessToken
|
|
1640
|
-
? (account.accountId ?? extractAccountId(currentAccessToken))
|
|
1641
|
-
: undefined;
|
|
1642
|
-
if (!probeAccountId || !currentAccessToken) {
|
|
1643
|
-
warnings += 1;
|
|
1644
|
-
signedInOnly += 1;
|
|
1645
|
-
healthTone = "warning";
|
|
1646
|
-
healthDetail =
|
|
1647
|
-
"signed in (live check skipped: missing account ID)";
|
|
1648
|
-
}
|
|
1649
|
-
else {
|
|
1650
|
-
try {
|
|
1651
|
-
const snapshot = await fetchCodexQuotaSnapshot({
|
|
1652
|
-
accountId: probeAccountId,
|
|
1653
|
-
accessToken: currentAccessToken,
|
|
1654
|
-
model: modelInspection.normalized,
|
|
1655
|
-
});
|
|
1656
|
-
if (workingQuotaCache) {
|
|
1657
|
-
quotaCacheChanged =
|
|
1658
|
-
updateQuotaCacheForAccount(workingQuotaCache, account, snapshot, storage.accounts, quotaEmailFallbackState ?? undefined) || quotaCacheChanged;
|
|
1659
|
-
}
|
|
1660
|
-
healthDetail = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1661
|
-
codexAvailable += 1;
|
|
1662
|
-
}
|
|
1663
|
-
catch (error) {
|
|
1664
|
-
warnings += 1;
|
|
1665
|
-
signedInOnly += 1;
|
|
1666
|
-
healthTone = "warning";
|
|
1667
|
-
if (isCodexUnavailableError(error)) {
|
|
1668
|
-
healthDetail =
|
|
1669
|
-
`signed in; ${CODEX_UNAVAILABLE_PROBE_NOTE}`;
|
|
1670
|
-
}
|
|
1671
|
-
else {
|
|
1672
|
-
const message = normalizeFailureDetail(error instanceof Error ? error.message : String(error), undefined);
|
|
1673
|
-
healthDetail = `signed in (live check failed: ${message})`;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
if (hasLikelyInvalidRefreshToken(account.refreshToken)) {
|
|
1679
|
-
healthDetail += " (re-login suggested soon)";
|
|
1680
|
-
}
|
|
1681
|
-
ok += 1;
|
|
1682
|
-
if (display.showPerAccountRows) {
|
|
1683
|
-
const healthMarker = healthTone === "success" ? "✓" : "!";
|
|
1684
|
-
console.log(` ${stylePromptText(healthMarker, healthTone)} ${labelText} ${stylePromptText("|", "muted")} ${styleAccountDetailText(healthDetail, healthTone)}`);
|
|
1685
|
-
}
|
|
1686
|
-
continue;
|
|
1687
|
-
}
|
|
1688
|
-
const result = await queuedRefresh(account.refreshToken);
|
|
1689
|
-
if (result.type === "success") {
|
|
1690
|
-
const tokenAccountId = extractAccountId(result.access);
|
|
1691
|
-
const nextEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
|
|
1692
|
-
const previousEmail = account.email;
|
|
1693
|
-
let accountIdentityChanged = false;
|
|
1694
|
-
if (account.refreshToken !== result.refresh) {
|
|
1695
|
-
account.refreshToken = result.refresh;
|
|
1696
|
-
changed = true;
|
|
1697
|
-
}
|
|
1698
|
-
if (account.accessToken !== result.access) {
|
|
1699
|
-
account.accessToken = result.access;
|
|
1700
|
-
changed = true;
|
|
1701
|
-
}
|
|
1702
|
-
if (account.expiresAt !== result.expires) {
|
|
1703
|
-
account.expiresAt = result.expires;
|
|
1704
|
-
changed = true;
|
|
1705
|
-
}
|
|
1706
|
-
if (nextEmail && nextEmail !== account.email) {
|
|
1707
|
-
account.email = nextEmail;
|
|
1708
|
-
changed = true;
|
|
1709
|
-
accountIdentityChanged = true;
|
|
1710
|
-
}
|
|
1711
|
-
if (applyTokenAccountIdentity(account, tokenAccountId)) {
|
|
1712
|
-
changed = true;
|
|
1713
|
-
accountIdentityChanged = true;
|
|
1714
|
-
}
|
|
1715
|
-
if (account.enabled === false) {
|
|
1716
|
-
account.enabled = true;
|
|
1717
|
-
changed = true;
|
|
1718
|
-
}
|
|
1719
|
-
if (accountIdentityChanged && liveProbe && workingQuotaCache) {
|
|
1720
|
-
quotaEmailFallbackState = buildQuotaEmailFallbackState(storage.accounts);
|
|
1721
|
-
quotaCacheChanged =
|
|
1722
|
-
pruneUnsafeQuotaEmailCacheEntry(workingQuotaCache, previousEmail, storage.accounts, quotaEmailFallbackState) || quotaCacheChanged;
|
|
1723
|
-
}
|
|
1724
|
-
account.lastUsed = Date.now();
|
|
1725
|
-
if (i === activeIndex) {
|
|
1726
|
-
activeAccountRefreshed = true;
|
|
1727
|
-
}
|
|
1728
|
-
ok += 1;
|
|
1729
|
-
let healthyMessage = "working now";
|
|
1730
|
-
let healthyTone = "success";
|
|
1731
|
-
if (liveProbe) {
|
|
1732
|
-
const probeAccountId = account.accountId ?? tokenAccountId;
|
|
1733
|
-
if (!probeAccountId) {
|
|
1734
|
-
warnings += 1;
|
|
1735
|
-
signedInOnly += 1;
|
|
1736
|
-
healthyTone = "warning";
|
|
1737
|
-
healthyMessage =
|
|
1738
|
-
"signed in (live check skipped: missing account ID)";
|
|
1739
|
-
}
|
|
1740
|
-
else {
|
|
1741
|
-
try {
|
|
1742
|
-
const snapshot = await fetchCodexQuotaSnapshot({
|
|
1743
|
-
accountId: probeAccountId,
|
|
1744
|
-
accessToken: result.access,
|
|
1745
|
-
model: modelInspection.normalized,
|
|
1746
|
-
});
|
|
1747
|
-
if (workingQuotaCache) {
|
|
1748
|
-
quotaCacheChanged =
|
|
1749
|
-
updateQuotaCacheForAccount(workingQuotaCache, account, snapshot, storage.accounts, quotaEmailFallbackState ?? undefined) || quotaCacheChanged;
|
|
1750
|
-
}
|
|
1751
|
-
healthyMessage = formatQuotaSnapshotForDashboard(snapshot, display);
|
|
1752
|
-
codexAvailable += 1;
|
|
1753
|
-
}
|
|
1754
|
-
catch (error) {
|
|
1755
|
-
warnings += 1;
|
|
1756
|
-
signedInOnly += 1;
|
|
1757
|
-
healthyTone = "warning";
|
|
1758
|
-
if (isCodexUnavailableError(error)) {
|
|
1759
|
-
healthyMessage =
|
|
1760
|
-
`signed in; ${CODEX_UNAVAILABLE_PROBE_NOTE}`;
|
|
1761
|
-
}
|
|
1762
|
-
else {
|
|
1763
|
-
const message = normalizeFailureDetail(error instanceof Error ? error.message : String(error), undefined);
|
|
1764
|
-
healthyMessage = `signed in (live check failed: ${message})`;
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
if (display.showPerAccountRows) {
|
|
1770
|
-
const healthyMarker = healthyTone === "success" ? "✓" : "!";
|
|
1771
|
-
console.log(` ${stylePromptText(healthyMarker, healthyTone)} ${labelText} ${stylePromptText("|", "muted")} ${styleAccountDetailText(healthyMessage, healthyTone)}`);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
else {
|
|
1775
|
-
const detail = normalizeFailureDetail(result.message, result.reason);
|
|
1776
|
-
if (sessionLikelyValid) {
|
|
1777
|
-
warnings += 1;
|
|
1778
|
-
if (liveProbe) {
|
|
1779
|
-
signedInOnly += 1;
|
|
1780
|
-
}
|
|
1781
|
-
if (display.showPerAccountRows) {
|
|
1782
|
-
console.log(` ${stylePromptText("!", "warning")} ${labelText} ${stylePromptText("|", "muted")} ${stylePromptText(`refresh failed (${detail}) but this account still works right now`, "warning")}`);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
else {
|
|
1786
|
-
failed += 1;
|
|
1787
|
-
if (display.showPerAccountRows) {
|
|
1788
|
-
console.log(` ${stylePromptText("✗", "danger")} ${labelText} ${stylePromptText("|", "muted")} ${stylePromptText(detail, "danger")}`);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
if (!display.showPerAccountRows) {
|
|
1794
|
-
console.log(stylePromptText("Per-account lines are hidden in dashboard settings.", "muted"));
|
|
1795
|
-
}
|
|
1796
|
-
if (workingQuotaCache && quotaCacheChanged) {
|
|
1797
|
-
try {
|
|
1798
|
-
await saveQuotaCache(workingQuotaCache);
|
|
1799
|
-
}
|
|
1800
|
-
catch (error) {
|
|
1801
|
-
// Quota cache is a derived artifact; a transient Windows EBUSY/EPERM
|
|
1802
|
-
// here must not abort the health check before account fixes commit.
|
|
1803
|
-
console.warn(`Quota cache save failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
if (changed) {
|
|
1807
|
-
await saveAccountsWithRetry(storage, saveAccounts);
|
|
1808
|
-
}
|
|
1809
|
-
if (activeAccountRefreshed &&
|
|
1810
|
-
activeIndex >= 0 &&
|
|
1811
|
-
activeIndex < storage.accounts.length) {
|
|
1812
|
-
const activeAccount = storage.accounts[activeIndex];
|
|
1813
|
-
if (activeAccount) {
|
|
1814
|
-
await setCodexCliActiveSelection({
|
|
1815
|
-
accountId: activeAccount.accountId,
|
|
1816
|
-
email: activeAccount.email,
|
|
1817
|
-
accessToken: activeAccount.accessToken,
|
|
1818
|
-
refreshToken: activeAccount.refreshToken,
|
|
1819
|
-
expiresAt: activeAccount.expiresAt,
|
|
1820
|
-
});
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
console.log("");
|
|
1824
|
-
console.log(formatResultSummary(liveProbe
|
|
1825
|
-
? [
|
|
1826
|
-
{
|
|
1827
|
-
text: `${codexAvailable} Codex available`,
|
|
1828
|
-
tone: codexAvailable > 0 ? "success" : "muted",
|
|
1829
|
-
},
|
|
1830
|
-
{
|
|
1831
|
-
text: `${signedInOnly} signed in only`,
|
|
1832
|
-
tone: signedInOnly > 0 ? "warning" : "muted",
|
|
1833
|
-
},
|
|
1834
|
-
{
|
|
1835
|
-
text: `${failed} need re-login`,
|
|
1836
|
-
tone: failed > 0 ? "danger" : "muted",
|
|
1837
|
-
},
|
|
1838
|
-
]
|
|
1839
|
-
: [
|
|
1840
|
-
{ text: `${ok} working`, tone: "success" },
|
|
1841
|
-
{
|
|
1842
|
-
text: `${failed} need re-login`,
|
|
1843
|
-
tone: failed > 0 ? "danger" : "muted",
|
|
1844
|
-
},
|
|
1845
|
-
{
|
|
1846
|
-
text: `${warnings} warning${warnings === 1 ? "" : "s"}`,
|
|
1847
|
-
tone: warnings > 0 ? "warning" : "muted",
|
|
1848
|
-
},
|
|
1849
|
-
]));
|
|
1850
|
-
}
|
|
1851
|
-
function printBestUsage() {
|
|
1852
|
-
console.log([
|
|
1853
|
-
"Usage:",
|
|
1854
|
-
" codex-multi-auth best [--live] [--json] [--model <model>]",
|
|
1855
|
-
"",
|
|
1856
|
-
"Options:",
|
|
1857
|
-
" --live, -l Probe live quota headers via Codex backend before switching",
|
|
1858
|
-
" --json, -j Print machine-readable JSON output",
|
|
1859
|
-
` --model, -m Probe model for live mode (default: ${DEFAULT_LIVE_PROBE_MODEL})`,
|
|
1860
|
-
"",
|
|
1861
|
-
"Behavior:",
|
|
1862
|
-
" - Chooses the healthiest account using forecast scoring",
|
|
1863
|
-
" - Switches to the recommended account when it is not already active",
|
|
1864
|
-
].join("\n"));
|
|
1865
|
-
}
|
|
1866
|
-
function parseBestArgs(args) {
|
|
1867
|
-
const options = {
|
|
1868
|
-
live: false,
|
|
1869
|
-
json: false,
|
|
1870
|
-
model: DEFAULT_LIVE_PROBE_MODEL,
|
|
1871
|
-
modelProvided: false,
|
|
1872
|
-
};
|
|
1873
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
1874
|
-
const arg = args[i];
|
|
1875
|
-
if (!arg)
|
|
1876
|
-
continue;
|
|
1877
|
-
if (arg === "--live" || arg === "-l") {
|
|
1878
|
-
options.live = true;
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
if (arg === "--json" || arg === "-j") {
|
|
1882
|
-
options.json = true;
|
|
1883
|
-
continue;
|
|
1884
|
-
}
|
|
1885
|
-
if (arg === "--model" || arg === "-m") {
|
|
1886
|
-
const value = args[i + 1]?.trim();
|
|
1887
|
-
if (!value || value.startsWith("-")) {
|
|
1888
|
-
return { ok: false, message: "Missing value for --model" };
|
|
1889
|
-
}
|
|
1890
|
-
options.model = value;
|
|
1891
|
-
options.modelProvided = true;
|
|
1892
|
-
i += 1;
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
if (arg.startsWith("--model=")) {
|
|
1896
|
-
const value = arg.slice("--model=".length).trim();
|
|
1897
|
-
if (!value || value.startsWith("-")) {
|
|
1898
|
-
return { ok: false, message: "Missing value for --model" };
|
|
1899
|
-
}
|
|
1900
|
-
options.model = value;
|
|
1901
|
-
options.modelProvided = true;
|
|
1902
|
-
continue;
|
|
1903
|
-
}
|
|
1904
|
-
return { ok: false, message: `Unknown option: ${arg}` };
|
|
1905
|
-
}
|
|
1906
|
-
return { ok: true, options };
|
|
1907
|
-
}
|
|
84
|
+
{ id: 11, name: "Auto-fix command (safe mode)" },
|
|
85
|
+
{ id: 12, name: "Doctor diagnostics command" },
|
|
86
|
+
{ id: 13, name: "JSON outputs for machine automation" },
|
|
87
|
+
{ id: 14, name: "Report generation command" },
|
|
88
|
+
{ id: 15, name: "Storage v3 normalization and migration" },
|
|
89
|
+
{ id: 16, name: "Storage backup and recovery journal" },
|
|
90
|
+
{ id: 17, name: "Project-scoped and global storage paths" },
|
|
91
|
+
{ id: 18, name: "Quota cache storage" },
|
|
92
|
+
{ id: 19, name: "Live account sync watcher" },
|
|
93
|
+
{ id: 20, name: "Session affinity store" },
|
|
94
|
+
{ id: 21, name: "Refresh queue dedupe (in-process)" },
|
|
95
|
+
{ id: 22, name: "Refresh lease dedupe (cross-process)" },
|
|
96
|
+
{ id: 23, name: "Token rotation mapping in refresh queue" },
|
|
97
|
+
{ id: 24, name: "Refresh guardian (proactive refresh)" },
|
|
98
|
+
{ id: 25, name: "Preemptive quota scheduler" },
|
|
99
|
+
{ id: 26, name: "Entitlement cache for unsupported models" },
|
|
100
|
+
{ id: 27, name: "Capability policy scoring store" },
|
|
101
|
+
{ id: 28, name: "Failure policy evaluation module" },
|
|
102
|
+
{ id: 29, name: "Streaming failover pipeline" },
|
|
103
|
+
{ id: 30, name: "Rate-limit backoff and cooldown handling" },
|
|
104
|
+
{ id: 31, name: "Host request transformer bridge" },
|
|
105
|
+
{ id: 32, name: "Prompt template sync with cache" },
|
|
106
|
+
{ id: 33, name: "Codex CLI active-account state sync" },
|
|
107
|
+
{ id: 34, name: "TUI quick-switch hotkeys (1-9)" },
|
|
108
|
+
{ id: 35, name: "TUI search and help toggles" },
|
|
109
|
+
{ id: 36, name: "TUI account detail hotkeys (S/R/E/D)" },
|
|
110
|
+
{ id: 37, name: "TUI settings hub (list/summary/behavior/theme)" },
|
|
111
|
+
{ id: 38, name: "Dashboard display customization" },
|
|
112
|
+
{ id: 39, name: "Unified color/theme runtime (v2 UI)" },
|
|
113
|
+
{ id: 40, name: "OAuth browser-first flow with manual callback fallback" },
|
|
114
|
+
{ id: 41, name: "Auto-switch to best account command" },
|
|
115
|
+
];
|
|
1908
116
|
async function runForecast(args) {
|
|
1909
117
|
return runForecastCommand(args, {
|
|
1910
118
|
setStoragePath,
|
|
@@ -1938,593 +146,6 @@ async function runForecast(args) {
|
|
|
1938
146
|
loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
|
|
1939
147
|
});
|
|
1940
148
|
}
|
|
1941
|
-
async function clearAccountsAndReset() {
|
|
1942
|
-
await clearAccounts();
|
|
1943
|
-
}
|
|
1944
|
-
function adjustManageActionSelectionIndex(currentIndex, removedIndex, remainingCount) {
|
|
1945
|
-
if (remainingCount <= 0) {
|
|
1946
|
-
return 0;
|
|
1947
|
-
}
|
|
1948
|
-
if (typeof currentIndex !== "number" || currentIndex < 0) {
|
|
1949
|
-
return 0;
|
|
1950
|
-
}
|
|
1951
|
-
if (currentIndex < removedIndex) {
|
|
1952
|
-
return Math.min(currentIndex, remainingCount - 1);
|
|
1953
|
-
}
|
|
1954
|
-
if (currentIndex > removedIndex) {
|
|
1955
|
-
return currentIndex - 1;
|
|
1956
|
-
}
|
|
1957
|
-
return Math.min(removedIndex, remainingCount - 1);
|
|
1958
|
-
}
|
|
1959
|
-
function resetManageActionSelection(storage, removedIndex) {
|
|
1960
|
-
const remainingCount = storage.accounts.length;
|
|
1961
|
-
if (remainingCount <= 0) {
|
|
1962
|
-
storage.activeIndex = 0;
|
|
1963
|
-
storage.activeIndexByFamily = {};
|
|
1964
|
-
for (const family of MODEL_FAMILIES) {
|
|
1965
|
-
storage.activeIndexByFamily[family] = 0;
|
|
1966
|
-
}
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
const previousActiveIndex = storage.activeIndex;
|
|
1970
|
-
const previousByFamily = { ...storage.activeIndexByFamily };
|
|
1971
|
-
storage.activeIndex = adjustManageActionSelectionIndex(previousActiveIndex, removedIndex, remainingCount);
|
|
1972
|
-
storage.activeIndexByFamily = {};
|
|
1973
|
-
for (const family of MODEL_FAMILIES) {
|
|
1974
|
-
storage.activeIndexByFamily[family] = adjustManageActionSelectionIndex(previousByFamily[family] ?? previousActiveIndex, removedIndex, remainingCount);
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
function replaceManageActionStorage(target, source) {
|
|
1978
|
-
target.version = source.version;
|
|
1979
|
-
target.accounts = structuredClone(source.accounts);
|
|
1980
|
-
target.activeIndex = source.activeIndex;
|
|
1981
|
-
target.activeIndexByFamily = {
|
|
1982
|
-
...source.activeIndexByFamily,
|
|
1983
|
-
};
|
|
1984
|
-
}
|
|
1985
|
-
function resolveManageActionAccountIndex(storage, fallbackIndex, account) {
|
|
1986
|
-
if (account) {
|
|
1987
|
-
const matchedIndex = findMatchingAccountIndex(storage.accounts, {
|
|
1988
|
-
accountId: account.accountId,
|
|
1989
|
-
email: account.email,
|
|
1990
|
-
refreshToken: account.refreshToken,
|
|
1991
|
-
}, {
|
|
1992
|
-
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
1993
|
-
});
|
|
1994
|
-
if (typeof matchedIndex === "number" && matchedIndex >= 0) {
|
|
1995
|
-
return matchedIndex;
|
|
1996
|
-
}
|
|
1997
|
-
return null;
|
|
1998
|
-
}
|
|
1999
|
-
return fallbackIndex >= 0 && fallbackIndex < storage.accounts.length
|
|
2000
|
-
? fallbackIndex
|
|
2001
|
-
: null;
|
|
2002
|
-
}
|
|
2003
|
-
function matchesManageActionAccount(account, candidate) {
|
|
2004
|
-
if (!account || !candidate) {
|
|
2005
|
-
return false;
|
|
2006
|
-
}
|
|
2007
|
-
if (account.accountId || candidate.accountId) {
|
|
2008
|
-
return account.accountId === candidate.accountId;
|
|
2009
|
-
}
|
|
2010
|
-
return (account.refreshToken === candidate.refreshToken &&
|
|
2011
|
-
sanitizeEmail(account.email) === sanitizeEmail(candidate.email));
|
|
2012
|
-
}
|
|
2013
|
-
async function handleManageAction(storage, menuResult) {
|
|
2014
|
-
if (typeof menuResult.switchAccountIndex === "number") {
|
|
2015
|
-
const index = menuResult.switchAccountIndex;
|
|
2016
|
-
await runSwitchCommand([String(index + 1)], {
|
|
2017
|
-
setStoragePath,
|
|
2018
|
-
loadAccounts,
|
|
2019
|
-
persistAndSyncSelectedAccount,
|
|
2020
|
-
});
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
if (typeof menuResult.deleteAccountIndex === "number") {
|
|
2024
|
-
const idx = menuResult.deleteAccountIndex;
|
|
2025
|
-
const selectedAccount = storage.accounts[idx];
|
|
2026
|
-
let deleted = false;
|
|
2027
|
-
if (selectedAccount) {
|
|
2028
|
-
await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
2029
|
-
const nextStorage = loadedStorage
|
|
2030
|
-
? structuredClone(loadedStorage)
|
|
2031
|
-
: structuredClone(storage);
|
|
2032
|
-
const nextIndex = resolveManageActionAccountIndex(nextStorage, idx, selectedAccount);
|
|
2033
|
-
if (nextIndex === null) {
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
const nextAccount = nextStorage.accounts[nextIndex];
|
|
2037
|
-
if (!matchesManageActionAccount(selectedAccount, nextAccount)) {
|
|
2038
|
-
return;
|
|
2039
|
-
}
|
|
2040
|
-
nextStorage.accounts.splice(nextIndex, 1);
|
|
2041
|
-
resetManageActionSelection(nextStorage, nextIndex);
|
|
2042
|
-
await persist(nextStorage);
|
|
2043
|
-
replaceManageActionStorage(storage, nextStorage);
|
|
2044
|
-
deleted = true;
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
if (deleted) {
|
|
2048
|
-
console.log(`Deleted account ${idx + 1}.`);
|
|
2049
|
-
}
|
|
2050
|
-
return;
|
|
2051
|
-
}
|
|
2052
|
-
if (typeof menuResult.toggleAccountIndex === "number") {
|
|
2053
|
-
const idx = menuResult.toggleAccountIndex;
|
|
2054
|
-
const selectedAccount = storage.accounts[idx];
|
|
2055
|
-
let nextEnabledState = null;
|
|
2056
|
-
if (selectedAccount) {
|
|
2057
|
-
await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
2058
|
-
const nextStorage = loadedStorage
|
|
2059
|
-
? structuredClone(loadedStorage)
|
|
2060
|
-
: structuredClone(storage);
|
|
2061
|
-
const nextIndex = resolveManageActionAccountIndex(nextStorage, idx, selectedAccount);
|
|
2062
|
-
if (nextIndex === null) {
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
const nextAccount = nextStorage.accounts[nextIndex];
|
|
2066
|
-
if (!nextAccount ||
|
|
2067
|
-
!matchesManageActionAccount(selectedAccount, nextAccount)) {
|
|
2068
|
-
return;
|
|
2069
|
-
}
|
|
2070
|
-
nextAccount.enabled = nextAccount.enabled === false;
|
|
2071
|
-
await persist(nextStorage);
|
|
2072
|
-
replaceManageActionStorage(storage, nextStorage);
|
|
2073
|
-
nextEnabledState = nextAccount.enabled !== false;
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
if (nextEnabledState !== null) {
|
|
2077
|
-
console.log(`${nextEnabledState ? "Enabled" : "Disabled"} account ${idx + 1}.`);
|
|
2078
|
-
}
|
|
2079
|
-
return;
|
|
2080
|
-
}
|
|
2081
|
-
if (typeof menuResult.refreshAccountIndex === "number") {
|
|
2082
|
-
const idx = menuResult.refreshAccountIndex;
|
|
2083
|
-
const existing = storage.accounts[idx];
|
|
2084
|
-
if (!existing)
|
|
2085
|
-
return;
|
|
2086
|
-
const signInMode = await promptOAuthSignInMode(null);
|
|
2087
|
-
if (signInMode === "cancel") {
|
|
2088
|
-
console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2091
|
-
if (signInMode !== "browser" && signInMode !== "manual") {
|
|
2092
|
-
return;
|
|
2093
|
-
}
|
|
2094
|
-
const tokenResult = await runOAuthFlow(true, signInMode);
|
|
2095
|
-
if (tokenResult.type !== "success") {
|
|
2096
|
-
console.error(`Refresh failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
|
|
2097
|
-
return;
|
|
2098
|
-
}
|
|
2099
|
-
const resolved = resolveAccountSelection(tokenResult);
|
|
2100
|
-
await persistAccountPool([resolved], false);
|
|
2101
|
-
await syncSelectionToCodex(resolved);
|
|
2102
|
-
console.log(`Refreshed account ${idx + 1}.`);
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
async function runAuthLogin(args) {
|
|
2106
|
-
const parsedArgs = parseAuthLoginArgs(args);
|
|
2107
|
-
if (!parsedArgs.ok) {
|
|
2108
|
-
if (parsedArgs.reason === "error") {
|
|
2109
|
-
console.error(parsedArgs.message);
|
|
2110
|
-
printUsage();
|
|
2111
|
-
return 1;
|
|
2112
|
-
}
|
|
2113
|
-
return 0;
|
|
2114
|
-
}
|
|
2115
|
-
const loginOptions = parsedArgs.options;
|
|
2116
|
-
// `--org <id>` binds this login to a specific workspace/org so the same
|
|
2117
|
-
// email's personal vs business/team workspace can be registered on demand
|
|
2118
|
-
// (issue #491). The org is threaded explicitly into resolveAccountSelection
|
|
2119
|
-
// (no process.env mutation), so concurrent re-entry (menu re-entry, a reused
|
|
2120
|
-
// test worker) can never bind a login to a stale org via a shared global.
|
|
2121
|
-
if (loginOptions.org) {
|
|
2122
|
-
console.log(`Binding this login to workspace org id: ${loginOptions.org}`);
|
|
2123
|
-
}
|
|
2124
|
-
return runAuthLoginFlow(loginOptions);
|
|
2125
|
-
}
|
|
2126
|
-
async function runAuthLoginFlow(loginOptions) {
|
|
2127
|
-
setStoragePath(null);
|
|
2128
|
-
let pendingMenuQuotaRefresh = null;
|
|
2129
|
-
let menuQuotaRefreshStatus;
|
|
2130
|
-
let skipNextMenuQuotaAutoRefresh = false;
|
|
2131
|
-
let menuQuotaRefreshGeneration = 0;
|
|
2132
|
-
const clearMenuQuotaAutoRefreshSkip = () => {
|
|
2133
|
-
skipNextMenuQuotaAutoRefresh = false;
|
|
2134
|
-
menuQuotaRefreshGeneration += 1;
|
|
2135
|
-
};
|
|
2136
|
-
// When the user explicitly picks a sign-in transport on the command line
|
|
2137
|
-
// (--device-auth, --manual, --no-browser), they want to add a new account
|
|
2138
|
-
// directly. Skipping the dashboard menu keeps `login --device-auth`
|
|
2139
|
-
// usable from scripts and matches the documented behavior of the help text.
|
|
2140
|
-
const explicitSignInMode = loginOptions.deviceAuth || loginOptions.manual;
|
|
2141
|
-
loginFlow: while (true) {
|
|
2142
|
-
let existingStorage = await loadAccounts();
|
|
2143
|
-
if (!explicitSignInMode &&
|
|
2144
|
-
existingStorage &&
|
|
2145
|
-
existingStorage.accounts.length > 0) {
|
|
2146
|
-
while (true) {
|
|
2147
|
-
existingStorage = await loadAccounts();
|
|
2148
|
-
if (!existingStorage || existingStorage.accounts.length === 0) {
|
|
2149
|
-
break;
|
|
2150
|
-
}
|
|
2151
|
-
const currentStorage = existingStorage;
|
|
2152
|
-
const displaySettings = await loadDashboardDisplaySettings();
|
|
2153
|
-
applyUiThemeFromDashboardSettings(displaySettings);
|
|
2154
|
-
const quotaCache = await loadQuotaCache();
|
|
2155
|
-
const shouldAutoFetchLimits = displaySettings.menuAutoFetchLimits ?? true;
|
|
2156
|
-
const showFetchStatus = displaySettings.menuShowFetchStatus ?? true;
|
|
2157
|
-
const quotaTtlMs = displaySettings.menuQuotaTtlMs ?? DEFAULT_MENU_QUOTA_REFRESH_TTL_MS;
|
|
2158
|
-
const shouldSkipAutoFetchThisPass = !pendingMenuQuotaRefresh && skipNextMenuQuotaAutoRefresh;
|
|
2159
|
-
if (shouldSkipAutoFetchThisPass) {
|
|
2160
|
-
skipNextMenuQuotaAutoRefresh = false;
|
|
2161
|
-
}
|
|
2162
|
-
if (shouldAutoFetchLimits &&
|
|
2163
|
-
!pendingMenuQuotaRefresh &&
|
|
2164
|
-
!shouldSkipAutoFetchThisPass) {
|
|
2165
|
-
const staleCount = countMenuQuotaRefreshTargets(currentStorage, quotaCache, quotaTtlMs);
|
|
2166
|
-
if (staleCount > 0) {
|
|
2167
|
-
if (showFetchStatus) {
|
|
2168
|
-
menuQuotaRefreshStatus = `${UI_COPY.mainMenu.loadingLimits} [0/${staleCount}]`;
|
|
2169
|
-
}
|
|
2170
|
-
const refreshGeneration = menuQuotaRefreshGeneration;
|
|
2171
|
-
pendingMenuQuotaRefresh = refreshQuotaCacheForMenu(currentStorage, quotaCache, quotaTtlMs, (current, total) => {
|
|
2172
|
-
if (!showFetchStatus)
|
|
2173
|
-
return;
|
|
2174
|
-
menuQuotaRefreshStatus = `${UI_COPY.mainMenu.loadingLimits} [${current}/${total}]`;
|
|
2175
|
-
})
|
|
2176
|
-
.then(() => {
|
|
2177
|
-
if (refreshGeneration === menuQuotaRefreshGeneration) {
|
|
2178
|
-
skipNextMenuQuotaAutoRefresh = true;
|
|
2179
|
-
}
|
|
2180
|
-
return undefined;
|
|
2181
|
-
})
|
|
2182
|
-
.catch(() => undefined)
|
|
2183
|
-
.finally(() => {
|
|
2184
|
-
menuQuotaRefreshStatus = undefined;
|
|
2185
|
-
pendingMenuQuotaRefresh = null;
|
|
2186
|
-
});
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
const flaggedStorage = await loadFlaggedAccounts();
|
|
2190
|
-
await syncCodexCliActiveSelectionIfDrifted(currentStorage);
|
|
2191
|
-
const runtimeCurrent = await loadRuntimeCurrentSelectionForStorage(currentStorage);
|
|
2192
|
-
const menuResult = await promptLoginMode(toExistingAccountInfo(currentStorage, quotaCache, displaySettings, runtimeCurrent), {
|
|
2193
|
-
flaggedCount: flaggedStorage.accounts.length,
|
|
2194
|
-
statusMessage: showFetchStatus
|
|
2195
|
-
? () => menuQuotaRefreshStatus
|
|
2196
|
-
: undefined,
|
|
2197
|
-
});
|
|
2198
|
-
if (menuResult.mode === "cancel") {
|
|
2199
|
-
console.log("Cancelled.");
|
|
2200
|
-
return 0;
|
|
2201
|
-
}
|
|
2202
|
-
if (menuResult.mode === "check") {
|
|
2203
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2204
|
-
await runActionPanel("Quick Check", "Checking local session + live status", async () => {
|
|
2205
|
-
await runHealthCheck({ forceRefresh: false, liveProbe: true });
|
|
2206
|
-
}, displaySettings);
|
|
2207
|
-
continue;
|
|
2208
|
-
}
|
|
2209
|
-
if (menuResult.mode === "deep-check") {
|
|
2210
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2211
|
-
await runActionPanel("Deep Check", "Refreshing and testing all accounts", async () => {
|
|
2212
|
-
await runHealthCheck({ forceRefresh: true, liveProbe: true });
|
|
2213
|
-
}, displaySettings);
|
|
2214
|
-
continue;
|
|
2215
|
-
}
|
|
2216
|
-
if (menuResult.mode === "forecast") {
|
|
2217
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2218
|
-
await runActionPanel("Best Account", "Comparing accounts", async () => {
|
|
2219
|
-
await runForecast(["--live"]);
|
|
2220
|
-
}, displaySettings);
|
|
2221
|
-
continue;
|
|
2222
|
-
}
|
|
2223
|
-
if (menuResult.mode === "fix") {
|
|
2224
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2225
|
-
await runActionPanel("Auto-Fix", "Checking and fixing common issues", async () => {
|
|
2226
|
-
await runRepairFix(["--live"], createRepairCommandDeps());
|
|
2227
|
-
}, displaySettings);
|
|
2228
|
-
continue;
|
|
2229
|
-
}
|
|
2230
|
-
if (menuResult.mode === "settings") {
|
|
2231
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2232
|
-
await configureUnifiedSettings(displaySettings);
|
|
2233
|
-
continue;
|
|
2234
|
-
}
|
|
2235
|
-
if (menuResult.mode === "verify-flagged") {
|
|
2236
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2237
|
-
await runActionPanel("Problem Account Check", "Checking problem accounts", async () => {
|
|
2238
|
-
await runRepairVerifyFlagged([], createRepairCommandDeps());
|
|
2239
|
-
}, displaySettings);
|
|
2240
|
-
continue;
|
|
2241
|
-
}
|
|
2242
|
-
if (menuResult.mode === "fresh" && menuResult.deleteAll) {
|
|
2243
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2244
|
-
await runActionPanel("Reset Accounts", "Deleting all saved accounts", async () => {
|
|
2245
|
-
await clearAccountsAndReset();
|
|
2246
|
-
console.log("Cleared saved accounts from active storage. Recovery snapshots remain available.");
|
|
2247
|
-
}, displaySettings);
|
|
2248
|
-
continue;
|
|
2249
|
-
}
|
|
2250
|
-
if (menuResult.mode === "manage") {
|
|
2251
|
-
clearMenuQuotaAutoRefreshSkip();
|
|
2252
|
-
const requiresInteractiveOAuth = typeof menuResult.refreshAccountIndex === "number";
|
|
2253
|
-
if (requiresInteractiveOAuth) {
|
|
2254
|
-
await handleManageAction(currentStorage, menuResult);
|
|
2255
|
-
continue;
|
|
2256
|
-
}
|
|
2257
|
-
await runActionPanel("Applying Change", "Updating selected account", async () => {
|
|
2258
|
-
await handleManageAction(currentStorage, menuResult);
|
|
2259
|
-
}, displaySettings);
|
|
2260
|
-
continue;
|
|
2261
|
-
}
|
|
2262
|
-
if (menuResult.mode === "add") {
|
|
2263
|
-
break;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
const refreshedStorage = await loadAccounts();
|
|
2268
|
-
let existingCount = refreshedStorage?.accounts.length ?? 0;
|
|
2269
|
-
let forceNewLogin = existingCount > 0;
|
|
2270
|
-
let onboardingBackupDiscoveryWarning = null;
|
|
2271
|
-
const loadNamedBackupsForOnboarding = async () => {
|
|
2272
|
-
if (existingCount > 0) {
|
|
2273
|
-
onboardingBackupDiscoveryWarning = null;
|
|
2274
|
-
return [];
|
|
2275
|
-
}
|
|
2276
|
-
try {
|
|
2277
|
-
onboardingBackupDiscoveryWarning = null;
|
|
2278
|
-
return await getNamedBackups();
|
|
2279
|
-
}
|
|
2280
|
-
catch (error) {
|
|
2281
|
-
const code = error.code;
|
|
2282
|
-
log.debug("getNamedBackups failed, skipping restore option", {
|
|
2283
|
-
code,
|
|
2284
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2285
|
-
});
|
|
2286
|
-
if (code && code !== "ENOENT") {
|
|
2287
|
-
onboardingBackupDiscoveryWarning =
|
|
2288
|
-
"Named backup discovery failed. Continuing with browser or manual sign-in only.";
|
|
2289
|
-
console.warn(onboardingBackupDiscoveryWarning);
|
|
2290
|
-
}
|
|
2291
|
-
else {
|
|
2292
|
-
onboardingBackupDiscoveryWarning = null;
|
|
2293
|
-
}
|
|
2294
|
-
return [];
|
|
2295
|
-
}
|
|
2296
|
-
};
|
|
2297
|
-
let namedBackups = await loadNamedBackupsForOnboarding();
|
|
2298
|
-
while (true) {
|
|
2299
|
-
const latestNamedBackup = namedBackups[0] ?? null;
|
|
2300
|
-
const preferManualMode = loginOptions.manual || isBrowserLaunchSuppressed();
|
|
2301
|
-
const signInMode = loginOptions.deviceAuth
|
|
2302
|
-
? "device"
|
|
2303
|
-
: preferManualMode
|
|
2304
|
-
? "manual"
|
|
2305
|
-
: await promptOAuthSignInMode(latestNamedBackup, onboardingBackupDiscoveryWarning);
|
|
2306
|
-
if (signInMode === "cancel") {
|
|
2307
|
-
if (existingCount > 0) {
|
|
2308
|
-
console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
|
|
2309
|
-
continue loginFlow;
|
|
2310
|
-
}
|
|
2311
|
-
console.log("Cancelled.");
|
|
2312
|
-
return 0;
|
|
2313
|
-
}
|
|
2314
|
-
if (signInMode === "restore-backup") {
|
|
2315
|
-
const latestAvailableBackup = namedBackups[0] ?? null;
|
|
2316
|
-
if (!latestAvailableBackup) {
|
|
2317
|
-
namedBackups = await loadNamedBackupsForOnboarding();
|
|
2318
|
-
continue;
|
|
2319
|
-
}
|
|
2320
|
-
const restoreMode = await promptBackupRestoreMode(latestAvailableBackup);
|
|
2321
|
-
if (restoreMode === "back") {
|
|
2322
|
-
namedBackups = await loadNamedBackupsForOnboarding();
|
|
2323
|
-
continue;
|
|
2324
|
-
}
|
|
2325
|
-
const selectedBackup = restoreMode === "manual"
|
|
2326
|
-
? await promptManualBackupSelection(namedBackups)
|
|
2327
|
-
: latestAvailableBackup;
|
|
2328
|
-
if (!selectedBackup) {
|
|
2329
|
-
namedBackups = await loadNamedBackupsForOnboarding();
|
|
2330
|
-
continue;
|
|
2331
|
-
}
|
|
2332
|
-
const confirmed = await confirm(UI_COPY.oauth.restoreBackupConfirm(selectedBackup.fileName, selectedBackup.accountCount));
|
|
2333
|
-
if (!confirmed) {
|
|
2334
|
-
namedBackups = await loadNamedBackupsForOnboarding();
|
|
2335
|
-
continue;
|
|
2336
|
-
}
|
|
2337
|
-
const displaySettings = await loadDashboardDisplaySettings();
|
|
2338
|
-
applyUiThemeFromDashboardSettings(displaySettings);
|
|
2339
|
-
try {
|
|
2340
|
-
await runActionPanel("Load Backup", `Loading ${selectedBackup.fileName}`, async () => {
|
|
2341
|
-
const restoredStorage = await restoreAccountsFromBackup(selectedBackup.path, { persist: false });
|
|
2342
|
-
const targetIndex = resolveActiveIndex(restoredStorage);
|
|
2343
|
-
const { synced } = await persistAndSyncSelectedAccount({
|
|
2344
|
-
storage: restoredStorage,
|
|
2345
|
-
targetIndex,
|
|
2346
|
-
parsed: targetIndex + 1,
|
|
2347
|
-
switchReason: "restore",
|
|
2348
|
-
preserveActiveIndexByFamily: true,
|
|
2349
|
-
});
|
|
2350
|
-
console.log(UI_COPY.oauth.restoreBackupLoaded(selectedBackup.fileName, restoredStorage.accounts.length));
|
|
2351
|
-
if (!synced) {
|
|
2352
|
-
console.warn(UI_COPY.oauth.restoreBackupSyncWarning);
|
|
2353
|
-
}
|
|
2354
|
-
}, displaySettings);
|
|
2355
|
-
}
|
|
2356
|
-
catch (error) {
|
|
2357
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2358
|
-
if (error instanceof StorageError) {
|
|
2359
|
-
console.error(formatStorageErrorHint(error, selectedBackup.path));
|
|
2360
|
-
}
|
|
2361
|
-
else {
|
|
2362
|
-
console.error(`Backup restore failed: ${message}`);
|
|
2363
|
-
}
|
|
2364
|
-
const storageAfterRestoreAttempt = await loadAccounts().catch(() => null);
|
|
2365
|
-
if ((storageAfterRestoreAttempt?.accounts.length ?? 0) > 0) {
|
|
2366
|
-
continue loginFlow;
|
|
2367
|
-
}
|
|
2368
|
-
namedBackups = await loadNamedBackupsForOnboarding();
|
|
2369
|
-
continue;
|
|
2370
|
-
}
|
|
2371
|
-
continue loginFlow;
|
|
2372
|
-
}
|
|
2373
|
-
if (signInMode !== "browser" &&
|
|
2374
|
-
signInMode !== "manual" &&
|
|
2375
|
-
signInMode !== "device") {
|
|
2376
|
-
continue;
|
|
2377
|
-
}
|
|
2378
|
-
const tokenResult = await runSignInFlow(forceNewLogin, signInMode);
|
|
2379
|
-
if (tokenResult.type !== "success") {
|
|
2380
|
-
if (isOAuthCancellation(tokenResult)) {
|
|
2381
|
-
// In explicit-mode invocations the dashboard was bypassed,
|
|
2382
|
-
// so falling back to it on cancel would re-enter the same
|
|
2383
|
-
// transport flow and trap the user. Exit cleanly instead.
|
|
2384
|
-
if (explicitSignInMode) {
|
|
2385
|
-
console.log("Cancelled.");
|
|
2386
|
-
return 0;
|
|
2387
|
-
}
|
|
2388
|
-
if (existingCount > 0) {
|
|
2389
|
-
console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
|
|
2390
|
-
continue loginFlow;
|
|
2391
|
-
}
|
|
2392
|
-
console.log("Cancelled.");
|
|
2393
|
-
return 0;
|
|
2394
|
-
}
|
|
2395
|
-
console.error(`Login failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
|
|
2396
|
-
return 1;
|
|
2397
|
-
}
|
|
2398
|
-
const resolved = resolveAccountSelection(tokenResult, loginOptions.org);
|
|
2399
|
-
await persistAccountPool([resolved], false);
|
|
2400
|
-
await syncSelectionToCodex(resolved);
|
|
2401
|
-
const latestStorage = await loadAccounts();
|
|
2402
|
-
const count = latestStorage?.accounts.length ?? 1;
|
|
2403
|
-
existingCount = count;
|
|
2404
|
-
namedBackups = [];
|
|
2405
|
-
onboardingBackupDiscoveryWarning = null;
|
|
2406
|
-
console.log(`Added account. Total: ${count}`);
|
|
2407
|
-
console.log("Next steps:");
|
|
2408
|
-
console.log(" codex-multi-auth status Check that the wrapper is active.");
|
|
2409
|
-
console.log(" codex-multi-auth check Confirm your saved accounts look healthy.");
|
|
2410
|
-
console.log(" codex-multi-auth list Review saved accounts before switching.");
|
|
2411
|
-
if (count >= ACCOUNT_LIMITS.MAX_ACCOUNTS) {
|
|
2412
|
-
console.log(`Reached maximum account limit (${ACCOUNT_LIMITS.MAX_ACCOUNTS}).`);
|
|
2413
|
-
// Same reasoning as the addAnother=false branch below: in
|
|
2414
|
-
// explicit mode the dashboard is bypassed, so falling out of
|
|
2415
|
-
// the inner loop would re-enter loginFlow and silently start
|
|
2416
|
-
// another sign-in session despite the cap.
|
|
2417
|
-
if (explicitSignInMode) {
|
|
2418
|
-
return 0;
|
|
2419
|
-
}
|
|
2420
|
-
break;
|
|
2421
|
-
}
|
|
2422
|
-
const addAnother = await promptAddAnotherAccount(count);
|
|
2423
|
-
if (!addAnother) {
|
|
2424
|
-
// With an explicit transport flag the dashboard was bypassed,
|
|
2425
|
-
// so falling back to it after declining would loop into a fresh
|
|
2426
|
-
// sign-in instead of exiting. Return directly in that case.
|
|
2427
|
-
if (explicitSignInMode) {
|
|
2428
|
-
return 0;
|
|
2429
|
-
}
|
|
2430
|
-
break;
|
|
2431
|
-
}
|
|
2432
|
-
forceNewLogin = true;
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
async function persistAndSyncSelectedAccount({ storage, targetIndex, parsed, switchReason, initialSyncIdToken, preserveActiveIndexByFamily = false, setPin = false, clearPin = false, bumpAffinityGeneration = false, }) {
|
|
2437
|
-
const account = storage.accounts[targetIndex];
|
|
2438
|
-
if (!account) {
|
|
2439
|
-
throw new Error(`Account ${parsed} not found.`);
|
|
2440
|
-
}
|
|
2441
|
-
const shouldPreserveActiveIndexByFamily = preserveActiveIndexByFamily &&
|
|
2442
|
-
!!storage.activeIndexByFamily &&
|
|
2443
|
-
targetIndex === storage.activeIndex;
|
|
2444
|
-
storage.activeIndex = targetIndex;
|
|
2445
|
-
storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
|
|
2446
|
-
if (shouldPreserveActiveIndexByFamily) {
|
|
2447
|
-
const maxIndex = Math.max(0, storage.accounts.length - 1);
|
|
2448
|
-
for (const family of MODEL_FAMILIES) {
|
|
2449
|
-
const raw = storage.activeIndexByFamily[family];
|
|
2450
|
-
const candidate = typeof raw === "number" && Number.isFinite(raw) ? raw : targetIndex;
|
|
2451
|
-
storage.activeIndexByFamily[family] = Math.max(0, Math.min(candidate, maxIndex));
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
else {
|
|
2455
|
-
storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
|
|
2456
|
-
for (const family of MODEL_FAMILIES) {
|
|
2457
|
-
storage.activeIndexByFamily[family] = targetIndex;
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
const wasDisabled = account.enabled === false;
|
|
2461
|
-
if (wasDisabled) {
|
|
2462
|
-
account.enabled = true;
|
|
2463
|
-
}
|
|
2464
|
-
const switchNow = Date.now();
|
|
2465
|
-
let syncAccessToken = account.accessToken;
|
|
2466
|
-
let syncRefreshToken = account.refreshToken;
|
|
2467
|
-
let syncExpiresAt = account.expiresAt;
|
|
2468
|
-
let syncIdToken = initialSyncIdToken;
|
|
2469
|
-
if (!hasUsableAccessToken(account, switchNow)) {
|
|
2470
|
-
const refreshResult = await queuedRefresh(account.refreshToken);
|
|
2471
|
-
if (refreshResult.type === "success") {
|
|
2472
|
-
const tokenAccountId = extractAccountId(refreshResult.access);
|
|
2473
|
-
const nextEmail = sanitizeEmail(extractAccountEmail(refreshResult.access, refreshResult.idToken));
|
|
2474
|
-
if (account.refreshToken !== refreshResult.refresh) {
|
|
2475
|
-
account.refreshToken = refreshResult.refresh;
|
|
2476
|
-
}
|
|
2477
|
-
if (account.accessToken !== refreshResult.access) {
|
|
2478
|
-
account.accessToken = refreshResult.access;
|
|
2479
|
-
}
|
|
2480
|
-
if (account.expiresAt !== refreshResult.expires) {
|
|
2481
|
-
account.expiresAt = refreshResult.expires;
|
|
2482
|
-
}
|
|
2483
|
-
if (nextEmail && nextEmail !== account.email) {
|
|
2484
|
-
account.email = nextEmail;
|
|
2485
|
-
}
|
|
2486
|
-
applyTokenAccountIdentity(account, tokenAccountId);
|
|
2487
|
-
syncAccessToken = refreshResult.access;
|
|
2488
|
-
syncRefreshToken = refreshResult.refresh;
|
|
2489
|
-
syncExpiresAt = refreshResult.expires;
|
|
2490
|
-
syncIdToken = refreshResult.idToken;
|
|
2491
|
-
}
|
|
2492
|
-
else {
|
|
2493
|
-
console.warn(`Switch validation refresh failed for account ${parsed}: ${normalizeFailureDetail(refreshResult.message, refreshResult.reason)}.`);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
account.lastUsed = switchNow;
|
|
2497
|
-
account.lastSwitchReason = switchReason;
|
|
2498
|
-
if (setPin) {
|
|
2499
|
-
storage.pinnedAccountIndex = targetIndex;
|
|
2500
|
-
}
|
|
2501
|
-
else if (clearPin) {
|
|
2502
|
-
delete storage.pinnedAccountIndex;
|
|
2503
|
-
}
|
|
2504
|
-
if (bumpAffinityGeneration) {
|
|
2505
|
-
// Re-read the on-disk generation right before saving so concurrent CLI
|
|
2506
|
-
// processes don't lose increments via lost-update on the load+mutate
|
|
2507
|
-
// pair. Math.max keeps the counter monotonically increasing — extra
|
|
2508
|
-
// bumps are harmless (just an additional affinity invalidation), but a
|
|
2509
|
-
// missed bump can let the proxy cling to the wrong account. The save
|
|
2510
|
-
// itself is serialized by withStorageLock; this only narrows the
|
|
2511
|
-
// lost-update window. See issue #474.
|
|
2512
|
-
const diskGeneration = readAffinityGenerationFromDisk(getStoragePath());
|
|
2513
|
-
const inMemoryGeneration = storage.affinityGeneration ?? 0;
|
|
2514
|
-
storage.affinityGeneration =
|
|
2515
|
-
Math.max(inMemoryGeneration, diskGeneration) + 1;
|
|
2516
|
-
}
|
|
2517
|
-
await saveAccountsWithRetry(storage, saveAccounts);
|
|
2518
|
-
const synced = await setCodexCliActiveSelection({
|
|
2519
|
-
accountId: account.accountId,
|
|
2520
|
-
email: account.email,
|
|
2521
|
-
accessToken: syncAccessToken,
|
|
2522
|
-
refreshToken: syncRefreshToken,
|
|
2523
|
-
expiresAt: syncExpiresAt,
|
|
2524
|
-
...(syncIdToken ? { idToken: syncIdToken } : {}),
|
|
2525
|
-
});
|
|
2526
|
-
return { synced, wasDisabled };
|
|
2527
|
-
}
|
|
2528
149
|
async function runBest(args) {
|
|
2529
150
|
return runBestCommand(args, {
|
|
2530
151
|
setStoragePath,
|
|
@@ -2674,117 +295,103 @@ function buildSelectAccountTraced() {
|
|
|
2674
295
|
});
|
|
2675
296
|
};
|
|
2676
297
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
loadAppBindStatus: async () => getAppBindStatus()
|
|
2714
|
-
.then((status) => (status.running ? status.router : null))
|
|
2715
|
-
.catch(() => null),
|
|
2716
|
-
loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
|
|
2717
|
-
loadQuotaCache,
|
|
2718
|
-
json: rest.includes("--json") || rest.includes("-j"),
|
|
2719
|
-
});
|
|
2720
|
-
}
|
|
2721
|
-
if (command === "switch") {
|
|
2722
|
-
return runSwitchCommand(rest, {
|
|
298
|
+
/**
|
|
299
|
+
* Shared handler for `list` and `status` (aliases of the same view).
|
|
300
|
+
*/
|
|
301
|
+
const runListOrStatusCommand = (rest) => runStatusCommand({
|
|
302
|
+
setStoragePath,
|
|
303
|
+
getStoragePath,
|
|
304
|
+
loadAccounts,
|
|
305
|
+
inspectStorageHealth,
|
|
306
|
+
resolveActiveIndex,
|
|
307
|
+
formatRateLimitEntry,
|
|
308
|
+
loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
|
|
309
|
+
loadAppBindStatus: async () => getAppBindStatus()
|
|
310
|
+
.then((status) => (status.running ? status.router : null))
|
|
311
|
+
.catch(() => null),
|
|
312
|
+
loadAppHelperStatus: readAppRuntimeHelperAccountSignal,
|
|
313
|
+
loadQuotaCache,
|
|
314
|
+
json: rest.includes("--json") || rest.includes("-j"),
|
|
315
|
+
});
|
|
316
|
+
/**
|
|
317
|
+
* Command registry for the `auth` dispatcher (audit roadmap §4.1.1 phase 2).
|
|
318
|
+
*
|
|
319
|
+
* Replaces the former `if (command === …)` chain in runCodexMultiAuthCli with
|
|
320
|
+
* a lookup map. Aliases (`status` → `list` view) point at the same handler.
|
|
321
|
+
* Per-invocation dependency factories (createRepairCommandDeps,
|
|
322
|
+
* buildSelectAccountTraced) are still invoked inside each handler so they are
|
|
323
|
+
* constructed at dispatch time, not at module load — identical to the old
|
|
324
|
+
* chain. Keys are exact-match and unique, so lookup order cannot change
|
|
325
|
+
* semantics relative to the sequential chain it replaces.
|
|
326
|
+
*/
|
|
327
|
+
const CLI_COMMAND_HANDLERS = new Map([
|
|
328
|
+
["login", (rest) => runAuthLogin(rest, { runForecast, createRepairCommandDeps })],
|
|
329
|
+
["list", runListOrStatusCommand],
|
|
330
|
+
["status", runListOrStatusCommand],
|
|
331
|
+
[
|
|
332
|
+
"switch",
|
|
333
|
+
(rest) => runSwitchCommand(rest, {
|
|
2723
334
|
setStoragePath,
|
|
2724
335
|
loadAccounts,
|
|
2725
336
|
persistAndSyncSelectedAccount,
|
|
2726
|
-
})
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
337
|
+
}),
|
|
338
|
+
],
|
|
339
|
+
[
|
|
340
|
+
"unpin",
|
|
341
|
+
() => runUnpinCommand({
|
|
2730
342
|
setStoragePath,
|
|
2731
343
|
loadAccounts,
|
|
2732
344
|
saveAccounts,
|
|
2733
345
|
getStoragePath,
|
|
2734
|
-
})
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
346
|
+
}),
|
|
347
|
+
],
|
|
348
|
+
[
|
|
349
|
+
"workspace",
|
|
350
|
+
(rest) => runWorkspaceCommand(rest, {
|
|
2738
351
|
setStoragePath,
|
|
2739
352
|
loadAccounts,
|
|
2740
353
|
saveAccounts,
|
|
2741
|
-
})
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
}
|
|
2758
|
-
if (command === "account") {
|
|
2759
|
-
return runAccountCommand(rest, {
|
|
354
|
+
}),
|
|
355
|
+
],
|
|
356
|
+
["check", () => runCheckCommand({ runHealthCheck })],
|
|
357
|
+
[
|
|
358
|
+
"features",
|
|
359
|
+
() => runFeaturesCommand({ implementedFeatures: IMPLEMENTED_FEATURES }),
|
|
360
|
+
],
|
|
361
|
+
[
|
|
362
|
+
"verify-flagged",
|
|
363
|
+
(rest) => runRepairVerifyFlagged(rest, createRepairCommandDeps()),
|
|
364
|
+
],
|
|
365
|
+
["forecast", (rest) => runForecast(rest)],
|
|
366
|
+
["best", (rest) => runBest(rest)],
|
|
367
|
+
[
|
|
368
|
+
"account",
|
|
369
|
+
(rest) => runAccountCommand(rest, {
|
|
2760
370
|
setStoragePath,
|
|
2761
371
|
loadAccounts,
|
|
2762
|
-
})
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
if (command === "integrations") {
|
|
2771
|
-
return runIntegrationsCommand(rest);
|
|
2772
|
-
}
|
|
2773
|
-
if (command === "models") {
|
|
2774
|
-
return runModelsCommand(rest, {
|
|
372
|
+
}),
|
|
373
|
+
],
|
|
374
|
+
["budget", (rest) => runBudgetCommand(rest)],
|
|
375
|
+
["bridge", (rest) => runBridgeCommand(rest)],
|
|
376
|
+
["integrations", (rest) => runIntegrationsCommand(rest)],
|
|
377
|
+
[
|
|
378
|
+
"models",
|
|
379
|
+
(rest) => runModelsCommand(rest, {
|
|
2775
380
|
setStoragePath,
|
|
2776
381
|
loadAccounts,
|
|
2777
382
|
loadQuotaCache,
|
|
2778
|
-
})
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
383
|
+
}),
|
|
384
|
+
],
|
|
385
|
+
[
|
|
386
|
+
"monitor",
|
|
387
|
+
(rest) => runMonitorCommand(rest, {
|
|
2782
388
|
setStoragePath,
|
|
2783
389
|
loadAccounts,
|
|
2784
|
-
})
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
390
|
+
}),
|
|
391
|
+
],
|
|
392
|
+
[
|
|
393
|
+
"report",
|
|
394
|
+
(rest) => runReportCommand(rest, {
|
|
2788
395
|
setStoragePath,
|
|
2789
396
|
getStoragePath,
|
|
2790
397
|
loadAccounts,
|
|
@@ -2798,13 +405,12 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
2798
405
|
normalizeFailureDetail,
|
|
2799
406
|
loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
|
|
2800
407
|
loadQuotaCache,
|
|
2801
|
-
})
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
return runRotationCommand(rest, {
|
|
408
|
+
}),
|
|
409
|
+
],
|
|
410
|
+
["usage", (rest) => runUsageCommand(rest)],
|
|
411
|
+
[
|
|
412
|
+
"rotation",
|
|
413
|
+
(rest) => runRotationCommand(rest, {
|
|
2808
414
|
loadPluginConfig,
|
|
2809
415
|
savePluginConfig,
|
|
2810
416
|
getCodexRuntimeRotationProxy,
|
|
@@ -2818,10 +424,11 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
2818
424
|
getCodexAppBindStatus: getAppBindStatus,
|
|
2819
425
|
loadRuntimeObservabilitySnapshot: loadPersistedRuntimeObservabilitySnapshot,
|
|
2820
426
|
loadQuotaCache,
|
|
2821
|
-
})
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
427
|
+
}),
|
|
428
|
+
],
|
|
429
|
+
[
|
|
430
|
+
"why-selected",
|
|
431
|
+
(rest) => runWhySelectedCommand(rest, {
|
|
2825
432
|
parseWhySelectedArgs,
|
|
2826
433
|
printWhySelectedUsage,
|
|
2827
434
|
setStoragePath,
|
|
@@ -2841,10 +448,11 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
2841
448
|
return { generatedAt };
|
|
2842
449
|
},
|
|
2843
450
|
sanitizeEmail,
|
|
2844
|
-
})
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
451
|
+
}),
|
|
452
|
+
],
|
|
453
|
+
[
|
|
454
|
+
"verify",
|
|
455
|
+
(rest) => runVerifyCommand(rest, {
|
|
2848
456
|
parseVerifyArgs,
|
|
2849
457
|
printVerifyUsage,
|
|
2850
458
|
runVerifyFlagged: async (flaggedArgs) => runRepairVerifyFlagged(flaggedArgs, createRepairCommandDeps()),
|
|
@@ -2858,50 +466,83 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
2858
466
|
getProjectGlobalConfigDir,
|
|
2859
467
|
resolvePath: resolveStoragePath,
|
|
2860
468
|
},
|
|
2861
|
-
})
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
|
|
469
|
+
}),
|
|
470
|
+
],
|
|
471
|
+
["fix", (rest) => runRepairFix(rest, createRepairCommandDeps())],
|
|
472
|
+
["doctor", (rest) => runRepairDoctor(rest, createRepairCommandDeps())],
|
|
473
|
+
["uninstall", (rest) => runUninstallCommand(rest, { clearAccounts })],
|
|
474
|
+
[
|
|
475
|
+
"config",
|
|
476
|
+
(rest) => {
|
|
477
|
+
const [subcommand, ...configArgs] = rest;
|
|
478
|
+
if (subcommand === "explain") {
|
|
479
|
+
return runConfigExplainCommand(configArgs, {
|
|
480
|
+
getReport: getPluginConfigExplainReport,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
if (subcommand === "template") {
|
|
484
|
+
return runInitConfigCommand(configArgs);
|
|
485
|
+
}
|
|
486
|
+
console.error(`Unknown config command: ${subcommand ?? "(missing)"}`);
|
|
487
|
+
return 1;
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
["init-config", (rest) => runInitConfigCommand(rest)],
|
|
491
|
+
[
|
|
492
|
+
"debug",
|
|
493
|
+
(rest) => {
|
|
494
|
+
const [subcommand, ...debugArgs] = rest;
|
|
495
|
+
if (subcommand === "bundle") {
|
|
496
|
+
return runDebugBundleCommand(debugArgs, {
|
|
497
|
+
getConfigReport: getPluginConfigExplainReport,
|
|
498
|
+
getStoragePath,
|
|
499
|
+
loadAccounts,
|
|
500
|
+
loadFlaggedAccounts,
|
|
501
|
+
loadCodexCliState,
|
|
502
|
+
getLastAccountsSaveTimestamp,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
console.error(`Unknown debug command: ${subcommand ?? "(missing)"}`);
|
|
506
|
+
return 1;
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
]);
|
|
510
|
+
export async function runCodexMultiAuthCli(rawArgs) {
|
|
511
|
+
// Lazy install setup (audit roadmap §4.5.4): app detection, Codex app bind,
|
|
512
|
+
// and launcher routing moved out of npm postinstall to the first CLI run.
|
|
513
|
+
// ensureFirstRunSetup never throws; the catch is belt-and-braces so no
|
|
514
|
+
// command can ever fail because of first-run housekeeping.
|
|
515
|
+
await ensureFirstRunSetup({
|
|
516
|
+
notify: (message) => console.error(`codex-multi-auth: ${message}`),
|
|
517
|
+
}).catch(() => undefined);
|
|
518
|
+
const startupDisplaySettings = await loadDashboardDisplaySettings();
|
|
519
|
+
applyUiThemeFromDashboardSettings(startupDisplaySettings);
|
|
520
|
+
const args = rawArgs[0] && rawArgs[0] !== "auth" && ACCOUNT_MANAGER_COMMANDS.has(rawArgs[0])
|
|
521
|
+
? ["auth", ...rawArgs]
|
|
522
|
+
: [...rawArgs];
|
|
523
|
+
if (args.length === 0) {
|
|
524
|
+
printUsage();
|
|
525
|
+
return 0;
|
|
2868
526
|
}
|
|
2869
|
-
if (
|
|
2870
|
-
|
|
527
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
528
|
+
printUsage();
|
|
529
|
+
return 0;
|
|
2871
530
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
return runConfigExplainCommand(configArgs, {
|
|
2876
|
-
getReport: getPluginConfigExplainReport,
|
|
2877
|
-
});
|
|
2878
|
-
}
|
|
2879
|
-
if (subcommand === "template") {
|
|
2880
|
-
return runInitConfigCommand(configArgs);
|
|
2881
|
-
}
|
|
2882
|
-
console.error(`Unknown config command: ${subcommand ?? "(missing)"}`);
|
|
531
|
+
const [root, sub, ...rest] = args;
|
|
532
|
+
if (root !== "auth") {
|
|
533
|
+
printUsage();
|
|
2883
534
|
return 1;
|
|
2884
535
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
536
|
+
const command = sub ?? "login";
|
|
537
|
+
if (command === "--help" || command === "-h") {
|
|
538
|
+
printUsage();
|
|
539
|
+
return 0;
|
|
2887
540
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
return runDebugBundleCommand(debugArgs, {
|
|
2892
|
-
getConfigReport: getPluginConfigExplainReport,
|
|
2893
|
-
getStoragePath,
|
|
2894
|
-
loadAccounts,
|
|
2895
|
-
loadFlaggedAccounts,
|
|
2896
|
-
loadCodexCliState,
|
|
2897
|
-
getLastAccountsSaveTimestamp,
|
|
2898
|
-
});
|
|
2899
|
-
}
|
|
2900
|
-
console.error(`Unknown debug command: ${subcommand ?? "(missing)"}`);
|
|
2901
|
-
return 1;
|
|
541
|
+
const handler = CLI_COMMAND_HANDLERS.get(command);
|
|
542
|
+
if (handler) {
|
|
543
|
+
return handler(rest);
|
|
2902
544
|
}
|
|
2903
545
|
console.error(`Unknown command: ${command}`);
|
|
2904
546
|
printUsage();
|
|
2905
547
|
return 1;
|
|
2906
548
|
}
|
|
2907
|
-
//# sourceMappingURL=codex-manager.js.map
|