@cortexkit/opencode-antigravity-auth 1.0.1 → 2.0.0
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 -553
- package/dist/cli.js +3790 -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 +18951 -13145
- 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/accounts.d.ts +7 -236
- package/dist/src/plugin/accounts.d.ts.map +1 -1
- package/dist/src/plugin/accounts.js +31 -1166
- package/dist/src/plugin/accounts.js.map +1 -1
- package/dist/src/plugin/agy-request-metadata.d.ts +2 -0
- package/dist/src/plugin/agy-request-metadata.d.ts.map +1 -0
- package/dist/src/plugin/agy-request-metadata.js +2 -0
- package/dist/src/plugin/agy-request-metadata.js.map +1 -0
- 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 +79 -62
- 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 +35 -0
- package/dist/src/plugin/auth-loader.d.ts.map +1 -0
- package/dist/src/plugin/auth-loader.js +144 -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/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 +276 -0
- package/dist/src/plugin/command-data.d.ts.map +1 -0
- package/dist/src/plugin/command-data.js +325 -0
- package/dist/src/plugin/command-data.js.map +1 -0
- package/dist/src/plugin/commands.d.ts +151 -0
- package/dist/src/plugin/commands.d.ts.map +1 -0
- package/dist/src/plugin/commands.js +639 -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 +21 -2
- package/dist/src/plugin/config/schema.d.ts.map +1 -1
- package/dist/src/plugin/config/schema.js +85 -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 +3 -3
- package/dist/src/plugin/debug.d.ts.map +1 -1
- package/dist/src/plugin/debug.js +85 -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 +1387 -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 +2 -21
- package/dist/src/plugin/image-saver.d.ts.map +1 -1
- package/dist/src/plugin/image-saver.js +22 -56
- package/dist/src/plugin/image-saver.js.map +1 -1
- package/dist/src/plugin/index.d.ts +23 -0
- package/dist/src/plugin/index.d.ts.map +1 -0
- package/dist/src/plugin/index.js +308 -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 +194 -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 +1284 -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 +97 -41
- package/dist/src/plugin/quota.d.ts.map +1 -1
- package/dist/src/plugin/quota.js +273 -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 +2 -1
- package/dist/src/plugin/request-helpers.d.ts.map +1 -1
- package/dist/src/plugin/request-helpers.js +458 -354
- package/dist/src/plugin/request-helpers.js.map +1 -1
- package/dist/src/plugin/request.d.ts +12 -6
- package/dist/src/plugin/request.d.ts.map +1 -1
- package/dist/src/plugin/request.js +801 -390
- 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 +22 -0
- package/dist/src/plugin/session-context.d.ts.map +1 -0
- package/dist/src/plugin/session-context.js +67 -0
- package/dist/src/plugin/session-context.js.map +1 -0
- package/dist/src/plugin/storage.d.ts +59 -114
- package/dist/src/plugin/storage.d.ts.map +1 -1
- package/dist/src/plugin/storage.js +140 -474
- 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 +2 -2
- package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
- package/dist/src/plugin/ui/auth-menu.js +84 -31
- 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 +51 -48
- 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 +240 -0
- package/dist/src/sidebar-state.d.ts.map +1 -0
- package/dist/src/sidebar-state.js +520 -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 +497 -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 +662 -0
- package/dist/src/tui.js.map +1 -0
- package/package.json +46 -22
- package/src/plugin/command-data.ts +651 -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 +717 -0
- package/src/tui/command-dialogs.tsx +699 -0
- package/src/tui/entry.mjs +74 -0
- package/src/tui/file-logger.ts +145 -0
- package/src/tui-compiled/plugin/command-data.ts +651 -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 +717 -0
- package/src/tui-compiled/tui/command-dialogs.tsx +659 -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 +1150 -0
- package/src/tui-preferences.ts +416 -0
- package/src/tui.tsx +1117 -0
- package/dist/src/plugin.d.ts +0 -32
- package/dist/src/plugin.d.ts.map +0 -1
- package/dist/src/plugin.js +0 -3151
- package/dist/src/plugin.js.map +0 -1
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy-safe data service for the data-first slash-command dialogs.
|
|
3
|
+
*
|
|
4
|
+
* `/antigravity-quota` (this task) and the future `/antigravity-account`
|
|
5
|
+
* dialogs (Tasks 10-11) read from a single shared service so they
|
|
6
|
+
* never touch raw account storage, never see the email PII field, and
|
|
7
|
+
* never run quota network I/O during the dialog's open path — the
|
|
8
|
+
* Refresh action is the only path that performs a live fetch.
|
|
9
|
+
*
|
|
10
|
+
* Why this is a separate module:
|
|
11
|
+
*
|
|
12
|
+
* - `commands.ts` owns the slash-command orchestration; mixing the row
|
|
13
|
+
* projection + quota refresh logic in there would balloon the surface
|
|
14
|
+
* for what is fundamentally a small read/refresh service.
|
|
15
|
+
* - The row type (`CommandAccountRow`) is the projection that crosses
|
|
16
|
+
* the PII firewall into the dialog payload. Defining it here (next
|
|
17
|
+
* to the code that constructs it) keeps the firewall reviewable.
|
|
18
|
+
* - Tests can pin the read-vs-refresh boundary, the email redaction, and
|
|
19
|
+
* the refresh-token-keyed persistence in one file rather than chasing
|
|
20
|
+
* them across the dispatcher + RPC + apply layers.
|
|
21
|
+
*
|
|
22
|
+
* Cache-only opening contract (Task 9 operator requirement):
|
|
23
|
+
*
|
|
24
|
+
* `listAccounts()` is a pure read of the live AccountManager view
|
|
25
|
+
* the auth-loader materialized at session start. It performs
|
|
26
|
+
* ZERO quota manager calls — opening the dialog must be instant even
|
|
27
|
+
* when the network is unreachable, and quota refresh must remain an
|
|
28
|
+
* explicit user-driven action so the cached percentages never quietly
|
|
29
|
+
* rewrite themselves behind the user's back.
|
|
30
|
+
*
|
|
31
|
+
* Refresh-token-keyed persistence (Task 9 plan trap):
|
|
32
|
+
*
|
|
33
|
+
* `refreshQuota()` runs the shared quota manager across every enabled
|
|
34
|
+
* account and folds the results back into the live AccountManager +
|
|
35
|
+
* storage. Concurrent OAuth can renumber the flat `accounts[]` array
|
|
36
|
+
* between read and write, so we re-read under the lock and key the
|
|
37
|
+
* update by `refreshToken` (the canonical identity) rather than the
|
|
38
|
+
* array index. This way a successful OAuth add that lands between
|
|
39
|
+
* the read and the write cannot cause the refresh to overwrite the
|
|
40
|
+
* wrong account.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
buildSidebarMachineStateFromAccounts,
|
|
45
|
+
type SidebarAccountRedactionInput,
|
|
46
|
+
setSidebarMachineState,
|
|
47
|
+
} from '../sidebar-state'
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Local copies of the few core types the data service touches.
|
|
51
|
+
*
|
|
52
|
+
* The shipped TUI tree cannot depend on the `@cortexkit/antigravity-auth-core`
|
|
53
|
+
* barrel (only subpath imports like `./file-lock` are allowed) — the
|
|
54
|
+
* compiled `command-data.ts` is copied verbatim into `tui-compiled/`,
|
|
55
|
+
* and the type-import would otherwise leak the full core surface into
|
|
56
|
+
* the dialog render path. Declaring the structural shape locally keeps
|
|
57
|
+
* the compiled tree free of the barrel while preserving duck-typed
|
|
58
|
+
* compatibility with the production quota manager.
|
|
59
|
+
*/
|
|
60
|
+
type CommandDataAccountMetadata = {
|
|
61
|
+
email?: string
|
|
62
|
+
refreshToken: string
|
|
63
|
+
projectId?: string
|
|
64
|
+
managedProjectId?: string
|
|
65
|
+
addedAt: number
|
|
66
|
+
lastUsed: number
|
|
67
|
+
enabled?: boolean
|
|
68
|
+
label?: string
|
|
69
|
+
cachedQuota?: Partial<
|
|
70
|
+
Record<CommandDataQuotaGroup, CommandDataQuotaGroupSummary>
|
|
71
|
+
>
|
|
72
|
+
cachedQuotaUpdatedAt?: number
|
|
73
|
+
accountIneligible?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type CommandDataQuotaGroup =
|
|
77
|
+
| 'claude'
|
|
78
|
+
| 'gemini-pro'
|
|
79
|
+
| 'gemini-flash'
|
|
80
|
+
| 'gpt-oss'
|
|
81
|
+
|
|
82
|
+
type CommandDataQuotaGroupSummary = {
|
|
83
|
+
remainingFraction?: number
|
|
84
|
+
resetTime?: string
|
|
85
|
+
modelCount: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type CommandDataAccountQuotaResult = {
|
|
89
|
+
index: number
|
|
90
|
+
status: 'ok' | 'disabled' | 'error'
|
|
91
|
+
error?: string
|
|
92
|
+
disabled?: boolean
|
|
93
|
+
quota?: {
|
|
94
|
+
groups: Partial<Record<CommandDataQuotaGroup, CommandDataQuotaGroupSummary>>
|
|
95
|
+
perModel?: Array<{
|
|
96
|
+
modelId: string
|
|
97
|
+
displayName?: string
|
|
98
|
+
group: CommandDataQuotaGroup | null
|
|
99
|
+
remainingFraction: number
|
|
100
|
+
resetTime?: string
|
|
101
|
+
}>
|
|
102
|
+
modelCount: number
|
|
103
|
+
error?: string
|
|
104
|
+
}
|
|
105
|
+
updatedAccount?: CommandDataAccountMetadata
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type CommandDataAccountStorage = {
|
|
109
|
+
version: 4
|
|
110
|
+
activeIndex: number
|
|
111
|
+
activeIndexByFamily?: { claude?: number; gemini?: number }
|
|
112
|
+
accounts: CommandDataAccountMetadata[]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Privacy-safe per-account row shown in `/antigravity-*` data-first
|
|
117
|
+
* dialogs. Carries the cached quota percentages and the labels the
|
|
118
|
+
* dialog needs, but NEVER the email — the email stays in private
|
|
119
|
+
* account storage and is invisible to the sidebar and the dialog.
|
|
120
|
+
*
|
|
121
|
+
* `index` is the position in the live account array at the time of
|
|
122
|
+
* projection. It is informational only and is NOT a stable identity
|
|
123
|
+
* across refreshes — concurrent OAuth can renumber the array between
|
|
124
|
+
* the dialog opening and a refresh.
|
|
125
|
+
*/
|
|
126
|
+
export interface CommandAccountRow {
|
|
127
|
+
/** Stable identity for dialog keying (`acct-<index>`). */
|
|
128
|
+
id: string
|
|
129
|
+
/** Position in the live account array at projection time. */
|
|
130
|
+
index: number
|
|
131
|
+
/** Privacy-safe ordinal display label (for example, `Account 1`). */
|
|
132
|
+
label: string
|
|
133
|
+
enabled: boolean
|
|
134
|
+
/** `true` when this row matches the harness-active account. */
|
|
135
|
+
current: boolean
|
|
136
|
+
quota: Array<{
|
|
137
|
+
key: 'claude' | 'gemini-pro' | 'gemini-flash' | 'gpt-oss'
|
|
138
|
+
label: string
|
|
139
|
+
remainingPercent: number | null
|
|
140
|
+
resetAt?: number
|
|
141
|
+
}>
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Quota display label for each supported quota group. Kept here so the
|
|
146
|
+
* dialog and the quota manager agree on the same vocabulary.
|
|
147
|
+
*/
|
|
148
|
+
const QUOTA_GROUP_LABELS: Record<
|
|
149
|
+
CommandAccountRow['quota'][number]['key'],
|
|
150
|
+
string
|
|
151
|
+
> = {
|
|
152
|
+
claude: 'Claude',
|
|
153
|
+
'gemini-pro': 'Gemini Pro',
|
|
154
|
+
'gemini-flash': 'Gemini Flash',
|
|
155
|
+
'gpt-oss': 'GPT-OSS',
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const SUPPORTED_QUOTA_KEYS = [
|
|
159
|
+
'claude',
|
|
160
|
+
'gemini-pro',
|
|
161
|
+
'gemini-flash',
|
|
162
|
+
'gpt-oss',
|
|
163
|
+
] as const satisfies readonly CommandAccountRow['quota'][number]['key'][]
|
|
164
|
+
|
|
165
|
+
interface LiveAccountSnapshot {
|
|
166
|
+
index: number
|
|
167
|
+
refreshToken: string
|
|
168
|
+
label?: string
|
|
169
|
+
enabled: boolean
|
|
170
|
+
active: boolean
|
|
171
|
+
cachedQuota?: Partial<
|
|
172
|
+
Record<CommandDataQuotaGroup, CommandDataQuotaGroupSummary>
|
|
173
|
+
>
|
|
174
|
+
cachedQuotaUpdatedAt?: number
|
|
175
|
+
accountIneligible?: boolean
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function toCommandAccountRow(entry: LiveAccountSnapshot): CommandAccountRow {
|
|
179
|
+
const cached = entry.cachedQuota
|
|
180
|
+
const quota: CommandAccountRow['quota'] = []
|
|
181
|
+
for (const key of SUPPORTED_QUOTA_KEYS) {
|
|
182
|
+
const cachedEntry = cached?.[key]
|
|
183
|
+
if (!cachedEntry) continue
|
|
184
|
+
const fraction = cachedEntry.remainingFraction
|
|
185
|
+
const remainingPercent =
|
|
186
|
+
typeof fraction === 'number' && Number.isFinite(fraction)
|
|
187
|
+
? Math.round(fraction * 100)
|
|
188
|
+
: null
|
|
189
|
+
let resetAt: number | undefined
|
|
190
|
+
if (
|
|
191
|
+
typeof cachedEntry.resetTime === 'string' &&
|
|
192
|
+
cachedEntry.resetTime.length > 0
|
|
193
|
+
) {
|
|
194
|
+
const parsed = Date.parse(cachedEntry.resetTime)
|
|
195
|
+
if (Number.isFinite(parsed)) resetAt = parsed
|
|
196
|
+
}
|
|
197
|
+
quota.push({
|
|
198
|
+
key,
|
|
199
|
+
label: QUOTA_GROUP_LABELS[key],
|
|
200
|
+
remainingPercent,
|
|
201
|
+
resetAt,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
const label = `Account ${entry.index + 1}`
|
|
205
|
+
return {
|
|
206
|
+
id: `acct-${entry.index}`,
|
|
207
|
+
index: entry.index,
|
|
208
|
+
label,
|
|
209
|
+
enabled: entry.enabled,
|
|
210
|
+
current: entry.active,
|
|
211
|
+
quota,
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Live AccountManager view the data service needs.
|
|
217
|
+
*
|
|
218
|
+
* - `getAccounts()` returns the in-memory snapshot — used for cache-only
|
|
219
|
+
* reads during dialog open and for re-reading the post-mutation state.
|
|
220
|
+
* `getAccountsForQuotaCheck()` returns the freshest `AccountMetadataV3`
|
|
221
|
+
* (the canonical shape the quota manager expects).
|
|
222
|
+
* - `updateQuotaCache(index, groups)` + `requestSaveToDisk()` fold the
|
|
223
|
+
* refreshed quota back into the live view and persist it. The service
|
|
224
|
+
* calls them together after the network fetch resolves.
|
|
225
|
+
* - `setAccountEnabled`, `setAccountCurrent`, `removeAccountByIndex`
|
|
226
|
+
* mirror the CLI menu's mutation primitives — the dialog actions
|
|
227
|
+
* (Task 10) reuse them so the TUI never invents its own mutation
|
|
228
|
+
* logic. Each method returns `true` when the live view changed.
|
|
229
|
+
* - `getRefreshTokenAt(index)` lets the service identify the canonical
|
|
230
|
+
* account identity before it reaches the locked storage mutator; a
|
|
231
|
+
* concurrent OAuth could renumber the flat array between the dialog
|
|
232
|
+
* opening and the apply, so keying by refresh token is mandatory.
|
|
233
|
+
* - `flushSaveToDisk()` drains the AccountManager's debounced save so a
|
|
234
|
+
* dialog-triggered mutation lands on disk before the dialog's response
|
|
235
|
+
* returns. Without it, the dialog could toast "Account removed" while
|
|
236
|
+
* the file still carries the old pool.
|
|
237
|
+
* - `activeIndex()` returns the position the harness considers active;
|
|
238
|
+
* the service uses it to mark the matching row as `current: true`.
|
|
239
|
+
*/
|
|
240
|
+
export interface CommandDataAccountManagerView {
|
|
241
|
+
getAccounts(): LiveAccountSnapshot[]
|
|
242
|
+
getAccountsForQuotaCheck(): CommandDataAccountMetadata[]
|
|
243
|
+
updateQuotaCache(
|
|
244
|
+
index: number,
|
|
245
|
+
groups: Partial<
|
|
246
|
+
Record<CommandDataQuotaGroup, CommandDataQuotaGroupSummary>
|
|
247
|
+
>,
|
|
248
|
+
): void
|
|
249
|
+
requestSaveToDisk(): void
|
|
250
|
+
flushSaveToDisk(): Promise<void>
|
|
251
|
+
activeIndex(): number
|
|
252
|
+
/** Enable/disable the account at `index`. Returns true when it changed. */
|
|
253
|
+
setAccountEnabled(index: number, enabled: boolean): boolean
|
|
254
|
+
/** Pin `index` as the active account for every family the dialog cares about. */
|
|
255
|
+
setAccountCurrent(index: number): boolean
|
|
256
|
+
/** Remove the account at `index` from the live view. Returns true when it changed. */
|
|
257
|
+
removeAccountByIndex(index: number): boolean
|
|
258
|
+
/** Canonical refresh token for the account at `index`, or undefined. */
|
|
259
|
+
getRefreshTokenAt(index: number): string | undefined
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Storage adapter the data service uses for re-read-under-lock writes.
|
|
264
|
+
* `mutate` runs the supplied callback against the latest snapshot under
|
|
265
|
+
* the file lock — the callback receives the current storage and may
|
|
266
|
+
* return a replacement; returning the input unchanged is a no-op.
|
|
267
|
+
*
|
|
268
|
+
* The promise resolves with the (possibly mutated) storage snapshot so
|
|
269
|
+
* the data service can await it. A rejected promise signals a failed
|
|
270
|
+
* write — `AccountStorageUnreadableError`, lock contention, or any
|
|
271
|
+
* other I/O failure — which the data service surfaces to the dialog
|
|
272
|
+
* as a friendly error toast.
|
|
273
|
+
*/
|
|
274
|
+
export interface CommandDataStorage {
|
|
275
|
+
mutate(
|
|
276
|
+
mutator: (
|
|
277
|
+
current: CommandDataAccountStorage,
|
|
278
|
+
) =>
|
|
279
|
+
| CommandDataAccountStorage
|
|
280
|
+
| undefined
|
|
281
|
+
| Promise<CommandDataAccountStorage | undefined>,
|
|
282
|
+
): Promise<CommandDataAccountStorage | undefined> | undefined
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Options for `createCommandDataService`. Each field is required so
|
|
287
|
+
* production wiring is explicit — a missing dependency is a startup
|
|
288
|
+
* error, not a silent no-op at dialog-open time.
|
|
289
|
+
*/
|
|
290
|
+
export interface CommandDataServiceOptions {
|
|
291
|
+
accountManagerView: CommandDataAccountManagerView
|
|
292
|
+
quotaManager: {
|
|
293
|
+
refreshAccounts(
|
|
294
|
+
accounts: CommandDataAccountMetadata[],
|
|
295
|
+
options: {
|
|
296
|
+
indexFor?: (account: CommandDataAccountMetadata) => number
|
|
297
|
+
force?: boolean
|
|
298
|
+
},
|
|
299
|
+
): Promise<CommandDataAccountQuotaResult[]>
|
|
300
|
+
}
|
|
301
|
+
/** Path to the label-only sidebar state file. */
|
|
302
|
+
sidebarStateFile: string
|
|
303
|
+
/**
|
|
304
|
+
* Optional storage adapter. When provided, the service persists the
|
|
305
|
+
* refreshed quota to disk via the lock-held mutator (best-effort —
|
|
306
|
+
* a lock contention never breaks the dialog response). When omitted,
|
|
307
|
+
* the service still folds results into the live AccountManager view;
|
|
308
|
+
* the auth-loader is responsible for the on-disk write.
|
|
309
|
+
*/
|
|
310
|
+
storage?: CommandDataStorage
|
|
311
|
+
/** Clock for `cachedQuotaUpdatedAt`. Tests inject a fixed clock. */
|
|
312
|
+
now?: () => number
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Public surface of the command-data service. Each method is the
|
|
317
|
+
* smallest possible projection so the dialog layer never has to know
|
|
318
|
+
* how the underlying quota manager or storage adapter is wired.
|
|
319
|
+
*/
|
|
320
|
+
export interface CommandDataService {
|
|
321
|
+
/**
|
|
322
|
+
* Cache-only snapshot. Performs zero quota manager calls — safe to
|
|
323
|
+
* call as part of the dialog's open path.
|
|
324
|
+
*/
|
|
325
|
+
listAccounts(): Promise<CommandAccountRow[]>
|
|
326
|
+
/**
|
|
327
|
+
* Force-refresh quota through the shared quota manager, persist by
|
|
328
|
+
* refresh token, bump `cachedQuotaUpdatedAt`, and push a label-only
|
|
329
|
+
* sidebar snapshot. Returns the freshly persisted rows so the
|
|
330
|
+
* dialog can re-render in place.
|
|
331
|
+
*/
|
|
332
|
+
refreshQuota(): Promise<CommandAccountRow[]>
|
|
333
|
+
/**
|
|
334
|
+
* Pin `index` as the active account for every family the dialog
|
|
335
|
+
* tracks (claude + gemini). Mutates the live AccountManager AND the
|
|
336
|
+
* locked storage so the new active index survives a restart. Returns
|
|
337
|
+
* the freshly projected rows so the dialog can re-render in place.
|
|
338
|
+
* Returns `null` when the index is out of range or the account has
|
|
339
|
+
* no refresh token — the dialog surfaces the null as a toast.
|
|
340
|
+
*/
|
|
341
|
+
setCurrentAccount(index: number): Promise<CommandAccountRow[] | null>
|
|
342
|
+
/**
|
|
343
|
+
* Flip the `enabled` flag on the account at `index`. Mirrors the CLI
|
|
344
|
+
* menu's "manage" toggle so the on-disk state matches what the CLI
|
|
345
|
+
* would produce. Returns the freshly projected rows (or `null` when
|
|
346
|
+
* the index is invalid or the account is ineligible).
|
|
347
|
+
*/
|
|
348
|
+
toggleAccountEnabled(index: number): Promise<CommandAccountRow[] | null>
|
|
349
|
+
/**
|
|
350
|
+
* Remove the account at `index` from both the live view and the
|
|
351
|
+
* locked storage. Removal renumbers the flat `accounts[]` array so
|
|
352
|
+
* the returned rows use the freshest indices; callers MUST re-key
|
|
353
|
+
* their transient dialog IDs (`acct-${index}`) by the row's `id`
|
|
354
|
+
* field after this method returns.
|
|
355
|
+
*
|
|
356
|
+
* Returns `null` when the index is out of range — the dialog
|
|
357
|
+
* surfaces the null as a toast.
|
|
358
|
+
*/
|
|
359
|
+
removeAccount(index: number): Promise<CommandAccountRow[] | null>
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Build the data service.
|
|
364
|
+
*
|
|
365
|
+
* The factory form (instead of a module-level singleton) keeps the
|
|
366
|
+
* service unit-testable: each test constructs its own dependencies
|
|
367
|
+
* (storage stub, quota manager stub, fixed clock) without touching
|
|
368
|
+
* the production quota path.
|
|
369
|
+
*/
|
|
370
|
+
export function createCommandDataService(
|
|
371
|
+
options: CommandDataServiceOptions,
|
|
372
|
+
): CommandDataService {
|
|
373
|
+
const {
|
|
374
|
+
accountManagerView,
|
|
375
|
+
quotaManager,
|
|
376
|
+
sidebarStateFile,
|
|
377
|
+
storage,
|
|
378
|
+
now = () => Date.now(),
|
|
379
|
+
} = options
|
|
380
|
+
|
|
381
|
+
const projectRows = (): CommandAccountRow[] =>
|
|
382
|
+
accountManagerView.getAccounts().map(toCommandAccountRow)
|
|
383
|
+
|
|
384
|
+
const writeSidebar = (rows: CommandAccountRow[]): void => {
|
|
385
|
+
const accounts: SidebarAccountRedactionInput[] = rows.map((row) => {
|
|
386
|
+
const claude = row.quota.find((q) => q.key === 'claude')
|
|
387
|
+
const geminiPro = row.quota.find((q) => q.key === 'gemini-pro')
|
|
388
|
+
const geminiFlash = row.quota.find((q) => q.key === 'gemini-flash')
|
|
389
|
+
const gptOss = row.quota.find((q) => q.key === 'gpt-oss')
|
|
390
|
+
const toFraction = (
|
|
391
|
+
q: { remainingPercent: number | null } | undefined,
|
|
392
|
+
): { remainingFraction?: number; resetTime?: string } | undefined => {
|
|
393
|
+
if (!q || q.remainingPercent == null) return undefined
|
|
394
|
+
return { remainingFraction: q.remainingPercent / 100 }
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
index: row.index,
|
|
398
|
+
label: row.label,
|
|
399
|
+
enabled: row.enabled,
|
|
400
|
+
current: row.current,
|
|
401
|
+
cachedQuota: {
|
|
402
|
+
claude: toFraction(claude),
|
|
403
|
+
'gemini-pro': toFraction(geminiPro),
|
|
404
|
+
'gemini-flash': toFraction(geminiFlash),
|
|
405
|
+
'gpt-oss': toFraction(gptOss),
|
|
406
|
+
},
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
// Fire-and-forget — the sidebar writer is fenced by its own queue,
|
|
410
|
+
// so a transient lock contention cannot block the dialog response.
|
|
411
|
+
void setSidebarMachineState(
|
|
412
|
+
buildSidebarMachineStateFromAccounts(accounts, { checkedAt: now() }),
|
|
413
|
+
{ stateFile: sidebarStateFile },
|
|
414
|
+
).catch(() => {
|
|
415
|
+
// Lock contention is the only realistic failure mode; the next
|
|
416
|
+
// periodic writer will catch up.
|
|
417
|
+
})
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
async listAccounts() {
|
|
422
|
+
return projectRows()
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
async refreshQuota() {
|
|
426
|
+
const accountsForQuota = accountManagerView.getAccountsForQuotaCheck()
|
|
427
|
+
if (accountsForQuota.length === 0) {
|
|
428
|
+
// Empty pool: still push a clean sidebar so the TUI drops any
|
|
429
|
+
// stale snapshot left over from a previous session.
|
|
430
|
+
writeSidebar([])
|
|
431
|
+
return []
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const results = await quotaManager.refreshAccounts(accountsForQuota, {
|
|
435
|
+
indexFor: (account) => accountsForQuota.indexOf(account),
|
|
436
|
+
force: true,
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
const refreshedAt = now()
|
|
440
|
+
const updates: Array<{
|
|
441
|
+
refreshToken: string
|
|
442
|
+
groups?: Partial<
|
|
443
|
+
Record<CommandDataQuotaGroup, CommandDataQuotaGroupSummary>
|
|
444
|
+
>
|
|
445
|
+
}> = []
|
|
446
|
+
for (const result of results) {
|
|
447
|
+
const refreshToken =
|
|
448
|
+
result.updatedAccount?.refreshToken ??
|
|
449
|
+
accountsForQuota[result.index]?.refreshToken
|
|
450
|
+
if (!refreshToken) continue
|
|
451
|
+
const groups =
|
|
452
|
+
result.status === 'ok' && result.quota?.groups
|
|
453
|
+
? result.quota.groups
|
|
454
|
+
: undefined
|
|
455
|
+
updates.push({ refreshToken, groups })
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Resolve live indexes only after the network request. Numeric indexes
|
|
459
|
+
// from the quota result refer to the original input array and may now
|
|
460
|
+
// identify a different account after a concurrent add/remove.
|
|
461
|
+
const liveIndexByRefreshToken = new Map<string, number>()
|
|
462
|
+
for (const entry of accountManagerView.getAccounts()) {
|
|
463
|
+
liveIndexByRefreshToken.set(entry.refreshToken, entry.index)
|
|
464
|
+
}
|
|
465
|
+
let liveQuotaChanged = false
|
|
466
|
+
for (const update of updates) {
|
|
467
|
+
const liveIndex = liveIndexByRefreshToken.get(update.refreshToken)
|
|
468
|
+
if (liveIndex === undefined || !update.groups) continue
|
|
469
|
+
accountManagerView.updateQuotaCache(liveIndex, update.groups)
|
|
470
|
+
liveQuotaChanged = true
|
|
471
|
+
}
|
|
472
|
+
if (liveQuotaChanged) accountManagerView.requestSaveToDisk()
|
|
473
|
+
|
|
474
|
+
// Persist by canonical refresh token against the latest locked snapshot.
|
|
475
|
+
// A removed account is skipped rather than falling back to its old index.
|
|
476
|
+
if (storage) {
|
|
477
|
+
const updateByRefreshToken = new Map(
|
|
478
|
+
updates.map((update) => [update.refreshToken, update]),
|
|
479
|
+
)
|
|
480
|
+
const writeResult = storage.mutate((current) => ({
|
|
481
|
+
...current,
|
|
482
|
+
accounts: current.accounts.map((entry) => {
|
|
483
|
+
const update = updateByRefreshToken.get(entry.refreshToken)
|
|
484
|
+
if (!update) return entry
|
|
485
|
+
if (update.groups) {
|
|
486
|
+
return {
|
|
487
|
+
...entry,
|
|
488
|
+
cachedQuota: update.groups,
|
|
489
|
+
cachedQuotaUpdatedAt: refreshedAt,
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// Error result keeps the previous cached percentage and only
|
|
493
|
+
// records that a refresh was attempted.
|
|
494
|
+
return {
|
|
495
|
+
...entry,
|
|
496
|
+
cachedQuotaUpdatedAt: refreshedAt,
|
|
497
|
+
}
|
|
498
|
+
}),
|
|
499
|
+
}))
|
|
500
|
+
await Promise.resolve(writeResult).catch(() => {
|
|
501
|
+
// The live AccountManager already carries successful refreshes; its
|
|
502
|
+
// next save can reconcile a transient storage-lock failure.
|
|
503
|
+
})
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Re-read the live view post-mutate so the rows reflect the
|
|
507
|
+
// freshly persisted percentages.
|
|
508
|
+
const rows = projectRows()
|
|
509
|
+
writeSidebar(rows)
|
|
510
|
+
return rows
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
async setCurrentAccount(index) {
|
|
514
|
+
return mutateLiveAndStorage({ action: 'setCurrent', index })
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
async toggleAccountEnabled(index) {
|
|
518
|
+
return mutateLiveAndStorage({ action: 'toggleEnabled', index })
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
async removeAccount(index) {
|
|
522
|
+
return mutateLiveAndStorage({ action: 'remove', index })
|
|
523
|
+
},
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function mutateLiveAndStorage(args: {
|
|
527
|
+
action: 'setCurrent' | 'toggleEnabled' | 'remove'
|
|
528
|
+
index: number
|
|
529
|
+
}): Promise<CommandAccountRow[] | null> {
|
|
530
|
+
const { index, action } = args
|
|
531
|
+
const target = accountManagerView.getAccounts()[index]
|
|
532
|
+
if (!target) return null
|
|
533
|
+
const refreshToken =
|
|
534
|
+
accountManagerView.getRefreshTokenAt(index) ?? target.refreshToken
|
|
535
|
+
if (!refreshToken) return null
|
|
536
|
+
if (
|
|
537
|
+
action === 'toggleEnabled' &&
|
|
538
|
+
target.enabled === false &&
|
|
539
|
+
target.accountIneligible === true
|
|
540
|
+
) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
'This account is ineligible and cannot be enabled until eligibility is rechecked.',
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!storage) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
'CommandDataService is missing a locked-storage adapter; account mutations are disabled.',
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let foundInStorage = false
|
|
553
|
+
let desiredEnabled: boolean | undefined
|
|
554
|
+
let previousEnabled: boolean | undefined
|
|
555
|
+
await storage.mutate((current) => {
|
|
556
|
+
const tokenIdx = current.accounts.findIndex(
|
|
557
|
+
(entry) => entry.refreshToken === refreshToken,
|
|
558
|
+
)
|
|
559
|
+
if (tokenIdx === -1) return current
|
|
560
|
+
foundInStorage = true
|
|
561
|
+
|
|
562
|
+
if (action === 'setCurrent') {
|
|
563
|
+
return {
|
|
564
|
+
...current,
|
|
565
|
+
activeIndex: tokenIdx,
|
|
566
|
+
activeIndexByFamily: { claude: tokenIdx, gemini: tokenIdx },
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (action === 'toggleEnabled') {
|
|
571
|
+
const entry = current.accounts[tokenIdx]
|
|
572
|
+
if (!entry) return current
|
|
573
|
+
previousEnabled = entry.enabled !== false
|
|
574
|
+
desiredEnabled = entry.enabled === false
|
|
575
|
+
if (desiredEnabled && entry.accountIneligible === true) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
'This account is ineligible and cannot be enabled until eligibility is rechecked.',
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
...current,
|
|
582
|
+
accounts: current.accounts.map((account) =>
|
|
583
|
+
account.refreshToken === refreshToken
|
|
584
|
+
? { ...account, enabled: desiredEnabled }
|
|
585
|
+
: account,
|
|
586
|
+
),
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
...current,
|
|
592
|
+
accounts: current.accounts.filter(
|
|
593
|
+
(account) => account.refreshToken !== refreshToken,
|
|
594
|
+
),
|
|
595
|
+
activeIndex: 0,
|
|
596
|
+
activeIndexByFamily: { claude: 0, gemini: 0 },
|
|
597
|
+
}
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
if (!foundInStorage) return null
|
|
601
|
+
|
|
602
|
+
// Re-resolve the live index by canonical identity after the awaited disk
|
|
603
|
+
// transaction. A concurrent OAuth add/remove may have shifted every index.
|
|
604
|
+
const liveIndex = accountManagerView
|
|
605
|
+
.getAccounts()
|
|
606
|
+
.findIndex((account) => account.refreshToken === refreshToken)
|
|
607
|
+
let applied = action === 'remove' && liveIndex === -1
|
|
608
|
+
if (liveIndex !== -1) {
|
|
609
|
+
if (action === 'setCurrent') {
|
|
610
|
+
applied = accountManagerView.setAccountCurrent(liveIndex)
|
|
611
|
+
} else if (action === 'toggleEnabled') {
|
|
612
|
+
applied = accountManagerView.setAccountEnabled(
|
|
613
|
+
liveIndex,
|
|
614
|
+
desiredEnabled === true,
|
|
615
|
+
)
|
|
616
|
+
if (!applied) {
|
|
617
|
+
applied =
|
|
618
|
+
accountManagerView.getAccounts()[liveIndex]?.enabled ===
|
|
619
|
+
desiredEnabled
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
applied = accountManagerView.removeAccountByIndex(liveIndex)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (!applied && action !== 'remove') {
|
|
627
|
+
if (action === 'toggleEnabled' && previousEnabled !== undefined) {
|
|
628
|
+
await storage.mutate((current) => ({
|
|
629
|
+
...current,
|
|
630
|
+
accounts: current.accounts.map((account) =>
|
|
631
|
+
account.refreshToken === refreshToken
|
|
632
|
+
? { ...account, enabled: previousEnabled }
|
|
633
|
+
: account,
|
|
634
|
+
),
|
|
635
|
+
}))
|
|
636
|
+
}
|
|
637
|
+
throw new Error(
|
|
638
|
+
'The account changed while the operation was being applied; reopen the dialog and try again.',
|
|
639
|
+
)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
await accountManagerView.flushSaveToDisk().catch(() => {
|
|
643
|
+
// The locked storage mutation already committed; a later periodic flush
|
|
644
|
+
// can reconcile transient AccountManager lock contention.
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
const rows = projectRows()
|
|
648
|
+
writeSidebar(rows)
|
|
649
|
+
return rows
|
|
650
|
+
}
|
|
651
|
+
}
|