@cortexkit/opencode-antigravity-auth 1.1.0 → 2.0.1
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/README.md +225 -569
- package/dist/cli.js +4017 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20742 -14586
- package/dist/index.js.map +4 -4
- package/dist/src/antigravity/oauth.d.ts +1 -1
- package/dist/src/antigravity/oauth.js +1 -1
- package/dist/src/cli.d.ts +21 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +237 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/cache.js +13 -13
- package/dist/src/hooks/auto-update-checker/cache.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/checker.d.ts +1 -1
- package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/checker.js +75 -51
- package/dist/src/hooks/auto-update-checker/checker.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/constants.js +12 -12
- package/dist/src/hooks/auto-update-checker/constants.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/index.d.ts +6 -6
- package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/src/hooks/auto-update-checker/index.js +17 -17
- package/dist/src/hooks/auto-update-checker/index.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/logging.js +2 -2
- package/dist/src/hooks/auto-update-checker/logging.js.map +1 -1
- package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -1
- package/dist/src/plugin/account-access.d.ts +99 -0
- package/dist/src/plugin/account-access.d.ts.map +1 -0
- package/dist/src/plugin/account-access.js +480 -0
- package/dist/src/plugin/account-access.js.map +1 -0
- package/dist/src/plugin/account-command-oauth.d.ts +34 -0
- package/dist/src/plugin/account-command-oauth.d.ts.map +1 -0
- package/dist/src/plugin/account-command-oauth.js +156 -0
- package/dist/src/plugin/account-command-oauth.js.map +1 -0
- package/dist/src/plugin/accounts.d.ts +7 -256
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +31 -1367
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/agy-request-metadata.d.ts +1 -1
- package/dist/src/plugin/agy-request-metadata.js +1 -1
- package/dist/src/plugin/agy-transport.d.ts +1 -1
- package/dist/src/plugin/agy-transport.js +1 -1
- package/dist/src/plugin/auth-doctor.d.ts +6 -6
- package/dist/src/plugin/auth-doctor.d.ts.map +1 -1
- package/dist/src/plugin/auth-doctor.js +74 -66
- package/dist/src/plugin/auth-doctor.js.map +1 -1
- package/dist/src/plugin/auth-drift.d.ts +4 -4
- package/dist/src/plugin/auth-drift.d.ts.map +1 -1
- package/dist/src/plugin/auth-drift.js +15 -15
- package/dist/src/plugin/auth-drift.js.map +1 -1
- package/dist/src/plugin/auth-loader.d.ts +50 -0
- package/dist/src/plugin/auth-loader.d.ts.map +1 -0
- package/dist/src/plugin/auth-loader.js +225 -0
- package/dist/src/plugin/auth-loader.js.map +1 -0
- package/dist/src/plugin/auth.d.ts +1 -1
- package/dist/src/plugin/auth.js +1 -1
- package/dist/src/plugin/background-quota-refresh.d.ts +146 -0
- package/dist/src/plugin/background-quota-refresh.d.ts.map +1 -0
- package/dist/src/plugin/background-quota-refresh.js +347 -0
- package/dist/src/plugin/background-quota-refresh.js.map +1 -0
- package/dist/src/plugin/cache/index.d.ts +1 -1
- package/dist/src/plugin/cache/index.d.ts.map +1 -1
- package/dist/src/plugin/cache/index.js +1 -1
- package/dist/src/plugin/cache/index.js.map +1 -1
- package/dist/src/plugin/cache/signature-cache.d.ts +1 -1
- package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -1
- package/dist/src/plugin/cache/signature-cache.js +23 -24
- package/dist/src/plugin/cache/signature-cache.js.map +1 -1
- package/dist/src/plugin/cache.d.ts +6 -5
- package/dist/src/plugin/cache.d.ts.map +1 -1
- package/dist/src/plugin/cache.js +21 -7
- package/dist/src/plugin/cache.js.map +1 -1
- package/dist/src/plugin/catalog.d.ts +23 -0
- package/dist/src/plugin/catalog.d.ts.map +1 -0
- package/dist/src/plugin/catalog.js +60 -0
- package/dist/src/plugin/catalog.js.map +1 -0
- package/dist/src/plugin/cli.d.ts +7 -7
- package/dist/src/plugin/cli.d.ts.map +1 -1
- package/dist/src/plugin/cli.js +62 -55
- package/dist/src/plugin/cli.js.map +1 -1
- package/dist/src/plugin/command-data.d.ts +329 -0
- package/dist/src/plugin/command-data.d.ts.map +1 -0
- package/dist/src/plugin/command-data.js +517 -0
- package/dist/src/plugin/command-data.js.map +1 -0
- package/dist/src/plugin/commands.d.ts +154 -0
- package/dist/src/plugin/commands.d.ts.map +1 -0
- package/dist/src/plugin/commands.js +746 -0
- package/dist/src/plugin/commands.js.map +1 -0
- package/dist/src/plugin/config/index.d.ts +2 -2
- package/dist/src/plugin/config/index.d.ts.map +1 -1
- package/dist/src/plugin/config/index.js +2 -2
- package/dist/src/plugin/config/index.js.map +1 -1
- package/dist/src/plugin/config/loader.d.ts +1 -1
- package/dist/src/plugin/config/loader.d.ts.map +1 -1
- package/dist/src/plugin/config/loader.js +18 -16
- package/dist/src/plugin/config/loader.js.map +1 -1
- package/dist/src/plugin/config/models.d.ts +2 -2
- package/dist/src/plugin/config/models.d.ts.map +1 -1
- package/dist/src/plugin/config/models.js +1 -1
- package/dist/src/plugin/config/models.js.map +1 -1
- package/dist/src/plugin/config/operator-settings-schema.d.ts +29 -0
- package/dist/src/plugin/config/operator-settings-schema.d.ts.map +1 -0
- package/dist/src/plugin/config/operator-settings-schema.js +28 -0
- package/dist/src/plugin/config/operator-settings-schema.js.map +1 -0
- package/dist/src/plugin/config/schema.d.ts +23 -2
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +109 -25
- package/dist/src/plugin/config/schema.js.map +1 -1
- package/dist/src/plugin/config/updater.d.ts.map +1 -1
- package/dist/src/plugin/config/updater.js +15 -15
- package/dist/src/plugin/config/updater.js.map +1 -1
- package/dist/src/plugin/config/writer.d.ts +34 -0
- package/dist/src/plugin/config/writer.d.ts.map +1 -0
- package/dist/src/plugin/config/writer.js +81 -0
- package/dist/src/plugin/config/writer.js.map +1 -0
- package/dist/src/plugin/core/streaming/index.d.ts +1 -1
- package/dist/src/plugin/core/streaming/index.d.ts.map +1 -1
- package/dist/src/plugin/core/streaming/index.js +1 -1
- package/dist/src/plugin/core/streaming/index.js.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -1
- package/dist/src/plugin/core/streaming/transformer.js +85 -44
- package/dist/src/plugin/core/streaming/transformer.js.map +1 -1
- package/dist/src/plugin/core/streaming/types.d.ts.map +1 -1
- package/dist/src/plugin/debug.d.ts +10 -3
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +110 -47
- package/dist/src/plugin/debug.js.map +1 -1
- package/dist/src/plugin/dependencies.d.ts +129 -0
- package/dist/src/plugin/dependencies.d.ts.map +1 -0
- package/dist/src/plugin/dependencies.js +107 -0
- package/dist/src/plugin/dependencies.js.map +1 -0
- package/dist/src/plugin/errors.d.ts +27 -0
- package/dist/src/plugin/errors.d.ts.map +1 -1
- package/dist/src/plugin/errors.js +29 -3
- package/dist/src/plugin/errors.js.map +1 -1
- package/dist/src/plugin/event-handler.d.ts +53 -0
- package/dist/src/plugin/event-handler.d.ts.map +1 -0
- package/dist/src/plugin/event-handler.js +101 -0
- package/dist/src/plugin/event-handler.js.map +1 -0
- package/dist/src/plugin/fetch/retry-state.d.ts +61 -0
- package/dist/src/plugin/fetch/retry-state.d.ts.map +1 -0
- package/dist/src/plugin/fetch/retry-state.js +248 -0
- package/dist/src/plugin/fetch/retry-state.js.map +1 -0
- package/dist/src/plugin/fetch/warmup.d.ts +30 -0
- package/dist/src/plugin/fetch/warmup.d.ts.map +1 -0
- package/dist/src/plugin/fetch/warmup.js +76 -0
- package/dist/src/plugin/fetch/warmup.js.map +1 -0
- package/dist/src/plugin/fetch-interceptor.d.ts +59 -0
- package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -0
- package/dist/src/plugin/fetch-interceptor.js +1399 -0
- package/dist/src/plugin/fetch-interceptor.js.map +1 -0
- package/dist/src/plugin/fetch-routing.d.ts +26 -0
- package/dist/src/plugin/fetch-routing.d.ts.map +1 -0
- package/dist/src/plugin/fetch-routing.js +99 -0
- package/dist/src/plugin/fetch-routing.js.map +1 -0
- package/dist/src/plugin/fingerprint.d.ts +1 -1
- package/dist/src/plugin/fingerprint.js +1 -1
- package/dist/src/plugin/gemini-dump.d.ts +5 -5
- package/dist/src/plugin/gemini-dump.d.ts.map +1 -1
- package/dist/src/plugin/gemini-dump.js +109 -55
- package/dist/src/plugin/gemini-dump.js.map +1 -1
- package/dist/src/plugin/google-search-tool.d.ts +9 -0
- package/dist/src/plugin/google-search-tool.d.ts.map +1 -0
- package/dist/src/plugin/google-search-tool.js +56 -0
- package/dist/src/plugin/google-search-tool.js.map +1 -0
- package/dist/src/plugin/image-saver.d.ts.map +1 -1
- package/dist/src/plugin/image-saver.js +16 -16
- package/dist/src/plugin/image-saver.js.map +1 -1
- package/dist/src/plugin/index.d.ts +30 -0
- package/dist/src/plugin/index.d.ts.map +1 -0
- package/dist/src/plugin/index.js +411 -0
- package/dist/src/plugin/index.js.map +1 -0
- package/dist/src/plugin/killswitch.d.ts +94 -0
- package/dist/src/plugin/killswitch.d.ts.map +1 -0
- package/dist/src/plugin/killswitch.js +197 -0
- package/dist/src/plugin/killswitch.js.map +1 -0
- package/dist/src/plugin/lifecycle.d.ts +40 -0
- package/dist/src/plugin/lifecycle.d.ts.map +1 -0
- package/dist/src/plugin/lifecycle.js +73 -0
- package/dist/src/plugin/lifecycle.js.map +1 -0
- package/dist/src/plugin/logger.d.ts +10 -1
- package/dist/src/plugin/logger.d.ts.map +1 -1
- package/dist/src/plugin/logger.js +38 -12
- package/dist/src/plugin/logger.js.map +1 -1
- package/dist/src/plugin/logging-utils.d.ts +35 -1
- package/dist/src/plugin/logging-utils.d.ts.map +1 -1
- package/dist/src/plugin/logging-utils.js +98 -15
- package/dist/src/plugin/logging-utils.js.map +1 -1
- package/dist/src/plugin/model-registry.d.ts +1 -1
- package/dist/src/plugin/model-registry.js +1 -1
- package/dist/src/plugin/oauth-login.d.ts +22 -0
- package/dist/src/plugin/oauth-login.d.ts.map +1 -0
- package/dist/src/plugin/oauth-login.js +37 -0
- package/dist/src/plugin/oauth-login.js.map +1 -0
- package/dist/src/plugin/oauth-methods.d.ts +42 -0
- package/dist/src/plugin/oauth-methods.d.ts.map +1 -0
- package/dist/src/plugin/oauth-methods.js +1276 -0
- package/dist/src/plugin/oauth-methods.js.map +1 -0
- package/dist/src/plugin/operator-settings.d.ts +40 -0
- package/dist/src/plugin/operator-settings.d.ts.map +1 -0
- package/dist/src/plugin/operator-settings.js +148 -0
- package/dist/src/plugin/operator-settings.js.map +1 -0
- package/dist/src/plugin/persist-account-pool.d.ts +37 -0
- package/dist/src/plugin/persist-account-pool.d.ts.map +1 -0
- package/dist/src/plugin/persist-account-pool.js +142 -0
- package/dist/src/plugin/persist-account-pool.js.map +1 -0
- package/dist/src/plugin/project.d.ts +1 -1
- package/dist/src/plugin/project.js +1 -1
- package/dist/src/plugin/prompt-context.d.ts.map +1 -1
- package/dist/src/plugin/prompt-context.js +11 -11
- package/dist/src/plugin/prompt-context.js.map +1 -1
- package/dist/src/plugin/quota.d.ts +138 -41
- package/dist/src/plugin/quota.d.ts.map +1 -1
- package/dist/src/plugin/quota.js +417 -285
- package/dist/src/plugin/quota.js.map +1 -1
- package/dist/src/plugin/recovery/constants.d.ts.map +1 -1
- package/dist/src/plugin/recovery/constants.js +24 -15
- package/dist/src/plugin/recovery/constants.js.map +1 -1
- package/dist/src/plugin/recovery/index.d.ts +3 -3
- package/dist/src/plugin/recovery/index.d.ts.map +1 -1
- package/dist/src/plugin/recovery/index.js +3 -3
- package/dist/src/plugin/recovery/index.js.map +1 -1
- package/dist/src/plugin/recovery/storage.d.ts +1 -1
- package/dist/src/plugin/recovery/storage.d.ts.map +1 -1
- package/dist/src/plugin/recovery/storage.js +31 -39
- package/dist/src/plugin/recovery/storage.js.map +1 -1
- package/dist/src/plugin/recovery/types.d.ts +12 -12
- package/dist/src/plugin/recovery/types.d.ts.map +1 -1
- package/dist/src/plugin/recovery.d.ts +3 -3
- package/dist/src/plugin/recovery.d.ts.map +1 -1
- package/dist/src/plugin/recovery.js +62 -58
- package/dist/src/plugin/recovery.js.map +1 -1
- package/dist/src/plugin/refresh-queue.d.ts +5 -2
- package/dist/src/plugin/refresh-queue.d.ts.map +1 -1
- package/dist/src/plugin/refresh-queue.js +37 -15
- package/dist/src/plugin/refresh-queue.js.map +1 -1
- package/dist/src/plugin/request-helpers.d.ts +1 -1
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +451 -352
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +7 -7
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +622 -384
- package/dist/src/plugin/request.js.map +1 -1
- package/dist/src/plugin/rotation.d.ts +1 -168
- package/dist/src/plugin/rotation.d.ts.map +1 -1
- package/dist/src/plugin/rotation.js +1 -327
- package/dist/src/plugin/rotation.js.map +1 -1
- package/dist/src/plugin/search.d.ts.map +1 -1
- package/dist/src/plugin/search.js +39 -35
- package/dist/src/plugin/search.js.map +1 -1
- package/dist/src/plugin/server.d.ts +1 -1
- package/dist/src/plugin/server.d.ts.map +1 -1
- package/dist/src/plugin/server.js +42 -37
- package/dist/src/plugin/server.js.map +1 -1
- package/dist/src/plugin/session-context.d.ts +2 -1
- package/dist/src/plugin/session-context.d.ts.map +1 -1
- package/dist/src/plugin/session-context.js +11 -7
- package/dist/src/plugin/session-context.js.map +1 -1
- package/dist/src/plugin/storage.d.ts +59 -120
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +140 -485
- package/dist/src/plugin/storage.js.map +1 -1
- package/dist/src/plugin/stores/signature-store.d.ts.map +1 -1
- package/dist/src/plugin/stores/signature-store.js.map +1 -1
- package/dist/src/plugin/thinking-recovery.d.ts.map +1 -1
- package/dist/src/plugin/thinking-recovery.js +35 -33
- package/dist/src/plugin/thinking-recovery.js.map +1 -1
- package/dist/src/plugin/token.d.ts +2 -2
- package/dist/src/plugin/token.d.ts.map +1 -1
- package/dist/src/plugin/token.js +30 -21
- package/dist/src/plugin/token.js.map +1 -1
- package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +1 -1
- package/dist/src/plugin/transform/cross-model-sanitizer.js +1 -1
- package/dist/src/plugin/transform/index.d.ts +1 -1
- package/dist/src/plugin/transform/index.js +1 -1
- package/dist/src/plugin/transform/model-resolver.d.ts +1 -1
- package/dist/src/plugin/transform/model-resolver.js +1 -1
- package/dist/src/plugin/transform/types.d.ts +1 -1
- package/dist/src/plugin/transform/types.js +1 -1
- package/dist/src/plugin/types.d.ts +25 -69
- package/dist/src/plugin/types.d.ts.map +1 -1
- package/dist/src/plugin/ui/ansi.d.ts.map +1 -1
- package/dist/src/plugin/ui/ansi.js.map +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts +1 -1
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
- package/dist/src/plugin/ui/auth-menu.js +84 -35
- package/dist/src/plugin/ui/auth-menu.js.map +1 -1
- package/dist/src/plugin/ui/confirm.d.ts.map +1 -1
- package/dist/src/plugin/ui/confirm.js.map +1 -1
- package/dist/src/plugin/ui/model-status.d.ts +3 -3
- package/dist/src/plugin/ui/model-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/model-status.js +3 -3
- package/dist/src/plugin/ui/model-status.js.map +1 -1
- package/dist/src/plugin/ui/quota-status.d.ts +4 -4
- package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
- package/dist/src/plugin/ui/quota-status.js +49 -49
- package/dist/src/plugin/ui/quota-status.js.map +1 -1
- package/dist/src/plugin/ui/select.d.ts.map +1 -1
- package/dist/src/plugin/ui/select.js +22 -12
- package/dist/src/plugin/ui/select.js.map +1 -1
- package/dist/src/plugin/version.d.ts +1 -1
- package/dist/src/plugin/version.js +1 -1
- package/dist/src/rpc/notifications.d.ts +6 -0
- package/dist/src/rpc/notifications.d.ts.map +1 -0
- package/dist/src/rpc/notifications.js +46 -0
- package/dist/src/rpc/notifications.js.map +1 -0
- package/dist/src/rpc/port-file.d.ts +13 -0
- package/dist/src/rpc/port-file.d.ts.map +1 -0
- package/dist/src/rpc/port-file.js +109 -0
- package/dist/src/rpc/port-file.js.map +1 -0
- package/dist/src/rpc/protocol.d.ts +22 -0
- package/dist/src/rpc/protocol.d.ts.map +1 -0
- package/dist/src/rpc/protocol.js +1 -0
- package/dist/src/rpc/protocol.js.map +1 -0
- package/dist/src/rpc/rpc-client.d.ts +10 -0
- package/dist/src/rpc/rpc-client.d.ts.map +1 -0
- package/dist/src/rpc/rpc-client.js +58 -0
- package/dist/src/rpc/rpc-client.js.map +1 -0
- package/dist/src/rpc/rpc-dir.d.ts +4 -0
- package/dist/src/rpc/rpc-dir.d.ts.map +1 -0
- package/dist/src/rpc/rpc-dir.js +19 -0
- package/dist/src/rpc/rpc-dir.js.map +1 -0
- package/dist/src/rpc/rpc-server.d.ts +13 -0
- package/dist/src/rpc/rpc-server.d.ts.map +1 -0
- package/dist/src/rpc/rpc-server.js +231 -0
- package/dist/src/rpc/rpc-server.js.map +1 -0
- package/dist/src/sidebar-state.d.ts +357 -0
- package/dist/src/sidebar-state.d.ts.map +1 -0
- package/dist/src/sidebar-state.js +680 -0
- package/dist/src/sidebar-state.js.map +1 -0
- package/dist/src/tui/command-dialogs.d.ts +61 -0
- package/dist/src/tui/command-dialogs.d.ts.map +1 -0
- package/dist/src/tui/command-dialogs.js +595 -0
- package/dist/src/tui/command-dialogs.js.map +1 -0
- package/dist/src/tui/file-logger.d.ts +41 -0
- package/dist/src/tui/file-logger.d.ts.map +1 -0
- package/dist/src/tui/file-logger.js +114 -0
- package/dist/src/tui/file-logger.js.map +1 -0
- package/dist/src/tui-preferences.d.ts +40 -0
- package/dist/src/tui-preferences.d.ts.map +1 -0
- package/dist/src/tui-preferences.js +328 -0
- package/dist/src/tui-preferences.js.map +1 -0
- package/dist/src/tui.d.ts +68 -0
- package/dist/src/tui.d.ts.map +1 -0
- package/dist/src/tui.js +697 -0
- package/dist/src/tui.js.map +1 -0
- package/package.json +46 -22
- package/src/plugin/command-data.ts +931 -0
- package/src/rpc/port-file.ts +143 -0
- package/src/rpc/protocol.ts +31 -0
- package/src/rpc/rpc-client.ts +102 -0
- package/src/rpc/rpc-dir.ts +24 -0
- package/src/sidebar-state.ts +991 -0
- package/src/tui/command-dialogs.tsx +832 -0
- package/src/tui/entry.mjs +74 -0
- package/src/tui/file-logger.ts +145 -0
- package/src/tui-compiled/plugin/command-data.ts +931 -0
- package/src/tui-compiled/rpc/port-file.ts +143 -0
- package/src/tui-compiled/rpc/protocol.ts +31 -0
- package/src/tui-compiled/rpc/rpc-client.ts +102 -0
- package/src/tui-compiled/rpc/rpc-dir.ts +24 -0
- package/src/tui-compiled/sidebar-state.ts +991 -0
- package/src/tui-compiled/tui/command-dialogs.tsx +779 -0
- package/src/tui-compiled/tui/file-logger.ts +145 -0
- package/src/tui-compiled/tui-preferences.ts +416 -0
- package/src/tui-compiled/tui.tsx +1193 -0
- package/src/tui-preferences.ts +416 -0
- package/src/tui.tsx +1171 -0
- package/dist/src/plugin.d.ts +0 -48
- package/dist/src/plugin.d.ts.map +0 -1
- package/dist/src/plugin.js +0 -3311
- package/dist/src/plugin.js.map +0 -1
|
@@ -0,0 +1,1276 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { authorizeAntigravity as defaultAuthorizeAntigravity, exchangeAntigravity as defaultExchangeAntigravity, } from '../antigravity/oauth';
|
|
4
|
+
import { clearStoredAccountAccessBlocks, markStoredAccountIneligible, markStoredAccountVerificationRequired, } from './account-access';
|
|
5
|
+
import { formatRefreshParts, parseRefreshParts } from './auth';
|
|
6
|
+
import { createAuthDoctorReport, formatAuthDoctorReport } from './auth-doctor';
|
|
7
|
+
import { promptAddAnotherAccount as defaultPromptAddAnotherAccount, promptLoginMode as defaultPromptLoginMode, promptProjectId as defaultPromptProjectId, } from './cli';
|
|
8
|
+
import { createLogger } from './logger';
|
|
9
|
+
import { performOAuthLogin } from './oauth-login';
|
|
10
|
+
import { clearProvisionFailedKeys } from './project';
|
|
11
|
+
import { createOpenCodeQuotaManager } from './quota';
|
|
12
|
+
import { startOAuthListener as defaultStartOAuthListener, } from './server';
|
|
13
|
+
import { AccountStorageUnreadableError } from './storage';
|
|
14
|
+
import { classifyGroupStatus, formatCachedQuotaWithStatus, formatQuotaStatusBadge, } from './ui/quota-status';
|
|
15
|
+
import { getAntigravityVersionResolution } from './version';
|
|
16
|
+
function toV1AuthCallbackResult(result) {
|
|
17
|
+
if (result.type === 'failed') {
|
|
18
|
+
return { type: 'failed' };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
type: 'success',
|
|
22
|
+
refresh: result.refresh,
|
|
23
|
+
access: result.access ?? '',
|
|
24
|
+
expires: result.expires ?? 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const MAX_OAUTH_ACCOUNTS = 10;
|
|
28
|
+
const log = createLogger('oauth-methods');
|
|
29
|
+
export { performOAuthLogin, } from './oauth-login';
|
|
30
|
+
/**
|
|
31
|
+
* Build the user-facing failure surfaced when a freshly-exchanged
|
|
32
|
+
* OAuth token could not be persisted. A login that returns
|
|
33
|
+
* `type: 'success'` while the token never reaches disk is the
|
|
34
|
+
* worse-than-no-error class of bug — the user sees "Authenticated"
|
|
35
|
+
* but their next call finds no account. So we ALWAYS surface the
|
|
36
|
+
* persistence error:
|
|
37
|
+
*
|
|
38
|
+
* - `AccountStorageUnreadableError` carries the file path, the
|
|
39
|
+
* reason (`malformed-json` / `invalid-shape` / etc.), and the
|
|
40
|
+
* backup sidecar path; we surface all three.
|
|
41
|
+
* - Any other error (lock contention, I/O hiccup) is surfaced with
|
|
42
|
+
* its own message so the user can retry or hand it to support.
|
|
43
|
+
*
|
|
44
|
+
* The matching failure toast uses `variant: 'error'` so the host's
|
|
45
|
+
* UI does not stack a green success next to a red failure.
|
|
46
|
+
*/
|
|
47
|
+
async function reportPersistenceFailure(error, client, log) {
|
|
48
|
+
const message = error instanceof AccountStorageUnreadableError
|
|
49
|
+
? `Account storage at ${error.details.path} is unreadable (${error.details.reason}: ${error.details.detail}).${error.details.backupPath
|
|
50
|
+
? ` A backup was written to ${error.details.backupPath}. Repair or remove the existing file before retrying.`
|
|
51
|
+
: ' A backup could not be written; repair or remove the existing file before retrying.'}`
|
|
52
|
+
: error instanceof Error
|
|
53
|
+
? error.message
|
|
54
|
+
: String(error);
|
|
55
|
+
log.error('OAuth login persistence failed; aborting login', {
|
|
56
|
+
error: message,
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await client.tui.showToast({
|
|
60
|
+
body: {
|
|
61
|
+
message: `Login failed: ${message}`,
|
|
62
|
+
variant: 'error',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* TUI may not be available */
|
|
68
|
+
}
|
|
69
|
+
return { type: 'failed', error: message };
|
|
70
|
+
}
|
|
71
|
+
function isWSL() {
|
|
72
|
+
if (process.platform !== 'linux')
|
|
73
|
+
return false;
|
|
74
|
+
try {
|
|
75
|
+
const release = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
76
|
+
return release.includes('microsoft') || release.includes('wsl');
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isWSL2() {
|
|
83
|
+
if (!isWSL())
|
|
84
|
+
return false;
|
|
85
|
+
try {
|
|
86
|
+
const version = readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
87
|
+
return version.includes('wsl2') || version.includes('microsoft-standard');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function isRemoteEnvironment() {
|
|
94
|
+
if (process.env.SSH_CLIENT ||
|
|
95
|
+
process.env.SSH_TTY ||
|
|
96
|
+
process.env.SSH_CONNECTION) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (process.env.REMOTE_CONTAINERS || process.env.CODESPACES)
|
|
100
|
+
return true;
|
|
101
|
+
return (process.platform === 'linux' &&
|
|
102
|
+
!process.env.DISPLAY &&
|
|
103
|
+
!process.env.WAYLAND_DISPLAY &&
|
|
104
|
+
!isWSL());
|
|
105
|
+
}
|
|
106
|
+
function defaultShouldSkipLocalServer() {
|
|
107
|
+
return isWSL2() || isRemoteEnvironment();
|
|
108
|
+
}
|
|
109
|
+
export async function openBrowserWithSystem(url) {
|
|
110
|
+
try {
|
|
111
|
+
if (process.platform === 'darwin') {
|
|
112
|
+
exec(`open "${url}"`);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (process.platform === 'win32') {
|
|
116
|
+
exec(`start "" "${url}"`);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (isWSL()) {
|
|
120
|
+
exec(`wslview "${url}"`);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)
|
|
124
|
+
return false;
|
|
125
|
+
exec(`xdg-open "${url}"`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function defaultPromptCallback(message) {
|
|
133
|
+
const { createInterface } = await import('node:readline/promises');
|
|
134
|
+
const { stdin, stdout } = await import('node:process');
|
|
135
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
136
|
+
try {
|
|
137
|
+
return (await rl.question(message)).trim();
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
rl.close();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function getStateFromAuthorizationUrl(authorizationUrl) {
|
|
144
|
+
try {
|
|
145
|
+
return new URL(authorizationUrl).searchParams.get('state') ?? '';
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function extractOAuthCallbackParams(url, expectedState) {
|
|
152
|
+
const code = url.searchParams.get('code');
|
|
153
|
+
const state = url.searchParams.get('state');
|
|
154
|
+
if (!code || !state)
|
|
155
|
+
return { error: 'Missing code or state in callback URL' };
|
|
156
|
+
if (expectedState && state !== expectedState) {
|
|
157
|
+
return { error: 'OAuth state mismatch' };
|
|
158
|
+
}
|
|
159
|
+
return { code, state };
|
|
160
|
+
}
|
|
161
|
+
export function parseOAuthCallbackInput(value, fallbackState) {
|
|
162
|
+
const trimmed = value.trim();
|
|
163
|
+
if (!trimmed)
|
|
164
|
+
return { error: 'Missing authorization code' };
|
|
165
|
+
try {
|
|
166
|
+
const url = new URL(trimmed);
|
|
167
|
+
const code = url.searchParams.get('code');
|
|
168
|
+
const state = url.searchParams.get('state') ?? fallbackState;
|
|
169
|
+
if (!code)
|
|
170
|
+
return { error: 'Missing code in callback URL' };
|
|
171
|
+
if (!state)
|
|
172
|
+
return { error: 'Missing state in callback URL' };
|
|
173
|
+
if (fallbackState && state !== fallbackState) {
|
|
174
|
+
return { error: 'OAuth state mismatch' };
|
|
175
|
+
}
|
|
176
|
+
return { code, state };
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
if (!fallbackState) {
|
|
180
|
+
return {
|
|
181
|
+
error: 'Missing state. Paste the full redirect URL instead of only the code.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { code: trimmed, state: fallbackState };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function buildAuthSuccessFromStoredAccount(account) {
|
|
188
|
+
return {
|
|
189
|
+
type: 'success',
|
|
190
|
+
refresh: formatRefreshParts({
|
|
191
|
+
refreshToken: account.refreshToken,
|
|
192
|
+
projectId: account.projectId,
|
|
193
|
+
managedProjectId: account.managedProjectId,
|
|
194
|
+
}),
|
|
195
|
+
access: '',
|
|
196
|
+
expires: 0,
|
|
197
|
+
email: account.email,
|
|
198
|
+
label: account.label,
|
|
199
|
+
projectId: account.projectId ?? '',
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function formatCachedQuotaSummary(account) {
|
|
203
|
+
return account.cachedQuota
|
|
204
|
+
? formatCachedQuotaWithStatus(account.cachedQuota)
|
|
205
|
+
: undefined;
|
|
206
|
+
}
|
|
207
|
+
function formatWaitTime(ms) {
|
|
208
|
+
if (ms < 1000)
|
|
209
|
+
return `${ms}ms`;
|
|
210
|
+
const seconds = Math.ceil(ms / 1000);
|
|
211
|
+
if (seconds < 60)
|
|
212
|
+
return `${seconds}s`;
|
|
213
|
+
const minutes = Math.floor(seconds / 60);
|
|
214
|
+
const remainingSeconds = seconds % 60;
|
|
215
|
+
if (minutes < 60) {
|
|
216
|
+
return remainingSeconds > 0
|
|
217
|
+
? `${minutes}m ${remainingSeconds}s`
|
|
218
|
+
: `${minutes}m`;
|
|
219
|
+
}
|
|
220
|
+
const hours = Math.floor(minutes / 60);
|
|
221
|
+
const remainingMinutes = minutes % 60;
|
|
222
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
223
|
+
}
|
|
224
|
+
export function createOAuthMethods({ client, providerId, config: _config, lifecycle, accountAccess, quotaManager: injectedQuotaManager, getAuth = null, dependencies, }) {
|
|
225
|
+
const deps = {
|
|
226
|
+
authorize: dependencies?.authorize ?? defaultAuthorizeAntigravity,
|
|
227
|
+
exchange: dependencies?.exchange ?? defaultExchangeAntigravity,
|
|
228
|
+
startListener: dependencies?.startListener ?? defaultStartOAuthListener,
|
|
229
|
+
promptProjectId: dependencies?.promptProjectId ?? defaultPromptProjectId,
|
|
230
|
+
promptAddAnotherAccount: dependencies?.promptAddAnotherAccount ?? defaultPromptAddAnotherAccount,
|
|
231
|
+
promptLoginMode: dependencies?.promptLoginMode ?? defaultPromptLoginMode,
|
|
232
|
+
promptCallback: dependencies?.promptCallback ?? defaultPromptCallback,
|
|
233
|
+
openBrowser: dependencies?.openBrowser ?? openBrowserWithSystem,
|
|
234
|
+
shouldSkipLocalServer: dependencies?.shouldSkipLocalServer ?? defaultShouldSkipLocalServer,
|
|
235
|
+
isHeadless: dependencies?.isHeadless ??
|
|
236
|
+
(() => Boolean(process.env.SSH_CONNECTION ||
|
|
237
|
+
process.env.SSH_CLIENT ||
|
|
238
|
+
process.env.SSH_TTY ||
|
|
239
|
+
process.env.OPENCODE_HEADLESS)),
|
|
240
|
+
confirmOpenVerificationUrl: dependencies?.confirmOpenVerificationUrl ??
|
|
241
|
+
(async () => {
|
|
242
|
+
const answer = (await defaultPromptCallback('Open verification URL in your browser now? [Y/n]: '))
|
|
243
|
+
.trim()
|
|
244
|
+
.toLowerCase();
|
|
245
|
+
return answer === '' || answer === 'y' || answer === 'yes';
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
const quotaManager = injectedQuotaManager ??
|
|
249
|
+
createOpenCodeQuotaManager(client, providerId, {
|
|
250
|
+
// Bind to the live AccountManager so the menu `check` action's
|
|
251
|
+
// `refreshAccounts` pushes the refreshed percentages into the
|
|
252
|
+
// sidebar. The lifecycle reference is stable for the lifetime of
|
|
253
|
+
// the plugin so this closure is safe to capture.
|
|
254
|
+
getAccountsForSidebar: () => {
|
|
255
|
+
const manager = lifecycle.getAccountManager();
|
|
256
|
+
if (!manager)
|
|
257
|
+
return null;
|
|
258
|
+
return manager.getAccounts().map((entry) => ({
|
|
259
|
+
index: entry.index,
|
|
260
|
+
label: entry.label,
|
|
261
|
+
enabled: entry.enabled,
|
|
262
|
+
coolingDownUntil: entry.coolingDownUntil,
|
|
263
|
+
cachedQuota: entry.cachedQuota,
|
|
264
|
+
}));
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
if (!injectedQuotaManager) {
|
|
268
|
+
lifecycle.register({ dispose: () => quotaManager.dispose() });
|
|
269
|
+
}
|
|
270
|
+
const cachedGetAuth = getAuth;
|
|
271
|
+
const loadAccounts = () => accountAccess.loadAccounts();
|
|
272
|
+
const clearAccounts = () => accountAccess.clearAccounts();
|
|
273
|
+
const persistAccountPool = accountAccess.persistAccountPool.bind(accountAccess);
|
|
274
|
+
const mutateAccountByRefreshToken = async (refreshToken, mutate) => {
|
|
275
|
+
let changed = false;
|
|
276
|
+
await accountAccess.mutateAccounts((current) => {
|
|
277
|
+
const account = current.accounts.find((candidate) => candidate.refreshToken === refreshToken);
|
|
278
|
+
if (account)
|
|
279
|
+
changed = mutate(account);
|
|
280
|
+
return current;
|
|
281
|
+
});
|
|
282
|
+
return changed;
|
|
283
|
+
};
|
|
284
|
+
const verifyAccountAccess = accountAccess.verifyAccount.bind(accountAccess);
|
|
285
|
+
const promptAccountIndexForVerification = accountAccess.selectAccount.bind(accountAccess);
|
|
286
|
+
const promptOpenVerificationUrl = deps.confirmOpenVerificationUrl;
|
|
287
|
+
const openBrowser = deps.openBrowser;
|
|
288
|
+
const shouldSkipLocalServer = deps.shouldSkipLocalServer;
|
|
289
|
+
const startOAuthListener = deps.startListener;
|
|
290
|
+
const promptOAuthCallbackValue = deps.promptCallback;
|
|
291
|
+
const promptManualOAuthInput = async (fallbackState) => {
|
|
292
|
+
console.log('1. Open the URL above in your browser and complete Google sign-in.');
|
|
293
|
+
console.log('2. After approving, copy the full redirected localhost URL from the address bar.');
|
|
294
|
+
console.log('3. Paste it back here.\n');
|
|
295
|
+
const callbackInput = await promptOAuthCallbackValue('Paste the redirect URL (or just the code) here: ');
|
|
296
|
+
const params = parseOAuthCallbackInput(callbackInput, fallbackState);
|
|
297
|
+
if ('error' in params)
|
|
298
|
+
return { type: 'failed', error: params.error };
|
|
299
|
+
return deps.exchange(params.code, params.state);
|
|
300
|
+
};
|
|
301
|
+
const authorizeAntigravity = deps.authorize;
|
|
302
|
+
const exchangeAntigravity = deps.exchange;
|
|
303
|
+
const promptProjectId = deps.promptProjectId;
|
|
304
|
+
const promptAddAnotherAccount = deps.promptAddAnotherAccount;
|
|
305
|
+
const promptLoginMode = deps.promptLoginMode;
|
|
306
|
+
return [
|
|
307
|
+
{
|
|
308
|
+
label: 'OAuth with Google (Antigravity)',
|
|
309
|
+
type: 'oauth',
|
|
310
|
+
authorize: async (inputs) => {
|
|
311
|
+
const isHeadless = deps.isHeadless();
|
|
312
|
+
// CLI flow (`opencode auth login`) passes an inputs object.
|
|
313
|
+
if (inputs) {
|
|
314
|
+
const accounts = [];
|
|
315
|
+
const noBrowser = inputs.noBrowser === 'true' || inputs['no-browser'] === 'true';
|
|
316
|
+
const useManualMode = noBrowser || shouldSkipLocalServer();
|
|
317
|
+
// Check for existing accounts and prompt user for login mode
|
|
318
|
+
let startFresh = true;
|
|
319
|
+
let refreshAccountIndex;
|
|
320
|
+
const existingStorage = await loadAccounts();
|
|
321
|
+
if (existingStorage && existingStorage.accounts.length > 0) {
|
|
322
|
+
let menuResult;
|
|
323
|
+
while (true) {
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
const existingAccounts = existingStorage.accounts.map((acc, idx) => {
|
|
326
|
+
let status = 'unknown';
|
|
327
|
+
if (acc.accountIneligible) {
|
|
328
|
+
status = 'ineligible';
|
|
329
|
+
}
|
|
330
|
+
else if (acc.verificationRequired) {
|
|
331
|
+
status = 'verification-required';
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const rateLimits = acc.rateLimitResetTimes;
|
|
335
|
+
if (rateLimits) {
|
|
336
|
+
const isRateLimited = Object.values(rateLimits).some((resetTime) => typeof resetTime === 'number' && resetTime > now);
|
|
337
|
+
if (isRateLimited) {
|
|
338
|
+
status = 'rate-limited';
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
status = 'active';
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
status = 'active';
|
|
346
|
+
}
|
|
347
|
+
if (acc.coolingDownUntil && acc.coolingDownUntil > now) {
|
|
348
|
+
status = 'rate-limited';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const cooldownMs = acc.coolingDownUntil && acc.coolingDownUntil > now
|
|
352
|
+
? acc.coolingDownUntil - now
|
|
353
|
+
: undefined;
|
|
354
|
+
// Age-gate quota data: ignore cached quota older than 60 minutes
|
|
355
|
+
// to prevent stale exhaustion data from persisting in the UI.
|
|
356
|
+
// AccountManager applies the same protection during account selection.
|
|
357
|
+
const DISPLAY_QUOTA_MAX_AGE_MS = 60 * 60 * 1000;
|
|
358
|
+
const quotaIsStale = acc.cachedQuotaUpdatedAt == null ||
|
|
359
|
+
now - acc.cachedQuotaUpdatedAt > DISPLAY_QUOTA_MAX_AGE_MS;
|
|
360
|
+
const displayQuota = quotaIsStale
|
|
361
|
+
? undefined
|
|
362
|
+
: acc.cachedQuota;
|
|
363
|
+
const displayPerModelQuota = quotaIsStale
|
|
364
|
+
? undefined
|
|
365
|
+
: acc.cachedPerModelQuota;
|
|
366
|
+
if (status === 'active' && displayQuota) {
|
|
367
|
+
const groups = Object.values(displayQuota);
|
|
368
|
+
const allExhausted = groups.length > 0 &&
|
|
369
|
+
groups.every((group) => typeof group.remainingFraction === 'number' &&
|
|
370
|
+
group.remainingFraction <= 0);
|
|
371
|
+
if (allExhausted) {
|
|
372
|
+
status = 'rate-limited';
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
email: acc.email,
|
|
377
|
+
index: idx,
|
|
378
|
+
addedAt: acc.addedAt,
|
|
379
|
+
lastUsed: acc.lastUsed,
|
|
380
|
+
status,
|
|
381
|
+
isCurrentAccount: idx === (existingStorage.activeIndex ?? 0),
|
|
382
|
+
enabled: acc.enabled !== false,
|
|
383
|
+
quotaSummary: quotaIsStale
|
|
384
|
+
? undefined
|
|
385
|
+
: formatCachedQuotaSummary(acc),
|
|
386
|
+
cooldownMs,
|
|
387
|
+
cooldownReason: cooldownMs ? acc.cooldownReason : undefined,
|
|
388
|
+
cachedQuota: displayQuota,
|
|
389
|
+
cachedPerModelQuota: displayPerModelQuota,
|
|
390
|
+
fingerprintHistory: acc.fingerprintHistory,
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
menuResult = await promptLoginMode(existingAccounts);
|
|
394
|
+
if (menuResult.mode === 'check') {
|
|
395
|
+
console.log('\n📊 Checking quotas for all accounts...\n');
|
|
396
|
+
clearProvisionFailedKeys();
|
|
397
|
+
// Manual quota dialog must always bypass backoff so the
|
|
398
|
+
// user sees fresh numbers even if a background refresh
|
|
399
|
+
// recently failed.
|
|
400
|
+
const results = await quotaManager.refreshAccounts(existingStorage.accounts, {
|
|
401
|
+
indexFor: (account) => existingStorage.accounts.indexOf(account),
|
|
402
|
+
force: true,
|
|
403
|
+
});
|
|
404
|
+
// Collect quota cache updates keyed by refresh token so a
|
|
405
|
+
// concurrent add cannot shift our index and let us
|
|
406
|
+
// overwrite the wrong account's quota cache.
|
|
407
|
+
const quotaUpdates = new Map();
|
|
408
|
+
for (const res of results) {
|
|
409
|
+
const label = res.email || `Account ${res.index + 1}`;
|
|
410
|
+
const disabledStr = res.disabled ? ' (disabled)' : '';
|
|
411
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
412
|
+
console.log(` ${label}${disabledStr}`);
|
|
413
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
414
|
+
if (res.status === 'error') {
|
|
415
|
+
console.log(` ❌ Error: ${res.error}\n`);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
// ANSI color codes
|
|
419
|
+
const colors = {
|
|
420
|
+
red: '\x1b[31m',
|
|
421
|
+
orange: '\x1b[33m', // Yellow/orange
|
|
422
|
+
green: '\x1b[32m',
|
|
423
|
+
reset: '\x1b[0m',
|
|
424
|
+
};
|
|
425
|
+
// Get color based on remaining percentage
|
|
426
|
+
const getColor = (remaining) => {
|
|
427
|
+
if (typeof remaining !== 'number')
|
|
428
|
+
return colors.reset;
|
|
429
|
+
if (remaining < 0.2)
|
|
430
|
+
return colors.red;
|
|
431
|
+
if (remaining < 0.6)
|
|
432
|
+
return colors.orange;
|
|
433
|
+
return colors.green;
|
|
434
|
+
};
|
|
435
|
+
// Helper to create colored progress bar
|
|
436
|
+
const createProgressBar = (remaining, width = 20) => {
|
|
437
|
+
if (typeof remaining !== 'number')
|
|
438
|
+
return `${'░'.repeat(width)} ???`;
|
|
439
|
+
const filled = Math.round(remaining * width);
|
|
440
|
+
const empty = width - filled;
|
|
441
|
+
const color = getColor(remaining);
|
|
442
|
+
const bar = `${color}${'█'.repeat(filled)}${colors.reset}${'░'.repeat(empty)}`;
|
|
443
|
+
const pct = `${color}${Math.round(remaining * 100)}%${colors.reset}`.padStart(4 + color.length + colors.reset.length);
|
|
444
|
+
return `${bar} ${pct}`;
|
|
445
|
+
};
|
|
446
|
+
// Helper to format reset time with days support
|
|
447
|
+
const formatReset = (resetTime, remainingFraction) => {
|
|
448
|
+
if (!resetTime)
|
|
449
|
+
return '';
|
|
450
|
+
const ms = Date.parse(resetTime) - Date.now();
|
|
451
|
+
if (ms <= 0) {
|
|
452
|
+
// If quota is 0% and reset time is in the past, the model is
|
|
453
|
+
// likely paywalled / permanently unavailable on this quota pool
|
|
454
|
+
return remainingFraction !== undefined &&
|
|
455
|
+
remainingFraction <= 0
|
|
456
|
+
? ' (paid only)'
|
|
457
|
+
: ' (resetting...)';
|
|
458
|
+
}
|
|
459
|
+
const hours = ms / (1000 * 60 * 60);
|
|
460
|
+
if (hours >= 24) {
|
|
461
|
+
const days = Math.floor(hours / 24);
|
|
462
|
+
const remainingHours = Math.floor(hours % 24);
|
|
463
|
+
if (remainingHours > 0) {
|
|
464
|
+
return ` (resets in ${days}d ${remainingHours}h)`;
|
|
465
|
+
}
|
|
466
|
+
return ` (resets in ${days}d)`;
|
|
467
|
+
}
|
|
468
|
+
return ` (resets in ${formatWaitTime(ms)})`;
|
|
469
|
+
};
|
|
470
|
+
// Display Gemini CLI Quota first (as requested - swap order)
|
|
471
|
+
const hasGeminiCli = res.geminiCliQuota && res.geminiCliQuota.models.length > 0;
|
|
472
|
+
console.log(`\n ┌─ Gemini CLI Quota`);
|
|
473
|
+
if (!hasGeminiCli) {
|
|
474
|
+
const errorMsg = res.geminiCliQuota?.error ||
|
|
475
|
+
'No Gemini CLI quota available';
|
|
476
|
+
console.log(` │ └─ ${errorMsg}`);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
const models = res.geminiCliQuota.models;
|
|
480
|
+
models.forEach((model, idx) => {
|
|
481
|
+
const isLast = idx === models.length - 1;
|
|
482
|
+
const connector = isLast ? '└─' : '├─';
|
|
483
|
+
const bar = createProgressBar(model.remainingFraction);
|
|
484
|
+
const reset = formatReset(model.resetTime, model.remainingFraction);
|
|
485
|
+
const status = classifyGroupStatus({
|
|
486
|
+
remainingFraction: model.remainingFraction,
|
|
487
|
+
resetTime: model.resetTime,
|
|
488
|
+
modelCount: 1,
|
|
489
|
+
});
|
|
490
|
+
const badge = formatQuotaStatusBadge(status);
|
|
491
|
+
const modelName = model.modelId.padEnd(29);
|
|
492
|
+
console.log(` │ ${connector} ${modelName} ${bar} ${badge}${reset}`);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// Display Antigravity Quota second
|
|
496
|
+
const hasAntigravity = res.quota && Object.keys(res.quota.groups).length > 0;
|
|
497
|
+
console.log(` │`);
|
|
498
|
+
console.log(` └─ Antigravity Quota`);
|
|
499
|
+
if (!hasAntigravity) {
|
|
500
|
+
const errorMsg = res.quota?.error || 'No quota information available';
|
|
501
|
+
console.log(` └─ ${errorMsg}`);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const groups = res.quota.groups;
|
|
505
|
+
const groupEntries = [
|
|
506
|
+
{ name: 'Non-Gemini', data: groups['non-gemini'] },
|
|
507
|
+
{ name: 'Gemini', data: groups.gemini },
|
|
508
|
+
].filter((g) => g.data);
|
|
509
|
+
groupEntries.forEach((g, idx) => {
|
|
510
|
+
const isLast = idx === groupEntries.length - 1;
|
|
511
|
+
const connector = isLast ? '└─' : '├─';
|
|
512
|
+
const bar = createProgressBar(g.data.remainingFraction);
|
|
513
|
+
const reset = formatReset(g.data.resetTime, g.data.remainingFraction);
|
|
514
|
+
const status = classifyGroupStatus(g.data);
|
|
515
|
+
const badge = formatQuotaStatusBadge(status);
|
|
516
|
+
const modelName = g.name.padEnd(29);
|
|
517
|
+
console.log(` ${connector} ${modelName} ${bar} ${badge}${reset}`);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
console.log('');
|
|
521
|
+
// Cache quota data for soft quota protection
|
|
522
|
+
const targetRefreshToken = existingStorage.accounts[res.index]?.refreshToken;
|
|
523
|
+
if (!targetRefreshToken)
|
|
524
|
+
continue;
|
|
525
|
+
const updatedAt = Date.now();
|
|
526
|
+
const existing = quotaUpdates.get(targetRefreshToken);
|
|
527
|
+
quotaUpdates.set(targetRefreshToken, {
|
|
528
|
+
quota: res.quota?.groups,
|
|
529
|
+
perModel: res.quota?.perModel,
|
|
530
|
+
updatedAccount: res.updatedAccount,
|
|
531
|
+
updatedAt: existing && existing.updatedAt > updatedAt
|
|
532
|
+
? existing.updatedAt
|
|
533
|
+
: updatedAt,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (quotaUpdates.size > 0) {
|
|
537
|
+
await accountAccess.mutateAccounts((current) => {
|
|
538
|
+
let changed = false;
|
|
539
|
+
for (const [refreshToken, update] of quotaUpdates) {
|
|
540
|
+
const idx = current.accounts.findIndex((acc) => acc.refreshToken === refreshToken);
|
|
541
|
+
if (idx === -1)
|
|
542
|
+
continue;
|
|
543
|
+
const target = current.accounts[idx];
|
|
544
|
+
if (!target)
|
|
545
|
+
continue;
|
|
546
|
+
current.accounts[idx] = {
|
|
547
|
+
...target,
|
|
548
|
+
...(update.updatedAccount ?? {}),
|
|
549
|
+
cachedQuota: update.quota,
|
|
550
|
+
cachedPerModelQuota: update.perModel,
|
|
551
|
+
cachedQuotaUpdatedAt: update.updatedAt,
|
|
552
|
+
};
|
|
553
|
+
changed = true;
|
|
554
|
+
}
|
|
555
|
+
return changed ? current : current;
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
console.log('');
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (menuResult.mode === 'doctor') {
|
|
562
|
+
const auth = cachedGetAuth
|
|
563
|
+
? await cachedGetAuth().catch(() => undefined)
|
|
564
|
+
: undefined;
|
|
565
|
+
const versionResolution = getAntigravityVersionResolution();
|
|
566
|
+
const report = createAuthDoctorReport({
|
|
567
|
+
auth,
|
|
568
|
+
storage: existingStorage,
|
|
569
|
+
runtime: {
|
|
570
|
+
antigravityVersion: versionResolution.version,
|
|
571
|
+
antigravityVersionSource: versionResolution.source,
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
console.log(`\n${formatAuthDoctorReport(report)}\n`);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (menuResult.mode === 'manage') {
|
|
578
|
+
if (menuResult.toggleAccountIndex !== undefined) {
|
|
579
|
+
const acc = existingStorage.accounts[menuResult.toggleAccountIndex];
|
|
580
|
+
if (acc) {
|
|
581
|
+
const shouldEnable = acc.enabled === false;
|
|
582
|
+
if (shouldEnable && acc.accountIneligible) {
|
|
583
|
+
console.log(`\n${acc.email || `Account ${menuResult.toggleAccountIndex + 1}`} remains disabled. ` +
|
|
584
|
+
'Use Verify accounts to recheck eligibility.\n');
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (acc.refreshToken) {
|
|
588
|
+
await mutateAccountByRefreshToken(acc.refreshToken, (target) => {
|
|
589
|
+
target.enabled = shouldEnable;
|
|
590
|
+
return true;
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
lifecycle
|
|
594
|
+
.getAccountManager()
|
|
595
|
+
?.setAccountEnabled(menuResult.toggleAccountIndex, shouldEnable);
|
|
596
|
+
console.log(`\nAccount ${acc.email || menuResult.toggleAccountIndex + 1} ${shouldEnable ? 'enabled' : 'disabled'}.\n`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (menuResult.mode === 'verify' ||
|
|
602
|
+
menuResult.mode === 'verify-all') {
|
|
603
|
+
const verifyAll = menuResult.mode === 'verify-all' ||
|
|
604
|
+
menuResult.verifyAll === true;
|
|
605
|
+
if (verifyAll) {
|
|
606
|
+
if (existingStorage.accounts.length === 0) {
|
|
607
|
+
console.log('\nNo accounts available to verify.\n');
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
console.log(`\nChecking verification status for ${existingStorage.accounts.length} account(s)...\n`);
|
|
611
|
+
let okCount = 0;
|
|
612
|
+
let blockedCount = 0;
|
|
613
|
+
let ineligibleCount = 0;
|
|
614
|
+
let errorCount = 0;
|
|
615
|
+
const blockedResults = [];
|
|
616
|
+
for (let i = 0; i < existingStorage.accounts.length; i++) {
|
|
617
|
+
const account = existingStorage.accounts[i];
|
|
618
|
+
if (!account)
|
|
619
|
+
continue;
|
|
620
|
+
const label = account.email || `Account ${i + 1}`;
|
|
621
|
+
process.stdout.write(`- [${i + 1}/${existingStorage.accounts.length}] ${label} ... `);
|
|
622
|
+
const verification = await verifyAccountAccess(account);
|
|
623
|
+
if (verification.status === 'ok') {
|
|
624
|
+
const wasAccessBlocked = account.verificationRequired === true ||
|
|
625
|
+
account.accountIneligible === true;
|
|
626
|
+
if (account.refreshToken) {
|
|
627
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => clearStoredAccountAccessBlocks(acc, true).changed);
|
|
628
|
+
}
|
|
629
|
+
lifecycle
|
|
630
|
+
.getAccountManager()
|
|
631
|
+
?.clearAccountAccessBlocks(i, wasAccessBlocked);
|
|
632
|
+
okCount += 1;
|
|
633
|
+
console.log('ok');
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (verification.status === 'verification-required') {
|
|
637
|
+
if (account.refreshToken) {
|
|
638
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountVerificationRequired(acc, verification.message, verification.verifyUrl));
|
|
639
|
+
}
|
|
640
|
+
lifecycle
|
|
641
|
+
.getAccountManager()
|
|
642
|
+
?.markAccountVerificationRequired(i, verification.message, verification.verifyUrl);
|
|
643
|
+
blockedCount += 1;
|
|
644
|
+
console.log('needs verification');
|
|
645
|
+
const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
|
|
646
|
+
blockedResults.push({
|
|
647
|
+
label,
|
|
648
|
+
message: verification.message,
|
|
649
|
+
verifyUrl,
|
|
650
|
+
});
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (verification.status === 'ineligible') {
|
|
654
|
+
if (account.refreshToken) {
|
|
655
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountIneligible(acc, verification.message));
|
|
656
|
+
}
|
|
657
|
+
lifecycle
|
|
658
|
+
.getAccountManager()
|
|
659
|
+
?.markAccountIneligible(i, verification.message);
|
|
660
|
+
ineligibleCount += 1;
|
|
661
|
+
console.log('ineligible');
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
errorCount += 1;
|
|
665
|
+
console.log(`error (${verification.message})`);
|
|
666
|
+
}
|
|
667
|
+
console.log(`\nVerification summary: ${okCount} ready, ${blockedCount} need verification, ` +
|
|
668
|
+
`${ineligibleCount} ineligible, ${errorCount} errors.`);
|
|
669
|
+
if (blockedResults.length > 0) {
|
|
670
|
+
console.log('\nAccounts needing verification:');
|
|
671
|
+
for (const result of blockedResults) {
|
|
672
|
+
console.log(`\n- ${result.label}`);
|
|
673
|
+
console.log(` ${result.message}`);
|
|
674
|
+
if (result.verifyUrl) {
|
|
675
|
+
console.log(` URL: ${result.verifyUrl}`);
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
console.log(' URL: not provided by API response');
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
console.log('');
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
console.log('');
|
|
685
|
+
}
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
let verifyAccountIndex = menuResult.verifyAccountIndex;
|
|
689
|
+
if (verifyAccountIndex === undefined) {
|
|
690
|
+
verifyAccountIndex =
|
|
691
|
+
await promptAccountIndexForVerification(existingAccounts);
|
|
692
|
+
}
|
|
693
|
+
if (verifyAccountIndex === undefined) {
|
|
694
|
+
console.log('\nVerification cancelled.\n');
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const account = existingStorage.accounts[verifyAccountIndex];
|
|
698
|
+
if (!account) {
|
|
699
|
+
console.log(`\nAccount ${verifyAccountIndex + 1} not found.\n`);
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const label = account.email || `Account ${verifyAccountIndex + 1}`;
|
|
703
|
+
console.log(`\nChecking verification status for ${label}...\n`);
|
|
704
|
+
const verification = await verifyAccountAccess(account);
|
|
705
|
+
if (verification.status === 'ok') {
|
|
706
|
+
const wasAccessBlocked = account.verificationRequired === true ||
|
|
707
|
+
account.accountIneligible === true;
|
|
708
|
+
if (account.refreshToken) {
|
|
709
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => clearStoredAccountAccessBlocks(acc, true).changed);
|
|
710
|
+
}
|
|
711
|
+
lifecycle
|
|
712
|
+
.getAccountManager()
|
|
713
|
+
?.clearAccountAccessBlocks(verifyAccountIndex, wasAccessBlocked);
|
|
714
|
+
if (wasAccessBlocked) {
|
|
715
|
+
console.log(`✓ ${label} is ready for requests and has been re-enabled.\n`);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
console.log(`✓ ${label} is ready for requests.\n`);
|
|
719
|
+
}
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (verification.status === 'verification-required') {
|
|
723
|
+
if (account.refreshToken) {
|
|
724
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountVerificationRequired(acc, verification.message, verification.verifyUrl));
|
|
725
|
+
}
|
|
726
|
+
lifecycle
|
|
727
|
+
.getAccountManager()
|
|
728
|
+
?.markAccountVerificationRequired(verifyAccountIndex, verification.message, verification.verifyUrl);
|
|
729
|
+
const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
|
|
730
|
+
console.log(`⚠ ${label} needs Google verification before it can be used.`);
|
|
731
|
+
if (verification.message) {
|
|
732
|
+
console.log(verification.message);
|
|
733
|
+
}
|
|
734
|
+
console.log(`${label} has been disabled until verification is completed.`);
|
|
735
|
+
if (verifyUrl) {
|
|
736
|
+
console.log(`\nVerification URL:\n${verifyUrl}\n`);
|
|
737
|
+
if (await promptOpenVerificationUrl()) {
|
|
738
|
+
const opened = await openBrowser(verifyUrl);
|
|
739
|
+
if (opened) {
|
|
740
|
+
console.log('Opened verification URL in your browser.\n');
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
console.log('Could not open browser automatically. Please open the URL manually.\n');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
console.log('No verification URL was returned. Try re-authenticating this account.\n');
|
|
749
|
+
}
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (verification.status === 'ineligible') {
|
|
753
|
+
if (account.refreshToken) {
|
|
754
|
+
await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountIneligible(acc, verification.message));
|
|
755
|
+
}
|
|
756
|
+
lifecycle
|
|
757
|
+
.getAccountManager()
|
|
758
|
+
?.markAccountIneligible(verifyAccountIndex, verification.message);
|
|
759
|
+
console.log(`⚠ ${label} is not eligible for Antigravity and has been disabled.`);
|
|
760
|
+
console.log(`${verification.message}\n`);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
console.log(`✗ ${label}: ${verification.message}\n`);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
if (menuResult.mode === 'cancel') {
|
|
769
|
+
return {
|
|
770
|
+
url: '',
|
|
771
|
+
instructions: 'Authentication cancelled',
|
|
772
|
+
method: 'auto',
|
|
773
|
+
callback: async () => toV1AuthCallbackResult({
|
|
774
|
+
type: 'failed',
|
|
775
|
+
error: 'Authentication cancelled',
|
|
776
|
+
}),
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
if (menuResult.deleteAccountIndex !== undefined) {
|
|
780
|
+
// Locate the to-be-deleted account by refresh token so a
|
|
781
|
+
// concurrent add cannot shift our index and let us
|
|
782
|
+
// remove the wrong account.
|
|
783
|
+
const targetRefreshToken = existingStorage.accounts[menuResult.deleteAccountIndex]
|
|
784
|
+
?.refreshToken;
|
|
785
|
+
const nextStorage = await accountAccess.mutateAccounts((current) => ({
|
|
786
|
+
...current,
|
|
787
|
+
accounts: targetRefreshToken
|
|
788
|
+
? current.accounts.filter((account) => account.refreshToken !== targetRefreshToken)
|
|
789
|
+
: current.accounts.filter((_, index) => index !== menuResult.deleteAccountIndex),
|
|
790
|
+
activeIndex: 0,
|
|
791
|
+
activeIndexByFamily: { claude: 0, gemini: 0 },
|
|
792
|
+
}));
|
|
793
|
+
const updatedAccounts = nextStorage.accounts;
|
|
794
|
+
// Sync in-memory state so deleted account stops being used immediately
|
|
795
|
+
lifecycle
|
|
796
|
+
.getAccountManager()
|
|
797
|
+
?.removeAccountByIndex(menuResult.deleteAccountIndex);
|
|
798
|
+
console.log('\nAccount deleted.\n');
|
|
799
|
+
if (updatedAccounts.length > 0) {
|
|
800
|
+
const fallbackAccount = updatedAccounts[0];
|
|
801
|
+
if (fallbackAccount?.refreshToken) {
|
|
802
|
+
const fallbackResult = buildAuthSuccessFromStoredAccount(fallbackAccount);
|
|
803
|
+
try {
|
|
804
|
+
await client.auth.set({
|
|
805
|
+
path: { id: providerId },
|
|
806
|
+
body: {
|
|
807
|
+
type: 'oauth',
|
|
808
|
+
refresh: fallbackResult.refresh,
|
|
809
|
+
access: '',
|
|
810
|
+
expires: 0,
|
|
811
|
+
},
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
catch (storeError) {
|
|
815
|
+
log.error('Failed to update stored Antigravity OAuth credentials', { error: String(storeError) });
|
|
816
|
+
}
|
|
817
|
+
const label = fallbackAccount.email || `Account ${1}`;
|
|
818
|
+
return {
|
|
819
|
+
url: '',
|
|
820
|
+
instructions: `Account deleted. Using ${label} for future requests.`,
|
|
821
|
+
method: 'auto',
|
|
822
|
+
callback: async () => toV1AuthCallbackResult(fallbackResult),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
await client.auth.set({
|
|
828
|
+
path: { id: providerId },
|
|
829
|
+
body: {
|
|
830
|
+
type: 'oauth',
|
|
831
|
+
refresh: '',
|
|
832
|
+
access: '',
|
|
833
|
+
expires: 0,
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
catch (storeError) {
|
|
838
|
+
log.error('Failed to clear stored Antigravity OAuth credentials', { error: String(storeError) });
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
url: '',
|
|
842
|
+
instructions: 'All accounts deleted. Run `opencode auth login` to reauthenticate.',
|
|
843
|
+
method: 'auto',
|
|
844
|
+
callback: async () => toV1AuthCallbackResult({
|
|
845
|
+
type: 'failed',
|
|
846
|
+
error: 'All accounts deleted. Reauthentication required.',
|
|
847
|
+
}),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (menuResult.refreshAccountIndex !== undefined) {
|
|
851
|
+
refreshAccountIndex = menuResult.refreshAccountIndex;
|
|
852
|
+
const refreshEmail = existingStorage.accounts[refreshAccountIndex]?.email;
|
|
853
|
+
console.log(`\nRe-authenticating ${refreshEmail || 'account'}...\n`);
|
|
854
|
+
startFresh = false;
|
|
855
|
+
}
|
|
856
|
+
if (menuResult.deleteAll) {
|
|
857
|
+
await clearAccounts();
|
|
858
|
+
console.log('\nAll accounts deleted.\n');
|
|
859
|
+
startFresh = true;
|
|
860
|
+
try {
|
|
861
|
+
await client.auth.set({
|
|
862
|
+
path: { id: providerId },
|
|
863
|
+
body: {
|
|
864
|
+
type: 'oauth',
|
|
865
|
+
refresh: '',
|
|
866
|
+
access: '',
|
|
867
|
+
expires: 0,
|
|
868
|
+
},
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
catch (storeError) {
|
|
872
|
+
log.error('Failed to clear stored Antigravity OAuth credentials', { error: String(storeError) });
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
startFresh = menuResult.mode === 'fresh';
|
|
877
|
+
}
|
|
878
|
+
if (startFresh && !menuResult.deleteAll) {
|
|
879
|
+
console.log('\nStarting fresh - existing accounts will be replaced.\n');
|
|
880
|
+
}
|
|
881
|
+
else if (!startFresh) {
|
|
882
|
+
console.log('\nAdding to existing accounts.\n');
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
while (accounts.length < MAX_OAUTH_ACCOUNTS) {
|
|
886
|
+
console.log(`\n=== Antigravity OAuth (Account ${accounts.length + 1}) ===`);
|
|
887
|
+
const projectId = await promptProjectId();
|
|
888
|
+
let persistedBySharedService = false;
|
|
889
|
+
const loginRequest = {
|
|
890
|
+
projectId,
|
|
891
|
+
noBrowser,
|
|
892
|
+
isHeadless,
|
|
893
|
+
refreshAccountIndex,
|
|
894
|
+
accounts: [...accounts],
|
|
895
|
+
startFresh,
|
|
896
|
+
};
|
|
897
|
+
const result = await (async () => {
|
|
898
|
+
if (!useManualMode && refreshAccountIndex === undefined) {
|
|
899
|
+
try {
|
|
900
|
+
return await performOAuthLogin(loginRequest, {
|
|
901
|
+
authorize: authorizeAntigravity,
|
|
902
|
+
exchange: exchangeAntigravity,
|
|
903
|
+
startListener: startOAuthListener,
|
|
904
|
+
openBrowser: async (url) => {
|
|
905
|
+
await openBrowser(url);
|
|
906
|
+
},
|
|
907
|
+
upsert: async (loginResult) => {
|
|
908
|
+
await persistAccountPool([loginResult], accounts.length === 0 && startFresh);
|
|
909
|
+
persistedBySharedService = true;
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
catch (error) {
|
|
914
|
+
return {
|
|
915
|
+
type: 'failed',
|
|
916
|
+
error: error instanceof Error ? error.message : String(error),
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const authorization = await authorizeAntigravity(projectId);
|
|
921
|
+
const fallbackState = getStateFromAuthorizationUrl(authorization.url);
|
|
922
|
+
console.log(`\nOAuth URL:\n${authorization.url}\n`);
|
|
923
|
+
if (useManualMode) {
|
|
924
|
+
const browserOpened = await openBrowser(authorization.url);
|
|
925
|
+
if (!browserOpened) {
|
|
926
|
+
console.log('Could not open browser automatically.');
|
|
927
|
+
console.log('Please open the URL above manually in your local browser.\n');
|
|
928
|
+
}
|
|
929
|
+
return promptManualOAuthInput(fallbackState);
|
|
930
|
+
}
|
|
931
|
+
let listener = null;
|
|
932
|
+
if (!isHeadless) {
|
|
933
|
+
try {
|
|
934
|
+
listener = await startOAuthListener();
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
937
|
+
listener = null;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (!isHeadless) {
|
|
941
|
+
await openBrowser(authorization.url);
|
|
942
|
+
}
|
|
943
|
+
if (listener) {
|
|
944
|
+
try {
|
|
945
|
+
const SOFT_TIMEOUT_MS = 30000;
|
|
946
|
+
const callbackPromise = listener.waitForCallback();
|
|
947
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('SOFT_TIMEOUT')), SOFT_TIMEOUT_MS));
|
|
948
|
+
let callbackUrl;
|
|
949
|
+
try {
|
|
950
|
+
callbackUrl = await Promise.race([
|
|
951
|
+
callbackPromise,
|
|
952
|
+
timeoutPromise,
|
|
953
|
+
]);
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
if (err instanceof Error &&
|
|
957
|
+
err.message === 'SOFT_TIMEOUT') {
|
|
958
|
+
console.log('\n⏳ Automatic callback not received after 30 seconds.');
|
|
959
|
+
console.log('You can paste the redirect URL manually.\n');
|
|
960
|
+
console.log('OAuth URL (in case you need it again):');
|
|
961
|
+
console.log(`${authorization.url}\n`);
|
|
962
|
+
try {
|
|
963
|
+
await listener.close();
|
|
964
|
+
}
|
|
965
|
+
catch { }
|
|
966
|
+
return promptManualOAuthInput(fallbackState);
|
|
967
|
+
}
|
|
968
|
+
throw err;
|
|
969
|
+
}
|
|
970
|
+
const params = extractOAuthCallbackParams(callbackUrl, fallbackState);
|
|
971
|
+
if ('error' in params) {
|
|
972
|
+
return {
|
|
973
|
+
type: 'failed',
|
|
974
|
+
error: params.error,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
return exchangeAntigravity(params.code, params.state);
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
if (error instanceof Error &&
|
|
981
|
+
error.message !== 'SOFT_TIMEOUT') {
|
|
982
|
+
return {
|
|
983
|
+
type: 'failed',
|
|
984
|
+
error: error.message,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
type: 'failed',
|
|
989
|
+
error: error instanceof Error
|
|
990
|
+
? error.message
|
|
991
|
+
: 'Unknown error',
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
try {
|
|
996
|
+
await listener.close();
|
|
997
|
+
}
|
|
998
|
+
catch { }
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return promptManualOAuthInput(fallbackState);
|
|
1002
|
+
})();
|
|
1003
|
+
if (result.type === 'failed') {
|
|
1004
|
+
if (accounts.length === 0) {
|
|
1005
|
+
return {
|
|
1006
|
+
url: '',
|
|
1007
|
+
instructions: `Authentication failed: ${result.error}`,
|
|
1008
|
+
method: 'auto',
|
|
1009
|
+
callback: async () => toV1AuthCallbackResult(result),
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
console.warn(`[opencode-antigravity-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
accounts.push(result);
|
|
1016
|
+
try {
|
|
1017
|
+
await client.tui.showToast({
|
|
1018
|
+
body: {
|
|
1019
|
+
message: `Account ${accounts.length} authenticated${result.email ? ` (${result.email})` : ''}`,
|
|
1020
|
+
variant: 'success',
|
|
1021
|
+
},
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
catch { }
|
|
1025
|
+
try {
|
|
1026
|
+
if (!persistedBySharedService &&
|
|
1027
|
+
refreshAccountIndex !== undefined) {
|
|
1028
|
+
const currentStorage = await loadAccounts();
|
|
1029
|
+
if (currentStorage) {
|
|
1030
|
+
const targetRefreshToken = currentStorage.accounts[refreshAccountIndex]?.refreshToken;
|
|
1031
|
+
const parts = parseRefreshParts(result.refresh);
|
|
1032
|
+
if (targetRefreshToken && parts.refreshToken) {
|
|
1033
|
+
// Build the replacement INSIDE the locked
|
|
1034
|
+
// callback from the freshest stored account.
|
|
1035
|
+
// A pre-lock snapshot would clobber any
|
|
1036
|
+
// concurrent quota/verification/fingerprint
|
|
1037
|
+
// update that landed between loadAccounts
|
|
1038
|
+
// and the mutate call.
|
|
1039
|
+
await accountAccess.mutateAccounts((current) => {
|
|
1040
|
+
const idx = current.accounts.findIndex((acc) => acc.refreshToken === targetRefreshToken);
|
|
1041
|
+
if (idx === -1)
|
|
1042
|
+
return current;
|
|
1043
|
+
const target = current.accounts[idx];
|
|
1044
|
+
if (!target)
|
|
1045
|
+
return current;
|
|
1046
|
+
current.accounts[idx] = {
|
|
1047
|
+
...target,
|
|
1048
|
+
email: result.email ?? target.email,
|
|
1049
|
+
label: result.label ?? target.label,
|
|
1050
|
+
refreshToken: parts.refreshToken,
|
|
1051
|
+
projectId: parts.projectId ?? target.projectId,
|
|
1052
|
+
managedProjectId: parts.managedProjectId ?? target.managedProjectId,
|
|
1053
|
+
addedAt: target.addedAt ?? Date.now(),
|
|
1054
|
+
lastUsed: Date.now(),
|
|
1055
|
+
};
|
|
1056
|
+
return current;
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
else if (!persistedBySharedService) {
|
|
1062
|
+
const isFirstAccount = accounts.length === 1;
|
|
1063
|
+
await persistAccountPool([result], isFirstAccount && startFresh);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
catch (error) {
|
|
1067
|
+
// Fail loud on unreadable storage: re-throw so the
|
|
1068
|
+
// outer OAuth flow can surface a toast and abort the
|
|
1069
|
+
// login instead of silently dropping the new account.
|
|
1070
|
+
if (error instanceof AccountStorageUnreadableError) {
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
// Transient (lock contention, I/O hiccup) → continue.
|
|
1074
|
+
}
|
|
1075
|
+
if (refreshAccountIndex !== undefined) {
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
if (accounts.length >= MAX_OAUTH_ACCOUNTS) {
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
// Get the actual deduplicated account count from storage for the prompt
|
|
1082
|
+
let currentAccountCount = accounts.length;
|
|
1083
|
+
try {
|
|
1084
|
+
const currentStorage = await loadAccounts();
|
|
1085
|
+
if (currentStorage) {
|
|
1086
|
+
currentAccountCount = currentStorage.accounts.length;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
catch {
|
|
1090
|
+
// Fall back to accounts.length if we can't read storage
|
|
1091
|
+
}
|
|
1092
|
+
const addAnother = await promptAddAnotherAccount(currentAccountCount);
|
|
1093
|
+
if (!addAnother) {
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
const primary = accounts[0];
|
|
1098
|
+
if (!primary) {
|
|
1099
|
+
return {
|
|
1100
|
+
url: '',
|
|
1101
|
+
instructions: 'Authentication cancelled',
|
|
1102
|
+
method: 'auto',
|
|
1103
|
+
callback: async () => toV1AuthCallbackResult({
|
|
1104
|
+
type: 'failed',
|
|
1105
|
+
error: 'Authentication cancelled',
|
|
1106
|
+
}),
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
let actualAccountCount = accounts.length;
|
|
1110
|
+
try {
|
|
1111
|
+
const finalStorage = await loadAccounts();
|
|
1112
|
+
if (finalStorage) {
|
|
1113
|
+
actualAccountCount = finalStorage.accounts.length;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
catch { }
|
|
1117
|
+
const successMessage = refreshAccountIndex !== undefined
|
|
1118
|
+
? `Token refreshed successfully.`
|
|
1119
|
+
: `Multi-account setup complete (${actualAccountCount} account(s)).`;
|
|
1120
|
+
return {
|
|
1121
|
+
url: '',
|
|
1122
|
+
instructions: successMessage,
|
|
1123
|
+
method: 'auto',
|
|
1124
|
+
callback: async () => primary,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
// TUI flow (`/connect`) does not support per-account prompts.
|
|
1128
|
+
// Default to adding new accounts (non-destructive).
|
|
1129
|
+
// Users can run `opencode auth logout` first if they want a fresh start.
|
|
1130
|
+
const projectId = '';
|
|
1131
|
+
// Check existing accounts count for toast message
|
|
1132
|
+
const existingStorage = await loadAccounts();
|
|
1133
|
+
const existingCount = existingStorage?.accounts.length ?? 0;
|
|
1134
|
+
const useManualFlow = isHeadless || shouldSkipLocalServer();
|
|
1135
|
+
let listener = null;
|
|
1136
|
+
if (!useManualFlow) {
|
|
1137
|
+
try {
|
|
1138
|
+
listener = await startOAuthListener();
|
|
1139
|
+
}
|
|
1140
|
+
catch {
|
|
1141
|
+
listener = null;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const authorization = await authorizeAntigravity(projectId);
|
|
1145
|
+
const fallbackState = getStateFromAuthorizationUrl(authorization.url);
|
|
1146
|
+
if (!useManualFlow) {
|
|
1147
|
+
const browserOpened = await openBrowser(authorization.url);
|
|
1148
|
+
if (!browserOpened) {
|
|
1149
|
+
listener?.close().catch(() => { });
|
|
1150
|
+
listener = null;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if (listener) {
|
|
1154
|
+
return {
|
|
1155
|
+
url: authorization.url,
|
|
1156
|
+
instructions: "Complete sign-in in your browser. We'll automatically detect the redirect back to localhost.",
|
|
1157
|
+
method: 'auto',
|
|
1158
|
+
callback: async () => {
|
|
1159
|
+
const CALLBACK_TIMEOUT_MS = 30000;
|
|
1160
|
+
try {
|
|
1161
|
+
const callbackPromise = listener.waitForCallback();
|
|
1162
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('CALLBACK_TIMEOUT')), CALLBACK_TIMEOUT_MS));
|
|
1163
|
+
let callbackUrl;
|
|
1164
|
+
try {
|
|
1165
|
+
callbackUrl = await Promise.race([
|
|
1166
|
+
callbackPromise,
|
|
1167
|
+
timeoutPromise,
|
|
1168
|
+
]);
|
|
1169
|
+
}
|
|
1170
|
+
catch (err) {
|
|
1171
|
+
if (err instanceof Error &&
|
|
1172
|
+
err.message === 'CALLBACK_TIMEOUT') {
|
|
1173
|
+
return {
|
|
1174
|
+
type: 'failed',
|
|
1175
|
+
error: 'Callback timeout - please use CLI with --no-browser flag for manual input',
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
throw err;
|
|
1179
|
+
}
|
|
1180
|
+
const params = extractOAuthCallbackParams(callbackUrl, fallbackState);
|
|
1181
|
+
if ('error' in params) {
|
|
1182
|
+
return {
|
|
1183
|
+
type: 'failed',
|
|
1184
|
+
error: params.error,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
const result = await exchangeAntigravity(params.code, params.state);
|
|
1188
|
+
if (result.type === 'success') {
|
|
1189
|
+
try {
|
|
1190
|
+
await persistAccountPool([result], false);
|
|
1191
|
+
}
|
|
1192
|
+
catch (persistError) {
|
|
1193
|
+
// Persistence failure is fatal for the login:
|
|
1194
|
+
// returning `type: 'success'` here would lie to the
|
|
1195
|
+
// host (success toast, no error logged) while the
|
|
1196
|
+
// user-visible state is "account vanished".
|
|
1197
|
+
return await reportPersistenceFailure(persistError, client, log);
|
|
1198
|
+
}
|
|
1199
|
+
const newTotal = existingCount + 1;
|
|
1200
|
+
const toastMessage = existingCount > 0
|
|
1201
|
+
? `Added account${result.email ? ` (${result.email})` : ''} - ${newTotal} total`
|
|
1202
|
+
: `Authenticated${result.email ? ` (${result.email})` : ''}`;
|
|
1203
|
+
try {
|
|
1204
|
+
await client.tui.showToast({
|
|
1205
|
+
body: {
|
|
1206
|
+
message: toastMessage,
|
|
1207
|
+
variant: 'success',
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
catch { }
|
|
1212
|
+
}
|
|
1213
|
+
return result;
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
return {
|
|
1217
|
+
type: 'failed',
|
|
1218
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
finally {
|
|
1222
|
+
try {
|
|
1223
|
+
await listener.close();
|
|
1224
|
+
}
|
|
1225
|
+
catch { }
|
|
1226
|
+
}
|
|
1227
|
+
},
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
url: authorization.url,
|
|
1232
|
+
instructions: 'Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code.',
|
|
1233
|
+
method: 'code',
|
|
1234
|
+
callback: async (codeInput) => {
|
|
1235
|
+
const params = parseOAuthCallbackInput(codeInput, fallbackState);
|
|
1236
|
+
if ('error' in params) {
|
|
1237
|
+
return { type: 'failed', error: params.error };
|
|
1238
|
+
}
|
|
1239
|
+
const result = await exchangeAntigravity(params.code, params.state);
|
|
1240
|
+
if (result.type === 'success') {
|
|
1241
|
+
try {
|
|
1242
|
+
// TUI flow adds to existing accounts (non-destructive)
|
|
1243
|
+
await persistAccountPool([result], false);
|
|
1244
|
+
}
|
|
1245
|
+
catch (persistError) {
|
|
1246
|
+
return await reportPersistenceFailure(persistError, client, log);
|
|
1247
|
+
}
|
|
1248
|
+
// Show appropriate toast message
|
|
1249
|
+
const newTotal = existingCount + 1;
|
|
1250
|
+
const toastMessage = existingCount > 0
|
|
1251
|
+
? `Added account${result.email ? ` (${result.email})` : ''} - ${newTotal} total`
|
|
1252
|
+
: `Authenticated${result.email ? ` (${result.email})` : ''}`;
|
|
1253
|
+
try {
|
|
1254
|
+
await client.tui.showToast({
|
|
1255
|
+
body: {
|
|
1256
|
+
message: toastMessage,
|
|
1257
|
+
variant: 'success',
|
|
1258
|
+
},
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
catch {
|
|
1262
|
+
// TUI may not be available
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return result;
|
|
1266
|
+
},
|
|
1267
|
+
};
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
label: 'Manually enter API Key',
|
|
1272
|
+
type: 'api',
|
|
1273
|
+
},
|
|
1274
|
+
];
|
|
1275
|
+
}
|
|
1276
|
+
//# sourceMappingURL=oauth-methods.js.map
|