@cortexkit/opencode-antigravity-auth 1.1.0 → 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.
Files changed (371) hide show
  1. package/README.md +225 -569
  2. package/dist/cli.js +3790 -0
  3. package/dist/cli.js.map +7 -0
  4. package/dist/index.d.ts +1 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +18767 -13829
  7. package/dist/index.js.map +4 -4
  8. package/dist/src/antigravity/oauth.d.ts +1 -1
  9. package/dist/src/antigravity/oauth.js +1 -1
  10. package/dist/src/cli.d.ts +21 -0
  11. package/dist/src/cli.d.ts.map +1 -0
  12. package/dist/src/cli.js +237 -0
  13. package/dist/src/cli.js.map +1 -0
  14. package/dist/src/constants.d.ts +1 -1
  15. package/dist/src/constants.js +1 -1
  16. package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -1
  17. package/dist/src/hooks/auto-update-checker/cache.js +13 -13
  18. package/dist/src/hooks/auto-update-checker/cache.js.map +1 -1
  19. package/dist/src/hooks/auto-update-checker/checker.d.ts +1 -1
  20. package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -1
  21. package/dist/src/hooks/auto-update-checker/checker.js +75 -51
  22. package/dist/src/hooks/auto-update-checker/checker.js.map +1 -1
  23. package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -1
  24. package/dist/src/hooks/auto-update-checker/constants.js +12 -12
  25. package/dist/src/hooks/auto-update-checker/constants.js.map +1 -1
  26. package/dist/src/hooks/auto-update-checker/index.d.ts +6 -6
  27. package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -1
  28. package/dist/src/hooks/auto-update-checker/index.js +17 -17
  29. package/dist/src/hooks/auto-update-checker/index.js.map +1 -1
  30. package/dist/src/hooks/auto-update-checker/logging.js +2 -2
  31. package/dist/src/hooks/auto-update-checker/logging.js.map +1 -1
  32. package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -1
  33. package/dist/src/plugin/account-access.d.ts +99 -0
  34. package/dist/src/plugin/account-access.d.ts.map +1 -0
  35. package/dist/src/plugin/account-access.js +480 -0
  36. package/dist/src/plugin/account-access.js.map +1 -0
  37. package/dist/src/plugin/accounts.d.ts +7 -256
  38. package/dist/src/plugin/accounts.d.ts.map +1 -1
  39. package/dist/src/plugin/accounts.js +31 -1367
  40. package/dist/src/plugin/accounts.js.map +1 -1
  41. package/dist/src/plugin/agy-request-metadata.d.ts +1 -1
  42. package/dist/src/plugin/agy-request-metadata.js +1 -1
  43. package/dist/src/plugin/agy-transport.d.ts +1 -1
  44. package/dist/src/plugin/agy-transport.js +1 -1
  45. package/dist/src/plugin/auth-doctor.d.ts +6 -6
  46. package/dist/src/plugin/auth-doctor.d.ts.map +1 -1
  47. package/dist/src/plugin/auth-doctor.js +74 -66
  48. package/dist/src/plugin/auth-doctor.js.map +1 -1
  49. package/dist/src/plugin/auth-drift.d.ts +4 -4
  50. package/dist/src/plugin/auth-drift.d.ts.map +1 -1
  51. package/dist/src/plugin/auth-drift.js +15 -15
  52. package/dist/src/plugin/auth-drift.js.map +1 -1
  53. package/dist/src/plugin/auth-loader.d.ts +35 -0
  54. package/dist/src/plugin/auth-loader.d.ts.map +1 -0
  55. package/dist/src/plugin/auth-loader.js +144 -0
  56. package/dist/src/plugin/auth-loader.js.map +1 -0
  57. package/dist/src/plugin/auth.d.ts +1 -1
  58. package/dist/src/plugin/auth.js +1 -1
  59. package/dist/src/plugin/cache/index.d.ts +1 -1
  60. package/dist/src/plugin/cache/index.d.ts.map +1 -1
  61. package/dist/src/plugin/cache/index.js +1 -1
  62. package/dist/src/plugin/cache/index.js.map +1 -1
  63. package/dist/src/plugin/cache/signature-cache.d.ts +1 -1
  64. package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -1
  65. package/dist/src/plugin/cache/signature-cache.js +23 -24
  66. package/dist/src/plugin/cache/signature-cache.js.map +1 -1
  67. package/dist/src/plugin/cache.d.ts +6 -5
  68. package/dist/src/plugin/cache.d.ts.map +1 -1
  69. package/dist/src/plugin/cache.js +21 -7
  70. package/dist/src/plugin/cache.js.map +1 -1
  71. package/dist/src/plugin/catalog.d.ts +23 -0
  72. package/dist/src/plugin/catalog.d.ts.map +1 -0
  73. package/dist/src/plugin/catalog.js +60 -0
  74. package/dist/src/plugin/catalog.js.map +1 -0
  75. package/dist/src/plugin/cli.d.ts +7 -7
  76. package/dist/src/plugin/cli.d.ts.map +1 -1
  77. package/dist/src/plugin/cli.js +62 -55
  78. package/dist/src/plugin/cli.js.map +1 -1
  79. package/dist/src/plugin/command-data.d.ts +276 -0
  80. package/dist/src/plugin/command-data.d.ts.map +1 -0
  81. package/dist/src/plugin/command-data.js +325 -0
  82. package/dist/src/plugin/command-data.js.map +1 -0
  83. package/dist/src/plugin/commands.d.ts +151 -0
  84. package/dist/src/plugin/commands.d.ts.map +1 -0
  85. package/dist/src/plugin/commands.js +639 -0
  86. package/dist/src/plugin/commands.js.map +1 -0
  87. package/dist/src/plugin/config/index.d.ts +2 -2
  88. package/dist/src/plugin/config/index.d.ts.map +1 -1
  89. package/dist/src/plugin/config/index.js +2 -2
  90. package/dist/src/plugin/config/index.js.map +1 -1
  91. package/dist/src/plugin/config/loader.d.ts +1 -1
  92. package/dist/src/plugin/config/loader.d.ts.map +1 -1
  93. package/dist/src/plugin/config/loader.js +18 -16
  94. package/dist/src/plugin/config/loader.js.map +1 -1
  95. package/dist/src/plugin/config/models.d.ts +2 -2
  96. package/dist/src/plugin/config/models.d.ts.map +1 -1
  97. package/dist/src/plugin/config/models.js +1 -1
  98. package/dist/src/plugin/config/models.js.map +1 -1
  99. package/dist/src/plugin/config/operator-settings-schema.d.ts +29 -0
  100. package/dist/src/plugin/config/operator-settings-schema.d.ts.map +1 -0
  101. package/dist/src/plugin/config/operator-settings-schema.js +28 -0
  102. package/dist/src/plugin/config/operator-settings-schema.js.map +1 -0
  103. package/dist/src/plugin/config/schema.d.ts +21 -2
  104. package/dist/src/plugin/config/schema.d.ts.map +1 -1
  105. package/dist/src/plugin/config/schema.js +85 -25
  106. package/dist/src/plugin/config/schema.js.map +1 -1
  107. package/dist/src/plugin/config/updater.d.ts.map +1 -1
  108. package/dist/src/plugin/config/updater.js +15 -15
  109. package/dist/src/plugin/config/updater.js.map +1 -1
  110. package/dist/src/plugin/config/writer.d.ts +34 -0
  111. package/dist/src/plugin/config/writer.d.ts.map +1 -0
  112. package/dist/src/plugin/config/writer.js +81 -0
  113. package/dist/src/plugin/config/writer.js.map +1 -0
  114. package/dist/src/plugin/core/streaming/index.d.ts +1 -1
  115. package/dist/src/plugin/core/streaming/index.d.ts.map +1 -1
  116. package/dist/src/plugin/core/streaming/index.js +1 -1
  117. package/dist/src/plugin/core/streaming/index.js.map +1 -1
  118. package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -1
  119. package/dist/src/plugin/core/streaming/transformer.js +85 -44
  120. package/dist/src/plugin/core/streaming/transformer.js.map +1 -1
  121. package/dist/src/plugin/core/streaming/types.d.ts.map +1 -1
  122. package/dist/src/plugin/debug.d.ts +3 -3
  123. package/dist/src/plugin/debug.d.ts.map +1 -1
  124. package/dist/src/plugin/debug.js +85 -47
  125. package/dist/src/plugin/debug.js.map +1 -1
  126. package/dist/src/plugin/dependencies.d.ts +129 -0
  127. package/dist/src/plugin/dependencies.d.ts.map +1 -0
  128. package/dist/src/plugin/dependencies.js +107 -0
  129. package/dist/src/plugin/dependencies.js.map +1 -0
  130. package/dist/src/plugin/errors.d.ts +27 -0
  131. package/dist/src/plugin/errors.d.ts.map +1 -1
  132. package/dist/src/plugin/errors.js +29 -3
  133. package/dist/src/plugin/errors.js.map +1 -1
  134. package/dist/src/plugin/event-handler.d.ts +53 -0
  135. package/dist/src/plugin/event-handler.d.ts.map +1 -0
  136. package/dist/src/plugin/event-handler.js +101 -0
  137. package/dist/src/plugin/event-handler.js.map +1 -0
  138. package/dist/src/plugin/fetch/retry-state.d.ts +61 -0
  139. package/dist/src/plugin/fetch/retry-state.d.ts.map +1 -0
  140. package/dist/src/plugin/fetch/retry-state.js +248 -0
  141. package/dist/src/plugin/fetch/retry-state.js.map +1 -0
  142. package/dist/src/plugin/fetch/warmup.d.ts +30 -0
  143. package/dist/src/plugin/fetch/warmup.d.ts.map +1 -0
  144. package/dist/src/plugin/fetch/warmup.js +76 -0
  145. package/dist/src/plugin/fetch/warmup.js.map +1 -0
  146. package/dist/src/plugin/fetch-interceptor.d.ts +59 -0
  147. package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -0
  148. package/dist/src/plugin/fetch-interceptor.js +1387 -0
  149. package/dist/src/plugin/fetch-interceptor.js.map +1 -0
  150. package/dist/src/plugin/fetch-routing.d.ts +26 -0
  151. package/dist/src/plugin/fetch-routing.d.ts.map +1 -0
  152. package/dist/src/plugin/fetch-routing.js +99 -0
  153. package/dist/src/plugin/fetch-routing.js.map +1 -0
  154. package/dist/src/plugin/fingerprint.d.ts +1 -1
  155. package/dist/src/plugin/fingerprint.js +1 -1
  156. package/dist/src/plugin/gemini-dump.d.ts +5 -5
  157. package/dist/src/plugin/gemini-dump.d.ts.map +1 -1
  158. package/dist/src/plugin/gemini-dump.js +109 -55
  159. package/dist/src/plugin/gemini-dump.js.map +1 -1
  160. package/dist/src/plugin/google-search-tool.d.ts +9 -0
  161. package/dist/src/plugin/google-search-tool.d.ts.map +1 -0
  162. package/dist/src/plugin/google-search-tool.js +56 -0
  163. package/dist/src/plugin/google-search-tool.js.map +1 -0
  164. package/dist/src/plugin/image-saver.d.ts.map +1 -1
  165. package/dist/src/plugin/image-saver.js +16 -16
  166. package/dist/src/plugin/image-saver.js.map +1 -1
  167. package/dist/src/plugin/index.d.ts +23 -0
  168. package/dist/src/plugin/index.d.ts.map +1 -0
  169. package/dist/src/plugin/index.js +308 -0
  170. package/dist/src/plugin/index.js.map +1 -0
  171. package/dist/src/plugin/killswitch.d.ts +94 -0
  172. package/dist/src/plugin/killswitch.d.ts.map +1 -0
  173. package/dist/src/plugin/killswitch.js +194 -0
  174. package/dist/src/plugin/killswitch.js.map +1 -0
  175. package/dist/src/plugin/lifecycle.d.ts +40 -0
  176. package/dist/src/plugin/lifecycle.d.ts.map +1 -0
  177. package/dist/src/plugin/lifecycle.js +73 -0
  178. package/dist/src/plugin/lifecycle.js.map +1 -0
  179. package/dist/src/plugin/logger.d.ts +10 -1
  180. package/dist/src/plugin/logger.d.ts.map +1 -1
  181. package/dist/src/plugin/logger.js +38 -12
  182. package/dist/src/plugin/logger.js.map +1 -1
  183. package/dist/src/plugin/logging-utils.d.ts +35 -1
  184. package/dist/src/plugin/logging-utils.d.ts.map +1 -1
  185. package/dist/src/plugin/logging-utils.js +98 -15
  186. package/dist/src/plugin/logging-utils.js.map +1 -1
  187. package/dist/src/plugin/model-registry.d.ts +1 -1
  188. package/dist/src/plugin/model-registry.js +1 -1
  189. package/dist/src/plugin/oauth-login.d.ts +22 -0
  190. package/dist/src/plugin/oauth-login.d.ts.map +1 -0
  191. package/dist/src/plugin/oauth-login.js +37 -0
  192. package/dist/src/plugin/oauth-login.js.map +1 -0
  193. package/dist/src/plugin/oauth-methods.d.ts +42 -0
  194. package/dist/src/plugin/oauth-methods.d.ts.map +1 -0
  195. package/dist/src/plugin/oauth-methods.js +1284 -0
  196. package/dist/src/plugin/oauth-methods.js.map +1 -0
  197. package/dist/src/plugin/operator-settings.d.ts +40 -0
  198. package/dist/src/plugin/operator-settings.d.ts.map +1 -0
  199. package/dist/src/plugin/operator-settings.js +148 -0
  200. package/dist/src/plugin/operator-settings.js.map +1 -0
  201. package/dist/src/plugin/persist-account-pool.d.ts +37 -0
  202. package/dist/src/plugin/persist-account-pool.d.ts.map +1 -0
  203. package/dist/src/plugin/persist-account-pool.js +142 -0
  204. package/dist/src/plugin/persist-account-pool.js.map +1 -0
  205. package/dist/src/plugin/project.d.ts +1 -1
  206. package/dist/src/plugin/project.js +1 -1
  207. package/dist/src/plugin/prompt-context.d.ts.map +1 -1
  208. package/dist/src/plugin/prompt-context.js +11 -11
  209. package/dist/src/plugin/prompt-context.js.map +1 -1
  210. package/dist/src/plugin/quota.d.ts +97 -41
  211. package/dist/src/plugin/quota.d.ts.map +1 -1
  212. package/dist/src/plugin/quota.js +273 -285
  213. package/dist/src/plugin/quota.js.map +1 -1
  214. package/dist/src/plugin/recovery/constants.d.ts.map +1 -1
  215. package/dist/src/plugin/recovery/constants.js +24 -15
  216. package/dist/src/plugin/recovery/constants.js.map +1 -1
  217. package/dist/src/plugin/recovery/index.d.ts +3 -3
  218. package/dist/src/plugin/recovery/index.d.ts.map +1 -1
  219. package/dist/src/plugin/recovery/index.js +3 -3
  220. package/dist/src/plugin/recovery/index.js.map +1 -1
  221. package/dist/src/plugin/recovery/storage.d.ts +1 -1
  222. package/dist/src/plugin/recovery/storage.d.ts.map +1 -1
  223. package/dist/src/plugin/recovery/storage.js +31 -39
  224. package/dist/src/plugin/recovery/storage.js.map +1 -1
  225. package/dist/src/plugin/recovery/types.d.ts +12 -12
  226. package/dist/src/plugin/recovery/types.d.ts.map +1 -1
  227. package/dist/src/plugin/recovery.d.ts +3 -3
  228. package/dist/src/plugin/recovery.d.ts.map +1 -1
  229. package/dist/src/plugin/recovery.js +62 -58
  230. package/dist/src/plugin/recovery.js.map +1 -1
  231. package/dist/src/plugin/refresh-queue.d.ts +5 -2
  232. package/dist/src/plugin/refresh-queue.d.ts.map +1 -1
  233. package/dist/src/plugin/refresh-queue.js +37 -15
  234. package/dist/src/plugin/refresh-queue.js.map +1 -1
  235. package/dist/src/plugin/request-helpers.d.ts +1 -1
  236. package/dist/src/plugin/request-helpers.d.ts.map +1 -1
  237. package/dist/src/plugin/request-helpers.js +451 -352
  238. package/dist/src/plugin/request-helpers.js.map +1 -1
  239. package/dist/src/plugin/request.d.ts +7 -7
  240. package/dist/src/plugin/request.d.ts.map +1 -1
  241. package/dist/src/plugin/request.js +622 -384
  242. package/dist/src/plugin/request.js.map +1 -1
  243. package/dist/src/plugin/rotation.d.ts +1 -168
  244. package/dist/src/plugin/rotation.d.ts.map +1 -1
  245. package/dist/src/plugin/rotation.js +1 -327
  246. package/dist/src/plugin/rotation.js.map +1 -1
  247. package/dist/src/plugin/search.d.ts.map +1 -1
  248. package/dist/src/plugin/search.js +39 -35
  249. package/dist/src/plugin/search.js.map +1 -1
  250. package/dist/src/plugin/server.d.ts +1 -1
  251. package/dist/src/plugin/server.d.ts.map +1 -1
  252. package/dist/src/plugin/server.js +42 -37
  253. package/dist/src/plugin/server.js.map +1 -1
  254. package/dist/src/plugin/session-context.d.ts +2 -1
  255. package/dist/src/plugin/session-context.d.ts.map +1 -1
  256. package/dist/src/plugin/session-context.js +11 -7
  257. package/dist/src/plugin/session-context.js.map +1 -1
  258. package/dist/src/plugin/storage.d.ts +59 -120
  259. package/dist/src/plugin/storage.d.ts.map +1 -1
  260. package/dist/src/plugin/storage.js +140 -485
  261. package/dist/src/plugin/storage.js.map +1 -1
  262. package/dist/src/plugin/stores/signature-store.d.ts.map +1 -1
  263. package/dist/src/plugin/stores/signature-store.js.map +1 -1
  264. package/dist/src/plugin/thinking-recovery.d.ts.map +1 -1
  265. package/dist/src/plugin/thinking-recovery.js +35 -33
  266. package/dist/src/plugin/thinking-recovery.js.map +1 -1
  267. package/dist/src/plugin/token.d.ts +2 -2
  268. package/dist/src/plugin/token.d.ts.map +1 -1
  269. package/dist/src/plugin/token.js +30 -21
  270. package/dist/src/plugin/token.js.map +1 -1
  271. package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +1 -1
  272. package/dist/src/plugin/transform/cross-model-sanitizer.js +1 -1
  273. package/dist/src/plugin/transform/index.d.ts +1 -1
  274. package/dist/src/plugin/transform/index.js +1 -1
  275. package/dist/src/plugin/transform/model-resolver.d.ts +1 -1
  276. package/dist/src/plugin/transform/model-resolver.js +1 -1
  277. package/dist/src/plugin/transform/types.d.ts +1 -1
  278. package/dist/src/plugin/transform/types.js +1 -1
  279. package/dist/src/plugin/types.d.ts +25 -69
  280. package/dist/src/plugin/types.d.ts.map +1 -1
  281. package/dist/src/plugin/ui/ansi.d.ts.map +1 -1
  282. package/dist/src/plugin/ui/ansi.js.map +1 -1
  283. package/dist/src/plugin/ui/auth-menu.d.ts +1 -1
  284. package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
  285. package/dist/src/plugin/ui/auth-menu.js +82 -31
  286. package/dist/src/plugin/ui/auth-menu.js.map +1 -1
  287. package/dist/src/plugin/ui/confirm.d.ts.map +1 -1
  288. package/dist/src/plugin/ui/confirm.js.map +1 -1
  289. package/dist/src/plugin/ui/model-status.d.ts +3 -3
  290. package/dist/src/plugin/ui/model-status.d.ts.map +1 -1
  291. package/dist/src/plugin/ui/model-status.js +3 -3
  292. package/dist/src/plugin/ui/model-status.js.map +1 -1
  293. package/dist/src/plugin/ui/quota-status.d.ts +4 -4
  294. package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
  295. package/dist/src/plugin/ui/quota-status.js +51 -49
  296. package/dist/src/plugin/ui/quota-status.js.map +1 -1
  297. package/dist/src/plugin/ui/select.d.ts.map +1 -1
  298. package/dist/src/plugin/ui/select.js +22 -12
  299. package/dist/src/plugin/ui/select.js.map +1 -1
  300. package/dist/src/plugin/version.d.ts +1 -1
  301. package/dist/src/plugin/version.js +1 -1
  302. package/dist/src/rpc/notifications.d.ts +6 -0
  303. package/dist/src/rpc/notifications.d.ts.map +1 -0
  304. package/dist/src/rpc/notifications.js +46 -0
  305. package/dist/src/rpc/notifications.js.map +1 -0
  306. package/dist/src/rpc/port-file.d.ts +13 -0
  307. package/dist/src/rpc/port-file.d.ts.map +1 -0
  308. package/dist/src/rpc/port-file.js +109 -0
  309. package/dist/src/rpc/port-file.js.map +1 -0
  310. package/dist/src/rpc/protocol.d.ts +22 -0
  311. package/dist/src/rpc/protocol.d.ts.map +1 -0
  312. package/dist/src/rpc/protocol.js +1 -0
  313. package/dist/src/rpc/protocol.js.map +1 -0
  314. package/dist/src/rpc/rpc-client.d.ts +10 -0
  315. package/dist/src/rpc/rpc-client.d.ts.map +1 -0
  316. package/dist/src/rpc/rpc-client.js +58 -0
  317. package/dist/src/rpc/rpc-client.js.map +1 -0
  318. package/dist/src/rpc/rpc-dir.d.ts +4 -0
  319. package/dist/src/rpc/rpc-dir.d.ts.map +1 -0
  320. package/dist/src/rpc/rpc-dir.js +19 -0
  321. package/dist/src/rpc/rpc-dir.js.map +1 -0
  322. package/dist/src/rpc/rpc-server.d.ts +13 -0
  323. package/dist/src/rpc/rpc-server.d.ts.map +1 -0
  324. package/dist/src/rpc/rpc-server.js +231 -0
  325. package/dist/src/rpc/rpc-server.js.map +1 -0
  326. package/dist/src/sidebar-state.d.ts +240 -0
  327. package/dist/src/sidebar-state.d.ts.map +1 -0
  328. package/dist/src/sidebar-state.js +520 -0
  329. package/dist/src/sidebar-state.js.map +1 -0
  330. package/dist/src/tui/command-dialogs.d.ts +61 -0
  331. package/dist/src/tui/command-dialogs.d.ts.map +1 -0
  332. package/dist/src/tui/command-dialogs.js +497 -0
  333. package/dist/src/tui/command-dialogs.js.map +1 -0
  334. package/dist/src/tui/file-logger.d.ts +41 -0
  335. package/dist/src/tui/file-logger.d.ts.map +1 -0
  336. package/dist/src/tui/file-logger.js +114 -0
  337. package/dist/src/tui/file-logger.js.map +1 -0
  338. package/dist/src/tui-preferences.d.ts +40 -0
  339. package/dist/src/tui-preferences.d.ts.map +1 -0
  340. package/dist/src/tui-preferences.js +328 -0
  341. package/dist/src/tui-preferences.js.map +1 -0
  342. package/dist/src/tui.d.ts +68 -0
  343. package/dist/src/tui.d.ts.map +1 -0
  344. package/dist/src/tui.js +662 -0
  345. package/dist/src/tui.js.map +1 -0
  346. package/package.json +46 -22
  347. package/src/plugin/command-data.ts +651 -0
  348. package/src/rpc/port-file.ts +143 -0
  349. package/src/rpc/protocol.ts +31 -0
  350. package/src/rpc/rpc-client.ts +102 -0
  351. package/src/rpc/rpc-dir.ts +24 -0
  352. package/src/sidebar-state.ts +717 -0
  353. package/src/tui/command-dialogs.tsx +699 -0
  354. package/src/tui/entry.mjs +74 -0
  355. package/src/tui/file-logger.ts +145 -0
  356. package/src/tui-compiled/plugin/command-data.ts +651 -0
  357. package/src/tui-compiled/rpc/port-file.ts +143 -0
  358. package/src/tui-compiled/rpc/protocol.ts +31 -0
  359. package/src/tui-compiled/rpc/rpc-client.ts +102 -0
  360. package/src/tui-compiled/rpc/rpc-dir.ts +24 -0
  361. package/src/tui-compiled/sidebar-state.ts +717 -0
  362. package/src/tui-compiled/tui/command-dialogs.tsx +659 -0
  363. package/src/tui-compiled/tui/file-logger.ts +145 -0
  364. package/src/tui-compiled/tui-preferences.ts +416 -0
  365. package/src/tui-compiled/tui.tsx +1150 -0
  366. package/src/tui-preferences.ts +416 -0
  367. package/src/tui.tsx +1117 -0
  368. package/dist/src/plugin.d.ts +0 -48
  369. package/dist/src/plugin.d.ts.map +0 -1
  370. package/dist/src/plugin.js +0 -3311
  371. package/dist/src/plugin.js.map +0 -1
@@ -0,0 +1,1284 @@
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: 'Claude', data: groups.claude },
507
+ {
508
+ name: 'Gemini 3 Pro',
509
+ data: groups['gemini-pro'],
510
+ },
511
+ {
512
+ name: 'Gemini 3 Flash',
513
+ data: groups['gemini-flash'],
514
+ },
515
+ { name: 'GPT-OSS', data: groups['gpt-oss'] },
516
+ ].filter((g) => g.data);
517
+ groupEntries.forEach((g, idx) => {
518
+ const isLast = idx === groupEntries.length - 1;
519
+ const connector = isLast ? '└─' : '├─';
520
+ const bar = createProgressBar(g.data.remainingFraction);
521
+ const reset = formatReset(g.data.resetTime, g.data.remainingFraction);
522
+ const status = classifyGroupStatus(g.data);
523
+ const badge = formatQuotaStatusBadge(status);
524
+ const modelName = g.name.padEnd(29);
525
+ console.log(` ${connector} ${modelName} ${bar} ${badge}${reset}`);
526
+ });
527
+ }
528
+ console.log('');
529
+ // Cache quota data for soft quota protection
530
+ const targetRefreshToken = existingStorage.accounts[res.index]?.refreshToken;
531
+ if (!targetRefreshToken)
532
+ continue;
533
+ const updatedAt = Date.now();
534
+ const existing = quotaUpdates.get(targetRefreshToken);
535
+ quotaUpdates.set(targetRefreshToken, {
536
+ quota: res.quota?.groups,
537
+ perModel: res.quota?.perModel,
538
+ updatedAccount: res.updatedAccount,
539
+ updatedAt: existing && existing.updatedAt > updatedAt
540
+ ? existing.updatedAt
541
+ : updatedAt,
542
+ });
543
+ }
544
+ if (quotaUpdates.size > 0) {
545
+ await accountAccess.mutateAccounts((current) => {
546
+ let changed = false;
547
+ for (const [refreshToken, update] of quotaUpdates) {
548
+ const idx = current.accounts.findIndex((acc) => acc.refreshToken === refreshToken);
549
+ if (idx === -1)
550
+ continue;
551
+ const target = current.accounts[idx];
552
+ if (!target)
553
+ continue;
554
+ current.accounts[idx] = {
555
+ ...target,
556
+ ...(update.updatedAccount ?? {}),
557
+ cachedQuota: update.quota,
558
+ cachedPerModelQuota: update.perModel,
559
+ cachedQuotaUpdatedAt: update.updatedAt,
560
+ };
561
+ changed = true;
562
+ }
563
+ return changed ? current : current;
564
+ });
565
+ }
566
+ console.log('');
567
+ continue;
568
+ }
569
+ if (menuResult.mode === 'doctor') {
570
+ const auth = cachedGetAuth
571
+ ? await cachedGetAuth().catch(() => undefined)
572
+ : undefined;
573
+ const versionResolution = getAntigravityVersionResolution();
574
+ const report = createAuthDoctorReport({
575
+ auth,
576
+ storage: existingStorage,
577
+ runtime: {
578
+ antigravityVersion: versionResolution.version,
579
+ antigravityVersionSource: versionResolution.source,
580
+ },
581
+ });
582
+ console.log(`\n${formatAuthDoctorReport(report)}\n`);
583
+ continue;
584
+ }
585
+ if (menuResult.mode === 'manage') {
586
+ if (menuResult.toggleAccountIndex !== undefined) {
587
+ const acc = existingStorage.accounts[menuResult.toggleAccountIndex];
588
+ if (acc) {
589
+ const shouldEnable = acc.enabled === false;
590
+ if (shouldEnable && acc.accountIneligible) {
591
+ console.log(`\n${acc.email || `Account ${menuResult.toggleAccountIndex + 1}`} remains disabled. ` +
592
+ 'Use Verify accounts to recheck eligibility.\n');
593
+ continue;
594
+ }
595
+ if (acc.refreshToken) {
596
+ await mutateAccountByRefreshToken(acc.refreshToken, (target) => {
597
+ target.enabled = shouldEnable;
598
+ return true;
599
+ });
600
+ }
601
+ lifecycle
602
+ .getAccountManager()
603
+ ?.setAccountEnabled(menuResult.toggleAccountIndex, shouldEnable);
604
+ console.log(`\nAccount ${acc.email || menuResult.toggleAccountIndex + 1} ${shouldEnable ? 'enabled' : 'disabled'}.\n`);
605
+ }
606
+ }
607
+ continue;
608
+ }
609
+ if (menuResult.mode === 'verify' ||
610
+ menuResult.mode === 'verify-all') {
611
+ const verifyAll = menuResult.mode === 'verify-all' ||
612
+ menuResult.verifyAll === true;
613
+ if (verifyAll) {
614
+ if (existingStorage.accounts.length === 0) {
615
+ console.log('\nNo accounts available to verify.\n');
616
+ continue;
617
+ }
618
+ console.log(`\nChecking verification status for ${existingStorage.accounts.length} account(s)...\n`);
619
+ let okCount = 0;
620
+ let blockedCount = 0;
621
+ let ineligibleCount = 0;
622
+ let errorCount = 0;
623
+ const blockedResults = [];
624
+ for (let i = 0; i < existingStorage.accounts.length; i++) {
625
+ const account = existingStorage.accounts[i];
626
+ if (!account)
627
+ continue;
628
+ const label = account.email || `Account ${i + 1}`;
629
+ process.stdout.write(`- [${i + 1}/${existingStorage.accounts.length}] ${label} ... `);
630
+ const verification = await verifyAccountAccess(account);
631
+ if (verification.status === 'ok') {
632
+ const wasAccessBlocked = account.verificationRequired === true ||
633
+ account.accountIneligible === true;
634
+ if (account.refreshToken) {
635
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => clearStoredAccountAccessBlocks(acc, true).changed);
636
+ }
637
+ lifecycle
638
+ .getAccountManager()
639
+ ?.clearAccountAccessBlocks(i, wasAccessBlocked);
640
+ okCount += 1;
641
+ console.log('ok');
642
+ continue;
643
+ }
644
+ if (verification.status === 'verification-required') {
645
+ if (account.refreshToken) {
646
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountVerificationRequired(acc, verification.message, verification.verifyUrl));
647
+ }
648
+ lifecycle
649
+ .getAccountManager()
650
+ ?.markAccountVerificationRequired(i, verification.message, verification.verifyUrl);
651
+ blockedCount += 1;
652
+ console.log('needs verification');
653
+ const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
654
+ blockedResults.push({
655
+ label,
656
+ message: verification.message,
657
+ verifyUrl,
658
+ });
659
+ continue;
660
+ }
661
+ if (verification.status === 'ineligible') {
662
+ if (account.refreshToken) {
663
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountIneligible(acc, verification.message));
664
+ }
665
+ lifecycle
666
+ .getAccountManager()
667
+ ?.markAccountIneligible(i, verification.message);
668
+ ineligibleCount += 1;
669
+ console.log('ineligible');
670
+ continue;
671
+ }
672
+ errorCount += 1;
673
+ console.log(`error (${verification.message})`);
674
+ }
675
+ console.log(`\nVerification summary: ${okCount} ready, ${blockedCount} need verification, ` +
676
+ `${ineligibleCount} ineligible, ${errorCount} errors.`);
677
+ if (blockedResults.length > 0) {
678
+ console.log('\nAccounts needing verification:');
679
+ for (const result of blockedResults) {
680
+ console.log(`\n- ${result.label}`);
681
+ console.log(` ${result.message}`);
682
+ if (result.verifyUrl) {
683
+ console.log(` URL: ${result.verifyUrl}`);
684
+ }
685
+ else {
686
+ console.log(' URL: not provided by API response');
687
+ }
688
+ }
689
+ console.log('');
690
+ }
691
+ else {
692
+ console.log('');
693
+ }
694
+ continue;
695
+ }
696
+ let verifyAccountIndex = menuResult.verifyAccountIndex;
697
+ if (verifyAccountIndex === undefined) {
698
+ verifyAccountIndex =
699
+ await promptAccountIndexForVerification(existingAccounts);
700
+ }
701
+ if (verifyAccountIndex === undefined) {
702
+ console.log('\nVerification cancelled.\n');
703
+ continue;
704
+ }
705
+ const account = existingStorage.accounts[verifyAccountIndex];
706
+ if (!account) {
707
+ console.log(`\nAccount ${verifyAccountIndex + 1} not found.\n`);
708
+ continue;
709
+ }
710
+ const label = account.email || `Account ${verifyAccountIndex + 1}`;
711
+ console.log(`\nChecking verification status for ${label}...\n`);
712
+ const verification = await verifyAccountAccess(account);
713
+ if (verification.status === 'ok') {
714
+ const wasAccessBlocked = account.verificationRequired === true ||
715
+ account.accountIneligible === true;
716
+ if (account.refreshToken) {
717
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => clearStoredAccountAccessBlocks(acc, true).changed);
718
+ }
719
+ lifecycle
720
+ .getAccountManager()
721
+ ?.clearAccountAccessBlocks(verifyAccountIndex, wasAccessBlocked);
722
+ if (wasAccessBlocked) {
723
+ console.log(`✓ ${label} is ready for requests and has been re-enabled.\n`);
724
+ }
725
+ else {
726
+ console.log(`✓ ${label} is ready for requests.\n`);
727
+ }
728
+ continue;
729
+ }
730
+ if (verification.status === 'verification-required') {
731
+ if (account.refreshToken) {
732
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountVerificationRequired(acc, verification.message, verification.verifyUrl));
733
+ }
734
+ lifecycle
735
+ .getAccountManager()
736
+ ?.markAccountVerificationRequired(verifyAccountIndex, verification.message, verification.verifyUrl);
737
+ const verifyUrl = verification.verifyUrl ?? account.verificationUrl;
738
+ console.log(`⚠ ${label} needs Google verification before it can be used.`);
739
+ if (verification.message) {
740
+ console.log(verification.message);
741
+ }
742
+ console.log(`${label} has been disabled until verification is completed.`);
743
+ if (verifyUrl) {
744
+ console.log(`\nVerification URL:\n${verifyUrl}\n`);
745
+ if (await promptOpenVerificationUrl()) {
746
+ const opened = await openBrowser(verifyUrl);
747
+ if (opened) {
748
+ console.log('Opened verification URL in your browser.\n');
749
+ }
750
+ else {
751
+ console.log('Could not open browser automatically. Please open the URL manually.\n');
752
+ }
753
+ }
754
+ }
755
+ else {
756
+ console.log('No verification URL was returned. Try re-authenticating this account.\n');
757
+ }
758
+ continue;
759
+ }
760
+ if (verification.status === 'ineligible') {
761
+ if (account.refreshToken) {
762
+ await mutateAccountByRefreshToken(account.refreshToken, (acc) => markStoredAccountIneligible(acc, verification.message));
763
+ }
764
+ lifecycle
765
+ .getAccountManager()
766
+ ?.markAccountIneligible(verifyAccountIndex, verification.message);
767
+ console.log(`⚠ ${label} is not eligible for Antigravity and has been disabled.`);
768
+ console.log(`${verification.message}\n`);
769
+ continue;
770
+ }
771
+ console.log(`✗ ${label}: ${verification.message}\n`);
772
+ continue;
773
+ }
774
+ break;
775
+ }
776
+ if (menuResult.mode === 'cancel') {
777
+ return {
778
+ url: '',
779
+ instructions: 'Authentication cancelled',
780
+ method: 'auto',
781
+ callback: async () => toV1AuthCallbackResult({
782
+ type: 'failed',
783
+ error: 'Authentication cancelled',
784
+ }),
785
+ };
786
+ }
787
+ if (menuResult.deleteAccountIndex !== undefined) {
788
+ // Locate the to-be-deleted account by refresh token so a
789
+ // concurrent add cannot shift our index and let us
790
+ // remove the wrong account.
791
+ const targetRefreshToken = existingStorage.accounts[menuResult.deleteAccountIndex]
792
+ ?.refreshToken;
793
+ const nextStorage = await accountAccess.mutateAccounts((current) => ({
794
+ ...current,
795
+ accounts: targetRefreshToken
796
+ ? current.accounts.filter((account) => account.refreshToken !== targetRefreshToken)
797
+ : current.accounts.filter((_, index) => index !== menuResult.deleteAccountIndex),
798
+ activeIndex: 0,
799
+ activeIndexByFamily: { claude: 0, gemini: 0 },
800
+ }));
801
+ const updatedAccounts = nextStorage.accounts;
802
+ // Sync in-memory state so deleted account stops being used immediately
803
+ lifecycle
804
+ .getAccountManager()
805
+ ?.removeAccountByIndex(menuResult.deleteAccountIndex);
806
+ console.log('\nAccount deleted.\n');
807
+ if (updatedAccounts.length > 0) {
808
+ const fallbackAccount = updatedAccounts[0];
809
+ if (fallbackAccount?.refreshToken) {
810
+ const fallbackResult = buildAuthSuccessFromStoredAccount(fallbackAccount);
811
+ try {
812
+ await client.auth.set({
813
+ path: { id: providerId },
814
+ body: {
815
+ type: 'oauth',
816
+ refresh: fallbackResult.refresh,
817
+ access: '',
818
+ expires: 0,
819
+ },
820
+ });
821
+ }
822
+ catch (storeError) {
823
+ log.error('Failed to update stored Antigravity OAuth credentials', { error: String(storeError) });
824
+ }
825
+ const label = fallbackAccount.email || `Account ${1}`;
826
+ return {
827
+ url: '',
828
+ instructions: `Account deleted. Using ${label} for future requests.`,
829
+ method: 'auto',
830
+ callback: async () => toV1AuthCallbackResult(fallbackResult),
831
+ };
832
+ }
833
+ }
834
+ try {
835
+ await client.auth.set({
836
+ path: { id: providerId },
837
+ body: {
838
+ type: 'oauth',
839
+ refresh: '',
840
+ access: '',
841
+ expires: 0,
842
+ },
843
+ });
844
+ }
845
+ catch (storeError) {
846
+ log.error('Failed to clear stored Antigravity OAuth credentials', { error: String(storeError) });
847
+ }
848
+ return {
849
+ url: '',
850
+ instructions: 'All accounts deleted. Run `opencode auth login` to reauthenticate.',
851
+ method: 'auto',
852
+ callback: async () => toV1AuthCallbackResult({
853
+ type: 'failed',
854
+ error: 'All accounts deleted. Reauthentication required.',
855
+ }),
856
+ };
857
+ }
858
+ if (menuResult.refreshAccountIndex !== undefined) {
859
+ refreshAccountIndex = menuResult.refreshAccountIndex;
860
+ const refreshEmail = existingStorage.accounts[refreshAccountIndex]?.email;
861
+ console.log(`\nRe-authenticating ${refreshEmail || 'account'}...\n`);
862
+ startFresh = false;
863
+ }
864
+ if (menuResult.deleteAll) {
865
+ await clearAccounts();
866
+ console.log('\nAll accounts deleted.\n');
867
+ startFresh = true;
868
+ try {
869
+ await client.auth.set({
870
+ path: { id: providerId },
871
+ body: {
872
+ type: 'oauth',
873
+ refresh: '',
874
+ access: '',
875
+ expires: 0,
876
+ },
877
+ });
878
+ }
879
+ catch (storeError) {
880
+ log.error('Failed to clear stored Antigravity OAuth credentials', { error: String(storeError) });
881
+ }
882
+ }
883
+ else {
884
+ startFresh = menuResult.mode === 'fresh';
885
+ }
886
+ if (startFresh && !menuResult.deleteAll) {
887
+ console.log('\nStarting fresh - existing accounts will be replaced.\n');
888
+ }
889
+ else if (!startFresh) {
890
+ console.log('\nAdding to existing accounts.\n');
891
+ }
892
+ }
893
+ while (accounts.length < MAX_OAUTH_ACCOUNTS) {
894
+ console.log(`\n=== Antigravity OAuth (Account ${accounts.length + 1}) ===`);
895
+ const projectId = await promptProjectId();
896
+ let persistedBySharedService = false;
897
+ const loginRequest = {
898
+ projectId,
899
+ noBrowser,
900
+ isHeadless,
901
+ refreshAccountIndex,
902
+ accounts: [...accounts],
903
+ startFresh,
904
+ };
905
+ const result = await (async () => {
906
+ if (!useManualMode && refreshAccountIndex === undefined) {
907
+ try {
908
+ return await performOAuthLogin(loginRequest, {
909
+ authorize: authorizeAntigravity,
910
+ exchange: exchangeAntigravity,
911
+ startListener: startOAuthListener,
912
+ openBrowser: async (url) => {
913
+ await openBrowser(url);
914
+ },
915
+ upsert: async (loginResult) => {
916
+ await persistAccountPool([loginResult], accounts.length === 0 && startFresh);
917
+ persistedBySharedService = true;
918
+ },
919
+ });
920
+ }
921
+ catch (error) {
922
+ return {
923
+ type: 'failed',
924
+ error: error instanceof Error ? error.message : String(error),
925
+ };
926
+ }
927
+ }
928
+ const authorization = await authorizeAntigravity(projectId);
929
+ const fallbackState = getStateFromAuthorizationUrl(authorization.url);
930
+ console.log(`\nOAuth URL:\n${authorization.url}\n`);
931
+ if (useManualMode) {
932
+ const browserOpened = await openBrowser(authorization.url);
933
+ if (!browserOpened) {
934
+ console.log('Could not open browser automatically.');
935
+ console.log('Please open the URL above manually in your local browser.\n');
936
+ }
937
+ return promptManualOAuthInput(fallbackState);
938
+ }
939
+ let listener = null;
940
+ if (!isHeadless) {
941
+ try {
942
+ listener = await startOAuthListener();
943
+ }
944
+ catch {
945
+ listener = null;
946
+ }
947
+ }
948
+ if (!isHeadless) {
949
+ await openBrowser(authorization.url);
950
+ }
951
+ if (listener) {
952
+ try {
953
+ const SOFT_TIMEOUT_MS = 30000;
954
+ const callbackPromise = listener.waitForCallback();
955
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('SOFT_TIMEOUT')), SOFT_TIMEOUT_MS));
956
+ let callbackUrl;
957
+ try {
958
+ callbackUrl = await Promise.race([
959
+ callbackPromise,
960
+ timeoutPromise,
961
+ ]);
962
+ }
963
+ catch (err) {
964
+ if (err instanceof Error &&
965
+ err.message === 'SOFT_TIMEOUT') {
966
+ console.log('\n⏳ Automatic callback not received after 30 seconds.');
967
+ console.log('You can paste the redirect URL manually.\n');
968
+ console.log('OAuth URL (in case you need it again):');
969
+ console.log(`${authorization.url}\n`);
970
+ try {
971
+ await listener.close();
972
+ }
973
+ catch { }
974
+ return promptManualOAuthInput(fallbackState);
975
+ }
976
+ throw err;
977
+ }
978
+ const params = extractOAuthCallbackParams(callbackUrl, fallbackState);
979
+ if ('error' in params) {
980
+ return {
981
+ type: 'failed',
982
+ error: params.error,
983
+ };
984
+ }
985
+ return exchangeAntigravity(params.code, params.state);
986
+ }
987
+ catch (error) {
988
+ if (error instanceof Error &&
989
+ error.message !== 'SOFT_TIMEOUT') {
990
+ return {
991
+ type: 'failed',
992
+ error: error.message,
993
+ };
994
+ }
995
+ return {
996
+ type: 'failed',
997
+ error: error instanceof Error
998
+ ? error.message
999
+ : 'Unknown error',
1000
+ };
1001
+ }
1002
+ finally {
1003
+ try {
1004
+ await listener.close();
1005
+ }
1006
+ catch { }
1007
+ }
1008
+ }
1009
+ return promptManualOAuthInput(fallbackState);
1010
+ })();
1011
+ if (result.type === 'failed') {
1012
+ if (accounts.length === 0) {
1013
+ return {
1014
+ url: '',
1015
+ instructions: `Authentication failed: ${result.error}`,
1016
+ method: 'auto',
1017
+ callback: async () => toV1AuthCallbackResult(result),
1018
+ };
1019
+ }
1020
+ console.warn(`[opencode-antigravity-auth] Skipping failed account ${accounts.length + 1}: ${result.error}`);
1021
+ break;
1022
+ }
1023
+ accounts.push(result);
1024
+ try {
1025
+ await client.tui.showToast({
1026
+ body: {
1027
+ message: `Account ${accounts.length} authenticated${result.email ? ` (${result.email})` : ''}`,
1028
+ variant: 'success',
1029
+ },
1030
+ });
1031
+ }
1032
+ catch { }
1033
+ try {
1034
+ if (!persistedBySharedService &&
1035
+ refreshAccountIndex !== undefined) {
1036
+ const currentStorage = await loadAccounts();
1037
+ if (currentStorage) {
1038
+ const targetRefreshToken = currentStorage.accounts[refreshAccountIndex]?.refreshToken;
1039
+ const parts = parseRefreshParts(result.refresh);
1040
+ if (targetRefreshToken && parts.refreshToken) {
1041
+ // Build the replacement INSIDE the locked
1042
+ // callback from the freshest stored account.
1043
+ // A pre-lock snapshot would clobber any
1044
+ // concurrent quota/verification/fingerprint
1045
+ // update that landed between loadAccounts
1046
+ // and the mutate call.
1047
+ await accountAccess.mutateAccounts((current) => {
1048
+ const idx = current.accounts.findIndex((acc) => acc.refreshToken === targetRefreshToken);
1049
+ if (idx === -1)
1050
+ return current;
1051
+ const target = current.accounts[idx];
1052
+ if (!target)
1053
+ return current;
1054
+ current.accounts[idx] = {
1055
+ ...target,
1056
+ email: result.email ?? target.email,
1057
+ label: result.label ?? target.label,
1058
+ refreshToken: parts.refreshToken,
1059
+ projectId: parts.projectId ?? target.projectId,
1060
+ managedProjectId: parts.managedProjectId ?? target.managedProjectId,
1061
+ addedAt: target.addedAt ?? Date.now(),
1062
+ lastUsed: Date.now(),
1063
+ };
1064
+ return current;
1065
+ });
1066
+ }
1067
+ }
1068
+ }
1069
+ else if (!persistedBySharedService) {
1070
+ const isFirstAccount = accounts.length === 1;
1071
+ await persistAccountPool([result], isFirstAccount && startFresh);
1072
+ }
1073
+ }
1074
+ catch (error) {
1075
+ // Fail loud on unreadable storage: re-throw so the
1076
+ // outer OAuth flow can surface a toast and abort the
1077
+ // login instead of silently dropping the new account.
1078
+ if (error instanceof AccountStorageUnreadableError) {
1079
+ throw error;
1080
+ }
1081
+ // Transient (lock contention, I/O hiccup) → continue.
1082
+ }
1083
+ if (refreshAccountIndex !== undefined) {
1084
+ break;
1085
+ }
1086
+ if (accounts.length >= MAX_OAUTH_ACCOUNTS) {
1087
+ break;
1088
+ }
1089
+ // Get the actual deduplicated account count from storage for the prompt
1090
+ let currentAccountCount = accounts.length;
1091
+ try {
1092
+ const currentStorage = await loadAccounts();
1093
+ if (currentStorage) {
1094
+ currentAccountCount = currentStorage.accounts.length;
1095
+ }
1096
+ }
1097
+ catch {
1098
+ // Fall back to accounts.length if we can't read storage
1099
+ }
1100
+ const addAnother = await promptAddAnotherAccount(currentAccountCount);
1101
+ if (!addAnother) {
1102
+ break;
1103
+ }
1104
+ }
1105
+ const primary = accounts[0];
1106
+ if (!primary) {
1107
+ return {
1108
+ url: '',
1109
+ instructions: 'Authentication cancelled',
1110
+ method: 'auto',
1111
+ callback: async () => toV1AuthCallbackResult({
1112
+ type: 'failed',
1113
+ error: 'Authentication cancelled',
1114
+ }),
1115
+ };
1116
+ }
1117
+ let actualAccountCount = accounts.length;
1118
+ try {
1119
+ const finalStorage = await loadAccounts();
1120
+ if (finalStorage) {
1121
+ actualAccountCount = finalStorage.accounts.length;
1122
+ }
1123
+ }
1124
+ catch { }
1125
+ const successMessage = refreshAccountIndex !== undefined
1126
+ ? `Token refreshed successfully.`
1127
+ : `Multi-account setup complete (${actualAccountCount} account(s)).`;
1128
+ return {
1129
+ url: '',
1130
+ instructions: successMessage,
1131
+ method: 'auto',
1132
+ callback: async () => primary,
1133
+ };
1134
+ }
1135
+ // TUI flow (`/connect`) does not support per-account prompts.
1136
+ // Default to adding new accounts (non-destructive).
1137
+ // Users can run `opencode auth logout` first if they want a fresh start.
1138
+ const projectId = '';
1139
+ // Check existing accounts count for toast message
1140
+ const existingStorage = await loadAccounts();
1141
+ const existingCount = existingStorage?.accounts.length ?? 0;
1142
+ const useManualFlow = isHeadless || shouldSkipLocalServer();
1143
+ let listener = null;
1144
+ if (!useManualFlow) {
1145
+ try {
1146
+ listener = await startOAuthListener();
1147
+ }
1148
+ catch {
1149
+ listener = null;
1150
+ }
1151
+ }
1152
+ const authorization = await authorizeAntigravity(projectId);
1153
+ const fallbackState = getStateFromAuthorizationUrl(authorization.url);
1154
+ if (!useManualFlow) {
1155
+ const browserOpened = await openBrowser(authorization.url);
1156
+ if (!browserOpened) {
1157
+ listener?.close().catch(() => { });
1158
+ listener = null;
1159
+ }
1160
+ }
1161
+ if (listener) {
1162
+ return {
1163
+ url: authorization.url,
1164
+ instructions: "Complete sign-in in your browser. We'll automatically detect the redirect back to localhost.",
1165
+ method: 'auto',
1166
+ callback: async () => {
1167
+ const CALLBACK_TIMEOUT_MS = 30000;
1168
+ try {
1169
+ const callbackPromise = listener.waitForCallback();
1170
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('CALLBACK_TIMEOUT')), CALLBACK_TIMEOUT_MS));
1171
+ let callbackUrl;
1172
+ try {
1173
+ callbackUrl = await Promise.race([
1174
+ callbackPromise,
1175
+ timeoutPromise,
1176
+ ]);
1177
+ }
1178
+ catch (err) {
1179
+ if (err instanceof Error &&
1180
+ err.message === 'CALLBACK_TIMEOUT') {
1181
+ return {
1182
+ type: 'failed',
1183
+ error: 'Callback timeout - please use CLI with --no-browser flag for manual input',
1184
+ };
1185
+ }
1186
+ throw err;
1187
+ }
1188
+ const params = extractOAuthCallbackParams(callbackUrl, fallbackState);
1189
+ if ('error' in params) {
1190
+ return {
1191
+ type: 'failed',
1192
+ error: params.error,
1193
+ };
1194
+ }
1195
+ const result = await exchangeAntigravity(params.code, params.state);
1196
+ if (result.type === 'success') {
1197
+ try {
1198
+ await persistAccountPool([result], false);
1199
+ }
1200
+ catch (persistError) {
1201
+ // Persistence failure is fatal for the login:
1202
+ // returning `type: 'success'` here would lie to the
1203
+ // host (success toast, no error logged) while the
1204
+ // user-visible state is "account vanished".
1205
+ return await reportPersistenceFailure(persistError, client, log);
1206
+ }
1207
+ const newTotal = existingCount + 1;
1208
+ const toastMessage = existingCount > 0
1209
+ ? `Added account${result.email ? ` (${result.email})` : ''} - ${newTotal} total`
1210
+ : `Authenticated${result.email ? ` (${result.email})` : ''}`;
1211
+ try {
1212
+ await client.tui.showToast({
1213
+ body: {
1214
+ message: toastMessage,
1215
+ variant: 'success',
1216
+ },
1217
+ });
1218
+ }
1219
+ catch { }
1220
+ }
1221
+ return result;
1222
+ }
1223
+ catch (error) {
1224
+ return {
1225
+ type: 'failed',
1226
+ error: error instanceof Error ? error.message : 'Unknown error',
1227
+ };
1228
+ }
1229
+ finally {
1230
+ try {
1231
+ await listener.close();
1232
+ }
1233
+ catch { }
1234
+ }
1235
+ },
1236
+ };
1237
+ }
1238
+ return {
1239
+ url: authorization.url,
1240
+ instructions: 'Visit the URL above, complete OAuth, then paste either the full redirect URL or the authorization code.',
1241
+ method: 'code',
1242
+ callback: async (codeInput) => {
1243
+ const params = parseOAuthCallbackInput(codeInput, fallbackState);
1244
+ if ('error' in params) {
1245
+ return { type: 'failed', error: params.error };
1246
+ }
1247
+ const result = await exchangeAntigravity(params.code, params.state);
1248
+ if (result.type === 'success') {
1249
+ try {
1250
+ // TUI flow adds to existing accounts (non-destructive)
1251
+ await persistAccountPool([result], false);
1252
+ }
1253
+ catch (persistError) {
1254
+ return await reportPersistenceFailure(persistError, client, log);
1255
+ }
1256
+ // Show appropriate toast message
1257
+ const newTotal = existingCount + 1;
1258
+ const toastMessage = existingCount > 0
1259
+ ? `Added account${result.email ? ` (${result.email})` : ''} - ${newTotal} total`
1260
+ : `Authenticated${result.email ? ` (${result.email})` : ''}`;
1261
+ try {
1262
+ await client.tui.showToast({
1263
+ body: {
1264
+ message: toastMessage,
1265
+ variant: 'success',
1266
+ },
1267
+ });
1268
+ }
1269
+ catch {
1270
+ // TUI may not be available
1271
+ }
1272
+ }
1273
+ return result;
1274
+ },
1275
+ };
1276
+ },
1277
+ },
1278
+ {
1279
+ label: 'Manually enter API Key',
1280
+ type: 'api',
1281
+ },
1282
+ ];
1283
+ }
1284
+ //# sourceMappingURL=oauth-methods.js.map