@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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-backed logger for the OpenTUI sidebar tree.
|
|
3
|
+
*
|
|
4
|
+
* The TUI renders directly into the host terminal. Any stray write to the
|
|
5
|
+
* terminal from inside the render path (or any module it imports) corrupts
|
|
6
|
+
* the frame buffer — a single byte out of place shoves every subsequent
|
|
7
|
+
* cell right and breaks the sidebar.
|
|
8
|
+
*
|
|
9
|
+
* This logger writes to a rotating file under the host's log directory and
|
|
10
|
+
* never touches stdout/stderr. The plugin already wires up the same kind of
|
|
11
|
+
* file logger via `debug.ts`; the sidebar gets its own file so log lines
|
|
12
|
+
* from this tree are easy to attribute.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
appendFileSync,
|
|
17
|
+
chmodSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
statSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from 'node:fs'
|
|
24
|
+
import { homedir } from 'node:os'
|
|
25
|
+
import { dirname, join } from 'node:path'
|
|
26
|
+
|
|
27
|
+
export type TuiLogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
28
|
+
|
|
29
|
+
export interface TuiLogger {
|
|
30
|
+
debug(message: string, extra?: Record<string, unknown>): void
|
|
31
|
+
info(message: string, extra?: Record<string, unknown>): void
|
|
32
|
+
warn(message: string, extra?: Record<string, unknown>): void
|
|
33
|
+
error(message: string, extra?: Record<string, unknown>): void
|
|
34
|
+
/** Resolve the path the logger writes to. `undefined` when file logging is disabled. */
|
|
35
|
+
getLogPath(): string | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const LOG_PREFIX = '[opencode-antigravity-auth/tui]'
|
|
39
|
+
const DEFAULT_MAX_BYTES = 1_000_000
|
|
40
|
+
const MAX_KEEP_LINES = 200
|
|
41
|
+
/**
|
|
42
|
+
* Owner-read/write only — matches the rest of the plugin's on-disk
|
|
43
|
+
* sensitive state (account storage, signature cache). Windows ignores
|
|
44
|
+
* POSIX mode bits so the assertion in the test is best-effort there.
|
|
45
|
+
*/
|
|
46
|
+
const FILE_MODE = 0o600
|
|
47
|
+
|
|
48
|
+
interface FileLoggerOptions {
|
|
49
|
+
filePath: string
|
|
50
|
+
maxBytes?: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the on-disk path for the TUI's log file.
|
|
55
|
+
*
|
|
56
|
+
* - `ANTIGRAVITY_AUTH_TUI_LOG_FILE` wins when set (used by tests so they
|
|
57
|
+
* never touch a real user log file).
|
|
58
|
+
* - Otherwise write under `<xdg-state>/cortexkit/antigravity-auth/tui.log`.
|
|
59
|
+
*
|
|
60
|
+
* Falls back to a temp file when the host path cannot be resolved (e.g. no
|
|
61
|
+
* home directory on a hostile CI box); the file logger itself never throws,
|
|
62
|
+
* it just drops the line.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveTuiLogPath(): string {
|
|
65
|
+
const override = process.env.ANTIGRAVITY_AUTH_TUI_LOG_FILE
|
|
66
|
+
if (override && override.trim().length > 0) return override
|
|
67
|
+
const base = process.env.XDG_STATE_HOME || join(homedir(), '.local', 'state')
|
|
68
|
+
return join(base, 'cortexkit', 'antigravity-auth', 'tui.log')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createTuiFileLogger(
|
|
72
|
+
options?: Partial<FileLoggerOptions>,
|
|
73
|
+
): TuiLogger {
|
|
74
|
+
const filePath = options?.filePath ?? resolveTuiLogPath()
|
|
75
|
+
const maxBytes = options?.maxBytes ?? DEFAULT_MAX_BYTES
|
|
76
|
+
return createFileLogger({ filePath, maxBytes })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createFileLogger(options: FileLoggerOptions): TuiLogger {
|
|
80
|
+
const { filePath, maxBytes = DEFAULT_MAX_BYTES } = options
|
|
81
|
+
|
|
82
|
+
const rotateIfNeeded = (): void => {
|
|
83
|
+
try {
|
|
84
|
+
if (!existsSync(filePath)) return
|
|
85
|
+
const stat = statSync(filePath)
|
|
86
|
+
if (stat.size < maxBytes) return
|
|
87
|
+
// Tail-truncate: keep the last MAX_KEEP_LINES, drop the rest. Cheap,
|
|
88
|
+
// bounded, and good enough for a sidecar debug stream.
|
|
89
|
+
const text = readFileSync(filePath, 'utf-8')
|
|
90
|
+
const lines = text.split('\n')
|
|
91
|
+
const keepFrom = Math.max(0, lines.length - MAX_KEEP_LINES)
|
|
92
|
+
const tail = lines.slice(keepFrom).join('\n')
|
|
93
|
+
writeFileSync(filePath, tail, 'utf-8')
|
|
94
|
+
// `writeFileSync` over an existing file keeps its mode; we rotate
|
|
95
|
+
// the file specifically to enforce owner-only access, so re-assert
|
|
96
|
+
// the mode after the truncation.
|
|
97
|
+
chmodSync(filePath, FILE_MODE)
|
|
98
|
+
} catch {
|
|
99
|
+
// Best-effort; ignore rotation errors.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const write = (
|
|
104
|
+
level: TuiLogLevel,
|
|
105
|
+
message: string,
|
|
106
|
+
extra?: Record<string, unknown>,
|
|
107
|
+
): void => {
|
|
108
|
+
try {
|
|
109
|
+
rotateIfNeeded()
|
|
110
|
+
// Enforce 0o700 on the parent directory even when it already
|
|
111
|
+
// exists — `mkdirSync({ recursive: true, mode })` only sets the
|
|
112
|
+
// mode on the leaf directory it creates, so an existing leaky
|
|
113
|
+
// directory would keep its old mode. The post-hoc chmodSync
|
|
114
|
+
// closes the gap.
|
|
115
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 })
|
|
116
|
+
chmodSync(dirname(filePath), 0o700)
|
|
117
|
+
const payload: Record<string, unknown> = {
|
|
118
|
+
ts: Date.now(),
|
|
119
|
+
level,
|
|
120
|
+
message,
|
|
121
|
+
}
|
|
122
|
+
if (extra && Object.keys(extra).length > 0) payload.extra = extra
|
|
123
|
+
// `mode: 0o600` so the log file is owner-only — the same default the
|
|
124
|
+
// plugin's account storage applies. `appendFileSync` only honours the
|
|
125
|
+
// mode flag when the file is created, so existing files keep whatever
|
|
126
|
+
// mode they had. `chmodSync` below re-asserts the mode after every
|
|
127
|
+
// append so a leaked-permissions file is repaired on the next write.
|
|
128
|
+
appendFileSync(filePath, `${LOG_PREFIX} ${JSON.stringify(payload)}\n`, {
|
|
129
|
+
encoding: 'utf-8',
|
|
130
|
+
mode: FILE_MODE,
|
|
131
|
+
})
|
|
132
|
+
chmodSync(filePath, FILE_MODE)
|
|
133
|
+
} catch {
|
|
134
|
+
// Drop the line — file logging must never throw into the render path.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
debug: (message, extra) => write('debug', message, extra),
|
|
140
|
+
info: (message, extra) => write('info', message, extra),
|
|
141
|
+
warn: (message, extra) => write('warn', message, extra),
|
|
142
|
+
error: (message, extra) => write('error', message, extra),
|
|
143
|
+
getLogPath: () => filePath,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { watch } from 'node:fs'
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { basename, dirname, join } from 'node:path'
|
|
6
|
+
import {
|
|
7
|
+
clearTimeout as nativeClearTimeout,
|
|
8
|
+
setTimeout as nativeSetTimeout,
|
|
9
|
+
} from 'node:timers'
|
|
10
|
+
import { acquireFencedFileLock } from '@cortexkit/antigravity-auth-core/file-lock'
|
|
11
|
+
import { applyEdits, modify, type ParseError, parse } from 'jsonc-parser'
|
|
12
|
+
|
|
13
|
+
export const TUI_PREFS_FILE_ENV = 'OPENCODE_TUI_PREFERENCES_FILE'
|
|
14
|
+
const FILE_NAME = 'tui-preferences.jsonc'
|
|
15
|
+
const PREFERENCES_LOCK_NAME = 'preferences'
|
|
16
|
+
const PREFERENCES_LOCK_TTL_MS = 10_000
|
|
17
|
+
const PREFERENCES_LOCK_TIMEOUT_MS = 2_000
|
|
18
|
+
const PREFERENCES_LOCK_RETRY_MS = 25
|
|
19
|
+
|
|
20
|
+
// Shared preferences file for opencode TUI plugins. One top-level key per
|
|
21
|
+
// plugin (short name, e.g. "antigravity-auth"). The file is optional: every
|
|
22
|
+
// reader must fall back to defaults when it is missing or malformed.
|
|
23
|
+
export function getTuiPreferencesFile(): string {
|
|
24
|
+
const override = process.env[TUI_PREFS_FILE_ENV]
|
|
25
|
+
if (override) return override
|
|
26
|
+
const configDir =
|
|
27
|
+
process.env.OPENCODE_CONFIG_DIR ||
|
|
28
|
+
join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode')
|
|
29
|
+
return join(configDir, FILE_NAME)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
33
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Tolerant read: missing file, parse errors, or a non-object root all resolve
|
|
37
|
+
// to {} so the sidebar never crashes on user-edited content. jsonc-parser's
|
|
38
|
+
// fault-tolerant parse can still hand back a partial object for an
|
|
39
|
+
// unterminated/bracketed file or trailing garbage, so we collect errors and
|
|
40
|
+
// treat any reported fault as malformed.
|
|
41
|
+
export async function readTuiPreferencesFile(): Promise<
|
|
42
|
+
Record<string, unknown>
|
|
43
|
+
> {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(getTuiPreferencesFile(), 'utf8')
|
|
46
|
+
const errors: ParseError[] = []
|
|
47
|
+
const root: unknown = parse(raw, errors, { allowTrailingComma: true })
|
|
48
|
+
if (errors.length > 0) return {}
|
|
49
|
+
return isRecord(root) ? root : {}
|
|
50
|
+
} catch {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const PLUGIN_KEY = 'antigravity-auth'
|
|
56
|
+
export const DEFAULT_SLOT_ORDER = 160
|
|
57
|
+
|
|
58
|
+
export interface AntigravityAuthTuiPrefs {
|
|
59
|
+
forceToTop: boolean
|
|
60
|
+
order: number
|
|
61
|
+
startCollapsed: boolean
|
|
62
|
+
rememberCollapsed: boolean
|
|
63
|
+
// null = never persisted; seed the UI from startCollapsed instead.
|
|
64
|
+
collapsed: boolean | null
|
|
65
|
+
pollMs: number
|
|
66
|
+
refreshDebounceMs: number
|
|
67
|
+
header: {
|
|
68
|
+
label: string
|
|
69
|
+
showVersion: boolean
|
|
70
|
+
}
|
|
71
|
+
sections: {
|
|
72
|
+
quota: boolean
|
|
73
|
+
fallbackAccounts: boolean
|
|
74
|
+
routing: boolean
|
|
75
|
+
health: boolean
|
|
76
|
+
}
|
|
77
|
+
appearance: {
|
|
78
|
+
barWidth: number
|
|
79
|
+
barFilledChar: string
|
|
80
|
+
barEmptyChar: string
|
|
81
|
+
warnThreshold: number
|
|
82
|
+
errorThreshold: number
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type AppearancePrefs = AntigravityAuthTuiPrefs['appearance']
|
|
87
|
+
|
|
88
|
+
export const DEFAULT_PREFS: AntigravityAuthTuiPrefs = {
|
|
89
|
+
forceToTop: false,
|
|
90
|
+
order: DEFAULT_SLOT_ORDER,
|
|
91
|
+
startCollapsed: false,
|
|
92
|
+
rememberCollapsed: true,
|
|
93
|
+
collapsed: null,
|
|
94
|
+
pollMs: 1500,
|
|
95
|
+
refreshDebounceMs: 200,
|
|
96
|
+
header: { label: 'ANTIGRAVITY', showVersion: true },
|
|
97
|
+
sections: {
|
|
98
|
+
quota: true,
|
|
99
|
+
fallbackAccounts: true,
|
|
100
|
+
routing: true,
|
|
101
|
+
health: true,
|
|
102
|
+
},
|
|
103
|
+
appearance: {
|
|
104
|
+
barWidth: 10,
|
|
105
|
+
barFilledChar: '█',
|
|
106
|
+
barEmptyChar: '░',
|
|
107
|
+
warnThreshold: 50,
|
|
108
|
+
errorThreshold: 80,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function bool(value: unknown, fallback: boolean): boolean {
|
|
113
|
+
return typeof value === 'boolean' ? value : fallback
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function int(
|
|
117
|
+
value: unknown,
|
|
118
|
+
fallback: number,
|
|
119
|
+
min: number,
|
|
120
|
+
max: number,
|
|
121
|
+
): number {
|
|
122
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback
|
|
123
|
+
return Math.min(Math.max(Math.round(value), min), max)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function label(value: unknown, fallback: string, maxLength: number): string {
|
|
127
|
+
if (typeof value !== 'string' || value.length === 0) return fallback
|
|
128
|
+
return value.slice(0, maxLength)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function char(value: unknown, fallback: string): string {
|
|
132
|
+
if (typeof value !== 'string') return fallback
|
|
133
|
+
const first = [...value][0]
|
|
134
|
+
return first ?? fallback
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Per-key validation: every value is independently clamped/defaulted so one
|
|
138
|
+
// bad entry never poisons the rest. Never throws.
|
|
139
|
+
//
|
|
140
|
+
// Antigravity reads the thresholds as remaining-percent: healthy when the
|
|
141
|
+
// remaining is above `warnThreshold`, warn between `errorThreshold` and
|
|
142
|
+
// `warnThreshold`, error below `errorThreshold`. So `errorThreshold` is the
|
|
143
|
+
// smaller number (the danger floor) and must stay strictly below `warnThreshold`.
|
|
144
|
+
export function resolveAntigravityAuthPrefs(
|
|
145
|
+
root: Record<string, unknown>,
|
|
146
|
+
): AntigravityAuthTuiPrefs {
|
|
147
|
+
const entry = root[PLUGIN_KEY]
|
|
148
|
+
if (!isRecord(entry)) return structuredClone(DEFAULT_PREFS)
|
|
149
|
+
|
|
150
|
+
const d = DEFAULT_PREFS
|
|
151
|
+
const header = isRecord(entry.header) ? entry.header : {}
|
|
152
|
+
const sections = isRecord(entry.sections) ? entry.sections : {}
|
|
153
|
+
const appearance = isRecord(entry.appearance) ? entry.appearance : {}
|
|
154
|
+
|
|
155
|
+
const configuredWarnThreshold = int(
|
|
156
|
+
appearance.warnThreshold,
|
|
157
|
+
d.appearance.warnThreshold,
|
|
158
|
+
0,
|
|
159
|
+
99,
|
|
160
|
+
)
|
|
161
|
+
const configuredErrorThreshold = int(
|
|
162
|
+
appearance.errorThreshold,
|
|
163
|
+
d.appearance.errorThreshold,
|
|
164
|
+
0,
|
|
165
|
+
100,
|
|
166
|
+
)
|
|
167
|
+
// Older preferences stored remaining-percent thresholds, whose error value
|
|
168
|
+
// was lower than warn. Convert that shape while accepting the fleet's new
|
|
169
|
+
// used-percent ordering without a preferences schema migration.
|
|
170
|
+
const [warnThreshold, errorThreshold] =
|
|
171
|
+
configuredErrorThreshold < configuredWarnThreshold
|
|
172
|
+
? [100 - configuredWarnThreshold, 100 - configuredErrorThreshold]
|
|
173
|
+
: [
|
|
174
|
+
configuredWarnThreshold,
|
|
175
|
+
Math.max(
|
|
176
|
+
configuredErrorThreshold,
|
|
177
|
+
Math.min(configuredWarnThreshold + 1, 100),
|
|
178
|
+
),
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
forceToTop: bool(entry.forceToTop, d.forceToTop),
|
|
183
|
+
order: int(entry.order, d.order, -10000, 10000),
|
|
184
|
+
startCollapsed: bool(entry.startCollapsed, d.startCollapsed),
|
|
185
|
+
rememberCollapsed: bool(entry.rememberCollapsed, d.rememberCollapsed),
|
|
186
|
+
collapsed: typeof entry.collapsed === 'boolean' ? entry.collapsed : null,
|
|
187
|
+
pollMs: int(entry.pollMs, d.pollMs, 500, 30000),
|
|
188
|
+
refreshDebounceMs: int(
|
|
189
|
+
entry.refreshDebounceMs,
|
|
190
|
+
d.refreshDebounceMs,
|
|
191
|
+
50,
|
|
192
|
+
5000,
|
|
193
|
+
),
|
|
194
|
+
header: {
|
|
195
|
+
label: label(header.label, d.header.label, 20),
|
|
196
|
+
showVersion: bool(header.showVersion, d.header.showVersion),
|
|
197
|
+
},
|
|
198
|
+
sections: {
|
|
199
|
+
quota: bool(sections.quota, d.sections.quota),
|
|
200
|
+
fallbackAccounts: bool(
|
|
201
|
+
sections.fallbackAccounts,
|
|
202
|
+
d.sections.fallbackAccounts,
|
|
203
|
+
),
|
|
204
|
+
routing: bool(sections.routing, d.sections.routing),
|
|
205
|
+
health: bool(sections.health, d.sections.health),
|
|
206
|
+
},
|
|
207
|
+
appearance: {
|
|
208
|
+
barWidth: int(appearance.barWidth, d.appearance.barWidth, 4, 40),
|
|
209
|
+
barFilledChar: char(appearance.barFilledChar, d.appearance.barFilledChar),
|
|
210
|
+
barEmptyChar: char(appearance.barEmptyChar, d.appearance.barEmptyChar),
|
|
211
|
+
warnThreshold,
|
|
212
|
+
errorThreshold,
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const FORCE_TOP_BASE = -100000
|
|
218
|
+
|
|
219
|
+
// Shared forceToTop convention: forced plugins sort below FORCE_TOP_BASE,
|
|
220
|
+
// ordered among themselves by their top-level key's position in the file, so
|
|
221
|
+
// users reprioritize by reordering keys. The user-facing `order` knob clamps
|
|
222
|
+
// to -10000..10000, strictly above the forced band, so a manual order can
|
|
223
|
+
// never beat forceToTop. Host slots render ascending by order; opencode's
|
|
224
|
+
// built-ins occupy 100-500.
|
|
225
|
+
//
|
|
226
|
+
// Key-naming requirement: plugin keys must be non-integer-like short names
|
|
227
|
+
// (e.g. "antigravity-auth"). JavaScript object key iteration order hoists
|
|
228
|
+
// integer-like keys ("0", "1", "42") ahead of any string keys, which would
|
|
229
|
+
// skew the indexOf-based ordering of forced plugins. The shared
|
|
230
|
+
// `tui-preferences.jsonc` convention requires non-numeric names, so this
|
|
231
|
+
// implementation does not paper over the JS quirk.
|
|
232
|
+
export function computeEffectiveOrder(
|
|
233
|
+
root: Record<string, unknown>,
|
|
234
|
+
pluginKey: string,
|
|
235
|
+
defaultOrder: number,
|
|
236
|
+
): number {
|
|
237
|
+
const entry = root[pluginKey]
|
|
238
|
+
if (!isRecord(entry)) return defaultOrder
|
|
239
|
+
if (entry.forceToTop === true) {
|
|
240
|
+
return FORCE_TOP_BASE + Object.keys(root).indexOf(pluginKey)
|
|
241
|
+
}
|
|
242
|
+
return int(entry.order, defaultOrder, -10000, 10000)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const TEMPLATE = `// Shared preferences for opencode TUI plugins.
|
|
246
|
+
// One top-level key per plugin (short name). See each plugin's README for
|
|
247
|
+
// its supported settings. This file is safe to hand-edit; plugins update
|
|
248
|
+
// individual keys in place and preserve comments.
|
|
249
|
+
{}
|
|
250
|
+
`
|
|
251
|
+
|
|
252
|
+
type JsonValue = string | number | boolean | null
|
|
253
|
+
|
|
254
|
+
async function acquirePreferencesLock(file: string) {
|
|
255
|
+
const deadline = Date.now() + PREFERENCES_LOCK_TIMEOUT_MS
|
|
256
|
+
for (;;) {
|
|
257
|
+
const lock = await acquireFencedFileLock({
|
|
258
|
+
path: file,
|
|
259
|
+
name: PREFERENCES_LOCK_NAME,
|
|
260
|
+
ttlMs: PREFERENCES_LOCK_TTL_MS,
|
|
261
|
+
renew: true,
|
|
262
|
+
})
|
|
263
|
+
if (lock) return lock
|
|
264
|
+
if (Date.now() >= deadline) {
|
|
265
|
+
throw new Error(`Timed out acquiring TUI preferences lock: ${file}`)
|
|
266
|
+
}
|
|
267
|
+
await new Promise<void>((resolve) =>
|
|
268
|
+
nativeSetTimeout(resolve, PREFERENCES_LOCK_RETRY_MS),
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function writePreference(
|
|
274
|
+
pluginKey: string,
|
|
275
|
+
path: string[],
|
|
276
|
+
value: JsonValue,
|
|
277
|
+
): Promise<void> {
|
|
278
|
+
const file = getTuiPreferencesFile()
|
|
279
|
+
await mkdir(dirname(file), { recursive: true })
|
|
280
|
+
const lock = await acquirePreferencesLock(file)
|
|
281
|
+
const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`
|
|
282
|
+
try {
|
|
283
|
+
let text: string
|
|
284
|
+
try {
|
|
285
|
+
text = await readFile(file, 'utf8')
|
|
286
|
+
} catch {
|
|
287
|
+
text = ''
|
|
288
|
+
}
|
|
289
|
+
if (text.trim() === '') text = TEMPLATE
|
|
290
|
+
const edits = modify(text, [pluginKey, ...path], value, {
|
|
291
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
292
|
+
})
|
|
293
|
+
const next = applyEdits(text, edits)
|
|
294
|
+
await writeFile(tmp, next, { encoding: 'utf8', mode: 0o600 })
|
|
295
|
+
await lock.assertOwned()
|
|
296
|
+
await rename(tmp, file)
|
|
297
|
+
} finally {
|
|
298
|
+
await rm(tmp, { force: true }).catch(() => {})
|
|
299
|
+
await lock.release()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let writeChain: Promise<void> = Promise.resolve()
|
|
304
|
+
|
|
305
|
+
// Writes are serialized on a promise chain: each update re-reads the file,
|
|
306
|
+
// applies a minimal comment-preserving edit to one property, and replaces the
|
|
307
|
+
// file atomically (temp + rename in the same directory). Best-effort by
|
|
308
|
+
// design — preferences are never worth crashing the TUI over.
|
|
309
|
+
export function queueTuiPreferenceUpdate(
|
|
310
|
+
pluginKey: string,
|
|
311
|
+
path: string[],
|
|
312
|
+
value: JsonValue,
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
writeChain = writeChain
|
|
315
|
+
.then(() => writePreference(pluginKey, path, value))
|
|
316
|
+
.catch(() => {})
|
|
317
|
+
return writeChain
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const WATCH_DEBOUNCE_MS = 150
|
|
321
|
+
const WATCH_POLL_MS = 100
|
|
322
|
+
|
|
323
|
+
// Watches the directory rather than the file: editors and our own atomic
|
|
324
|
+
// writes replace the file via rename, which kills file-level watchers.
|
|
325
|
+
//
|
|
326
|
+
// Filtering is two-stage:
|
|
327
|
+
// 1. Filename pre-filter: only debounce events for the preferences file
|
|
328
|
+
// name, or our atomic-write temp file. This is a cheap first pass that
|
|
329
|
+
// drops the common case (unrelated sibling files).
|
|
330
|
+
// 2. Content check inside the debounce: after the timer fires, re-read
|
|
331
|
+
// the file and compare it against the last seen content. Only fire
|
|
332
|
+
// `onChange` when the content actually changed. This is the authority.
|
|
333
|
+
// Some platforms (notably macOS FSEvents and a few inotify backends)
|
|
334
|
+
// can misattribute a rename of an unrelated sibling file to the
|
|
335
|
+
// real preferences filename in addition to emitting the sibling's
|
|
336
|
+
// own event, so a name-only filter still produces a stray callback.
|
|
337
|
+
// A content comparison is robust against that, against coalesced
|
|
338
|
+
// events, and against mtime granularity.
|
|
339
|
+
//
|
|
340
|
+
// Returns a disposer; never throws.
|
|
341
|
+
export function watchTuiPreferences(onChange: () => void): () => void {
|
|
342
|
+
const file = getTuiPreferencesFile()
|
|
343
|
+
const name = basename(file)
|
|
344
|
+
let timer: ReturnType<typeof nativeSetTimeout> | null = null
|
|
345
|
+
let lastSeen: string | null = null
|
|
346
|
+
let observedEvent = false
|
|
347
|
+
let disposed = false
|
|
348
|
+
|
|
349
|
+
const checkForChange = async () => {
|
|
350
|
+
const text = await readFile(file, 'utf8').catch(() => null)
|
|
351
|
+
if (disposed || text === null) return
|
|
352
|
+
if (text === lastSeen) return
|
|
353
|
+
lastSeen = text
|
|
354
|
+
onChange()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const scheduleCheck = () => {
|
|
358
|
+
observedEvent = true
|
|
359
|
+
if (timer) nativeClearTimeout(timer)
|
|
360
|
+
timer = nativeSetTimeout(() => {
|
|
361
|
+
timer = null
|
|
362
|
+
void checkForChange()
|
|
363
|
+
}, WATCH_DEBOUNCE_MS)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Seed asynchronously. If a real file event arrives before the read resolves,
|
|
367
|
+
// do not let the seed overwrite that pending change; the debounced re-read
|
|
368
|
+
// will compare against the previous value and fire.
|
|
369
|
+
void readFile(file, 'utf8')
|
|
370
|
+
.then((text) => {
|
|
371
|
+
if (!observedEvent && lastSeen === null) lastSeen = text
|
|
372
|
+
})
|
|
373
|
+
.catch(() => {})
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const watcher = watch(dirname(file), (_event, filename) => {
|
|
377
|
+
// Exact match against the preferences file name, plus the temp file
|
|
378
|
+
// we use for atomic writes (carrying our pid + `.tmp`).
|
|
379
|
+
const isOurs =
|
|
380
|
+
filename === name ||
|
|
381
|
+
(filename?.startsWith(`${name}.`) && filename.endsWith('.tmp'))
|
|
382
|
+
if (filename != null && !isOurs) return
|
|
383
|
+
scheduleCheck()
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
// Bun's fs.watch can miss atomic temp-file rename updates on some
|
|
387
|
+
// backends, so keep a low-cost polling loop as the correctness path.
|
|
388
|
+
// Use recursive timers instead of fs.watchFile because watchFile can keep
|
|
389
|
+
// Bun test processes alive long after unwatchFile().
|
|
390
|
+
let pollTimer: ReturnType<typeof nativeSetTimeout> | null = null
|
|
391
|
+
let pollInFlight = false
|
|
392
|
+
const schedulePoll = () => {
|
|
393
|
+
if (disposed || pollTimer) return
|
|
394
|
+
pollTimer = nativeSetTimeout(() => {
|
|
395
|
+
pollTimer = null
|
|
396
|
+
if (disposed || pollInFlight) return
|
|
397
|
+
pollInFlight = true
|
|
398
|
+
void checkForChange().finally(() => {
|
|
399
|
+
pollInFlight = false
|
|
400
|
+
schedulePoll()
|
|
401
|
+
})
|
|
402
|
+
}, WATCH_POLL_MS)
|
|
403
|
+
pollTimer.unref?.()
|
|
404
|
+
}
|
|
405
|
+
schedulePoll()
|
|
406
|
+
|
|
407
|
+
return () => {
|
|
408
|
+
disposed = true
|
|
409
|
+
if (timer) nativeClearTimeout(timer)
|
|
410
|
+
if (pollTimer) nativeClearTimeout(pollTimer)
|
|
411
|
+
watcher.close()
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
return () => {}
|
|
415
|
+
}
|
|
416
|
+
}
|