@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.
Files changed (379) hide show
  1. package/README.md +225 -569
  2. package/dist/cli.js +4017 -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 +20742 -14586
  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/account-command-oauth.d.ts +34 -0
  38. package/dist/src/plugin/account-command-oauth.d.ts.map +1 -0
  39. package/dist/src/plugin/account-command-oauth.js +156 -0
  40. package/dist/src/plugin/account-command-oauth.js.map +1 -0
  41. package/dist/src/plugin/accounts.d.ts +7 -256
  42. package/dist/src/plugin/accounts.d.ts.map +1 -1
  43. package/dist/src/plugin/accounts.js +31 -1367
  44. package/dist/src/plugin/accounts.js.map +1 -1
  45. package/dist/src/plugin/agy-request-metadata.d.ts +1 -1
  46. package/dist/src/plugin/agy-request-metadata.js +1 -1
  47. package/dist/src/plugin/agy-transport.d.ts +1 -1
  48. package/dist/src/plugin/agy-transport.js +1 -1
  49. package/dist/src/plugin/auth-doctor.d.ts +6 -6
  50. package/dist/src/plugin/auth-doctor.d.ts.map +1 -1
  51. package/dist/src/plugin/auth-doctor.js +74 -66
  52. package/dist/src/plugin/auth-doctor.js.map +1 -1
  53. package/dist/src/plugin/auth-drift.d.ts +4 -4
  54. package/dist/src/plugin/auth-drift.d.ts.map +1 -1
  55. package/dist/src/plugin/auth-drift.js +15 -15
  56. package/dist/src/plugin/auth-drift.js.map +1 -1
  57. package/dist/src/plugin/auth-loader.d.ts +50 -0
  58. package/dist/src/plugin/auth-loader.d.ts.map +1 -0
  59. package/dist/src/plugin/auth-loader.js +225 -0
  60. package/dist/src/plugin/auth-loader.js.map +1 -0
  61. package/dist/src/plugin/auth.d.ts +1 -1
  62. package/dist/src/plugin/auth.js +1 -1
  63. package/dist/src/plugin/background-quota-refresh.d.ts +146 -0
  64. package/dist/src/plugin/background-quota-refresh.d.ts.map +1 -0
  65. package/dist/src/plugin/background-quota-refresh.js +347 -0
  66. package/dist/src/plugin/background-quota-refresh.js.map +1 -0
  67. package/dist/src/plugin/cache/index.d.ts +1 -1
  68. package/dist/src/plugin/cache/index.d.ts.map +1 -1
  69. package/dist/src/plugin/cache/index.js +1 -1
  70. package/dist/src/plugin/cache/index.js.map +1 -1
  71. package/dist/src/plugin/cache/signature-cache.d.ts +1 -1
  72. package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -1
  73. package/dist/src/plugin/cache/signature-cache.js +23 -24
  74. package/dist/src/plugin/cache/signature-cache.js.map +1 -1
  75. package/dist/src/plugin/cache.d.ts +6 -5
  76. package/dist/src/plugin/cache.d.ts.map +1 -1
  77. package/dist/src/plugin/cache.js +21 -7
  78. package/dist/src/plugin/cache.js.map +1 -1
  79. package/dist/src/plugin/catalog.d.ts +23 -0
  80. package/dist/src/plugin/catalog.d.ts.map +1 -0
  81. package/dist/src/plugin/catalog.js +60 -0
  82. package/dist/src/plugin/catalog.js.map +1 -0
  83. package/dist/src/plugin/cli.d.ts +7 -7
  84. package/dist/src/plugin/cli.d.ts.map +1 -1
  85. package/dist/src/plugin/cli.js +62 -55
  86. package/dist/src/plugin/cli.js.map +1 -1
  87. package/dist/src/plugin/command-data.d.ts +329 -0
  88. package/dist/src/plugin/command-data.d.ts.map +1 -0
  89. package/dist/src/plugin/command-data.js +517 -0
  90. package/dist/src/plugin/command-data.js.map +1 -0
  91. package/dist/src/plugin/commands.d.ts +154 -0
  92. package/dist/src/plugin/commands.d.ts.map +1 -0
  93. package/dist/src/plugin/commands.js +746 -0
  94. package/dist/src/plugin/commands.js.map +1 -0
  95. package/dist/src/plugin/config/index.d.ts +2 -2
  96. package/dist/src/plugin/config/index.d.ts.map +1 -1
  97. package/dist/src/plugin/config/index.js +2 -2
  98. package/dist/src/plugin/config/index.js.map +1 -1
  99. package/dist/src/plugin/config/loader.d.ts +1 -1
  100. package/dist/src/plugin/config/loader.d.ts.map +1 -1
  101. package/dist/src/plugin/config/loader.js +18 -16
  102. package/dist/src/plugin/config/loader.js.map +1 -1
  103. package/dist/src/plugin/config/models.d.ts +2 -2
  104. package/dist/src/plugin/config/models.d.ts.map +1 -1
  105. package/dist/src/plugin/config/models.js +1 -1
  106. package/dist/src/plugin/config/models.js.map +1 -1
  107. package/dist/src/plugin/config/operator-settings-schema.d.ts +29 -0
  108. package/dist/src/plugin/config/operator-settings-schema.d.ts.map +1 -0
  109. package/dist/src/plugin/config/operator-settings-schema.js +28 -0
  110. package/dist/src/plugin/config/operator-settings-schema.js.map +1 -0
  111. package/dist/src/plugin/config/schema.d.ts +23 -2
  112. package/dist/src/plugin/config/schema.d.ts.map +1 -1
  113. package/dist/src/plugin/config/schema.js +109 -25
  114. package/dist/src/plugin/config/schema.js.map +1 -1
  115. package/dist/src/plugin/config/updater.d.ts.map +1 -1
  116. package/dist/src/plugin/config/updater.js +15 -15
  117. package/dist/src/plugin/config/updater.js.map +1 -1
  118. package/dist/src/plugin/config/writer.d.ts +34 -0
  119. package/dist/src/plugin/config/writer.d.ts.map +1 -0
  120. package/dist/src/plugin/config/writer.js +81 -0
  121. package/dist/src/plugin/config/writer.js.map +1 -0
  122. package/dist/src/plugin/core/streaming/index.d.ts +1 -1
  123. package/dist/src/plugin/core/streaming/index.d.ts.map +1 -1
  124. package/dist/src/plugin/core/streaming/index.js +1 -1
  125. package/dist/src/plugin/core/streaming/index.js.map +1 -1
  126. package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -1
  127. package/dist/src/plugin/core/streaming/transformer.js +85 -44
  128. package/dist/src/plugin/core/streaming/transformer.js.map +1 -1
  129. package/dist/src/plugin/core/streaming/types.d.ts.map +1 -1
  130. package/dist/src/plugin/debug.d.ts +10 -3
  131. package/dist/src/plugin/debug.d.ts.map +1 -1
  132. package/dist/src/plugin/debug.js +110 -47
  133. package/dist/src/plugin/debug.js.map +1 -1
  134. package/dist/src/plugin/dependencies.d.ts +129 -0
  135. package/dist/src/plugin/dependencies.d.ts.map +1 -0
  136. package/dist/src/plugin/dependencies.js +107 -0
  137. package/dist/src/plugin/dependencies.js.map +1 -0
  138. package/dist/src/plugin/errors.d.ts +27 -0
  139. package/dist/src/plugin/errors.d.ts.map +1 -1
  140. package/dist/src/plugin/errors.js +29 -3
  141. package/dist/src/plugin/errors.js.map +1 -1
  142. package/dist/src/plugin/event-handler.d.ts +53 -0
  143. package/dist/src/plugin/event-handler.d.ts.map +1 -0
  144. package/dist/src/plugin/event-handler.js +101 -0
  145. package/dist/src/plugin/event-handler.js.map +1 -0
  146. package/dist/src/plugin/fetch/retry-state.d.ts +61 -0
  147. package/dist/src/plugin/fetch/retry-state.d.ts.map +1 -0
  148. package/dist/src/plugin/fetch/retry-state.js +248 -0
  149. package/dist/src/plugin/fetch/retry-state.js.map +1 -0
  150. package/dist/src/plugin/fetch/warmup.d.ts +30 -0
  151. package/dist/src/plugin/fetch/warmup.d.ts.map +1 -0
  152. package/dist/src/plugin/fetch/warmup.js +76 -0
  153. package/dist/src/plugin/fetch/warmup.js.map +1 -0
  154. package/dist/src/plugin/fetch-interceptor.d.ts +59 -0
  155. package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -0
  156. package/dist/src/plugin/fetch-interceptor.js +1399 -0
  157. package/dist/src/plugin/fetch-interceptor.js.map +1 -0
  158. package/dist/src/plugin/fetch-routing.d.ts +26 -0
  159. package/dist/src/plugin/fetch-routing.d.ts.map +1 -0
  160. package/dist/src/plugin/fetch-routing.js +99 -0
  161. package/dist/src/plugin/fetch-routing.js.map +1 -0
  162. package/dist/src/plugin/fingerprint.d.ts +1 -1
  163. package/dist/src/plugin/fingerprint.js +1 -1
  164. package/dist/src/plugin/gemini-dump.d.ts +5 -5
  165. package/dist/src/plugin/gemini-dump.d.ts.map +1 -1
  166. package/dist/src/plugin/gemini-dump.js +109 -55
  167. package/dist/src/plugin/gemini-dump.js.map +1 -1
  168. package/dist/src/plugin/google-search-tool.d.ts +9 -0
  169. package/dist/src/plugin/google-search-tool.d.ts.map +1 -0
  170. package/dist/src/plugin/google-search-tool.js +56 -0
  171. package/dist/src/plugin/google-search-tool.js.map +1 -0
  172. package/dist/src/plugin/image-saver.d.ts.map +1 -1
  173. package/dist/src/plugin/image-saver.js +16 -16
  174. package/dist/src/plugin/image-saver.js.map +1 -1
  175. package/dist/src/plugin/index.d.ts +30 -0
  176. package/dist/src/plugin/index.d.ts.map +1 -0
  177. package/dist/src/plugin/index.js +411 -0
  178. package/dist/src/plugin/index.js.map +1 -0
  179. package/dist/src/plugin/killswitch.d.ts +94 -0
  180. package/dist/src/plugin/killswitch.d.ts.map +1 -0
  181. package/dist/src/plugin/killswitch.js +197 -0
  182. package/dist/src/plugin/killswitch.js.map +1 -0
  183. package/dist/src/plugin/lifecycle.d.ts +40 -0
  184. package/dist/src/plugin/lifecycle.d.ts.map +1 -0
  185. package/dist/src/plugin/lifecycle.js +73 -0
  186. package/dist/src/plugin/lifecycle.js.map +1 -0
  187. package/dist/src/plugin/logger.d.ts +10 -1
  188. package/dist/src/plugin/logger.d.ts.map +1 -1
  189. package/dist/src/plugin/logger.js +38 -12
  190. package/dist/src/plugin/logger.js.map +1 -1
  191. package/dist/src/plugin/logging-utils.d.ts +35 -1
  192. package/dist/src/plugin/logging-utils.d.ts.map +1 -1
  193. package/dist/src/plugin/logging-utils.js +98 -15
  194. package/dist/src/plugin/logging-utils.js.map +1 -1
  195. package/dist/src/plugin/model-registry.d.ts +1 -1
  196. package/dist/src/plugin/model-registry.js +1 -1
  197. package/dist/src/plugin/oauth-login.d.ts +22 -0
  198. package/dist/src/plugin/oauth-login.d.ts.map +1 -0
  199. package/dist/src/plugin/oauth-login.js +37 -0
  200. package/dist/src/plugin/oauth-login.js.map +1 -0
  201. package/dist/src/plugin/oauth-methods.d.ts +42 -0
  202. package/dist/src/plugin/oauth-methods.d.ts.map +1 -0
  203. package/dist/src/plugin/oauth-methods.js +1276 -0
  204. package/dist/src/plugin/oauth-methods.js.map +1 -0
  205. package/dist/src/plugin/operator-settings.d.ts +40 -0
  206. package/dist/src/plugin/operator-settings.d.ts.map +1 -0
  207. package/dist/src/plugin/operator-settings.js +148 -0
  208. package/dist/src/plugin/operator-settings.js.map +1 -0
  209. package/dist/src/plugin/persist-account-pool.d.ts +37 -0
  210. package/dist/src/plugin/persist-account-pool.d.ts.map +1 -0
  211. package/dist/src/plugin/persist-account-pool.js +142 -0
  212. package/dist/src/plugin/persist-account-pool.js.map +1 -0
  213. package/dist/src/plugin/project.d.ts +1 -1
  214. package/dist/src/plugin/project.js +1 -1
  215. package/dist/src/plugin/prompt-context.d.ts.map +1 -1
  216. package/dist/src/plugin/prompt-context.js +11 -11
  217. package/dist/src/plugin/prompt-context.js.map +1 -1
  218. package/dist/src/plugin/quota.d.ts +138 -41
  219. package/dist/src/plugin/quota.d.ts.map +1 -1
  220. package/dist/src/plugin/quota.js +417 -285
  221. package/dist/src/plugin/quota.js.map +1 -1
  222. package/dist/src/plugin/recovery/constants.d.ts.map +1 -1
  223. package/dist/src/plugin/recovery/constants.js +24 -15
  224. package/dist/src/plugin/recovery/constants.js.map +1 -1
  225. package/dist/src/plugin/recovery/index.d.ts +3 -3
  226. package/dist/src/plugin/recovery/index.d.ts.map +1 -1
  227. package/dist/src/plugin/recovery/index.js +3 -3
  228. package/dist/src/plugin/recovery/index.js.map +1 -1
  229. package/dist/src/plugin/recovery/storage.d.ts +1 -1
  230. package/dist/src/plugin/recovery/storage.d.ts.map +1 -1
  231. package/dist/src/plugin/recovery/storage.js +31 -39
  232. package/dist/src/plugin/recovery/storage.js.map +1 -1
  233. package/dist/src/plugin/recovery/types.d.ts +12 -12
  234. package/dist/src/plugin/recovery/types.d.ts.map +1 -1
  235. package/dist/src/plugin/recovery.d.ts +3 -3
  236. package/dist/src/plugin/recovery.d.ts.map +1 -1
  237. package/dist/src/plugin/recovery.js +62 -58
  238. package/dist/src/plugin/recovery.js.map +1 -1
  239. package/dist/src/plugin/refresh-queue.d.ts +5 -2
  240. package/dist/src/plugin/refresh-queue.d.ts.map +1 -1
  241. package/dist/src/plugin/refresh-queue.js +37 -15
  242. package/dist/src/plugin/refresh-queue.js.map +1 -1
  243. package/dist/src/plugin/request-helpers.d.ts +1 -1
  244. package/dist/src/plugin/request-helpers.d.ts.map +1 -1
  245. package/dist/src/plugin/request-helpers.js +451 -352
  246. package/dist/src/plugin/request-helpers.js.map +1 -1
  247. package/dist/src/plugin/request.d.ts +7 -7
  248. package/dist/src/plugin/request.d.ts.map +1 -1
  249. package/dist/src/plugin/request.js +622 -384
  250. package/dist/src/plugin/request.js.map +1 -1
  251. package/dist/src/plugin/rotation.d.ts +1 -168
  252. package/dist/src/plugin/rotation.d.ts.map +1 -1
  253. package/dist/src/plugin/rotation.js +1 -327
  254. package/dist/src/plugin/rotation.js.map +1 -1
  255. package/dist/src/plugin/search.d.ts.map +1 -1
  256. package/dist/src/plugin/search.js +39 -35
  257. package/dist/src/plugin/search.js.map +1 -1
  258. package/dist/src/plugin/server.d.ts +1 -1
  259. package/dist/src/plugin/server.d.ts.map +1 -1
  260. package/dist/src/plugin/server.js +42 -37
  261. package/dist/src/plugin/server.js.map +1 -1
  262. package/dist/src/plugin/session-context.d.ts +2 -1
  263. package/dist/src/plugin/session-context.d.ts.map +1 -1
  264. package/dist/src/plugin/session-context.js +11 -7
  265. package/dist/src/plugin/session-context.js.map +1 -1
  266. package/dist/src/plugin/storage.d.ts +59 -120
  267. package/dist/src/plugin/storage.d.ts.map +1 -1
  268. package/dist/src/plugin/storage.js +140 -485
  269. package/dist/src/plugin/storage.js.map +1 -1
  270. package/dist/src/plugin/stores/signature-store.d.ts.map +1 -1
  271. package/dist/src/plugin/stores/signature-store.js.map +1 -1
  272. package/dist/src/plugin/thinking-recovery.d.ts.map +1 -1
  273. package/dist/src/plugin/thinking-recovery.js +35 -33
  274. package/dist/src/plugin/thinking-recovery.js.map +1 -1
  275. package/dist/src/plugin/token.d.ts +2 -2
  276. package/dist/src/plugin/token.d.ts.map +1 -1
  277. package/dist/src/plugin/token.js +30 -21
  278. package/dist/src/plugin/token.js.map +1 -1
  279. package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +1 -1
  280. package/dist/src/plugin/transform/cross-model-sanitizer.js +1 -1
  281. package/dist/src/plugin/transform/index.d.ts +1 -1
  282. package/dist/src/plugin/transform/index.js +1 -1
  283. package/dist/src/plugin/transform/model-resolver.d.ts +1 -1
  284. package/dist/src/plugin/transform/model-resolver.js +1 -1
  285. package/dist/src/plugin/transform/types.d.ts +1 -1
  286. package/dist/src/plugin/transform/types.js +1 -1
  287. package/dist/src/plugin/types.d.ts +25 -69
  288. package/dist/src/plugin/types.d.ts.map +1 -1
  289. package/dist/src/plugin/ui/ansi.d.ts.map +1 -1
  290. package/dist/src/plugin/ui/ansi.js.map +1 -1
  291. package/dist/src/plugin/ui/auth-menu.d.ts +1 -1
  292. package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
  293. package/dist/src/plugin/ui/auth-menu.js +84 -35
  294. package/dist/src/plugin/ui/auth-menu.js.map +1 -1
  295. package/dist/src/plugin/ui/confirm.d.ts.map +1 -1
  296. package/dist/src/plugin/ui/confirm.js.map +1 -1
  297. package/dist/src/plugin/ui/model-status.d.ts +3 -3
  298. package/dist/src/plugin/ui/model-status.d.ts.map +1 -1
  299. package/dist/src/plugin/ui/model-status.js +3 -3
  300. package/dist/src/plugin/ui/model-status.js.map +1 -1
  301. package/dist/src/plugin/ui/quota-status.d.ts +4 -4
  302. package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
  303. package/dist/src/plugin/ui/quota-status.js +49 -49
  304. package/dist/src/plugin/ui/quota-status.js.map +1 -1
  305. package/dist/src/plugin/ui/select.d.ts.map +1 -1
  306. package/dist/src/plugin/ui/select.js +22 -12
  307. package/dist/src/plugin/ui/select.js.map +1 -1
  308. package/dist/src/plugin/version.d.ts +1 -1
  309. package/dist/src/plugin/version.js +1 -1
  310. package/dist/src/rpc/notifications.d.ts +6 -0
  311. package/dist/src/rpc/notifications.d.ts.map +1 -0
  312. package/dist/src/rpc/notifications.js +46 -0
  313. package/dist/src/rpc/notifications.js.map +1 -0
  314. package/dist/src/rpc/port-file.d.ts +13 -0
  315. package/dist/src/rpc/port-file.d.ts.map +1 -0
  316. package/dist/src/rpc/port-file.js +109 -0
  317. package/dist/src/rpc/port-file.js.map +1 -0
  318. package/dist/src/rpc/protocol.d.ts +22 -0
  319. package/dist/src/rpc/protocol.d.ts.map +1 -0
  320. package/dist/src/rpc/protocol.js +1 -0
  321. package/dist/src/rpc/protocol.js.map +1 -0
  322. package/dist/src/rpc/rpc-client.d.ts +10 -0
  323. package/dist/src/rpc/rpc-client.d.ts.map +1 -0
  324. package/dist/src/rpc/rpc-client.js +58 -0
  325. package/dist/src/rpc/rpc-client.js.map +1 -0
  326. package/dist/src/rpc/rpc-dir.d.ts +4 -0
  327. package/dist/src/rpc/rpc-dir.d.ts.map +1 -0
  328. package/dist/src/rpc/rpc-dir.js +19 -0
  329. package/dist/src/rpc/rpc-dir.js.map +1 -0
  330. package/dist/src/rpc/rpc-server.d.ts +13 -0
  331. package/dist/src/rpc/rpc-server.d.ts.map +1 -0
  332. package/dist/src/rpc/rpc-server.js +231 -0
  333. package/dist/src/rpc/rpc-server.js.map +1 -0
  334. package/dist/src/sidebar-state.d.ts +357 -0
  335. package/dist/src/sidebar-state.d.ts.map +1 -0
  336. package/dist/src/sidebar-state.js +680 -0
  337. package/dist/src/sidebar-state.js.map +1 -0
  338. package/dist/src/tui/command-dialogs.d.ts +61 -0
  339. package/dist/src/tui/command-dialogs.d.ts.map +1 -0
  340. package/dist/src/tui/command-dialogs.js +595 -0
  341. package/dist/src/tui/command-dialogs.js.map +1 -0
  342. package/dist/src/tui/file-logger.d.ts +41 -0
  343. package/dist/src/tui/file-logger.d.ts.map +1 -0
  344. package/dist/src/tui/file-logger.js +114 -0
  345. package/dist/src/tui/file-logger.js.map +1 -0
  346. package/dist/src/tui-preferences.d.ts +40 -0
  347. package/dist/src/tui-preferences.d.ts.map +1 -0
  348. package/dist/src/tui-preferences.js +328 -0
  349. package/dist/src/tui-preferences.js.map +1 -0
  350. package/dist/src/tui.d.ts +68 -0
  351. package/dist/src/tui.d.ts.map +1 -0
  352. package/dist/src/tui.js +697 -0
  353. package/dist/src/tui.js.map +1 -0
  354. package/package.json +46 -22
  355. package/src/plugin/command-data.ts +931 -0
  356. package/src/rpc/port-file.ts +143 -0
  357. package/src/rpc/protocol.ts +31 -0
  358. package/src/rpc/rpc-client.ts +102 -0
  359. package/src/rpc/rpc-dir.ts +24 -0
  360. package/src/sidebar-state.ts +991 -0
  361. package/src/tui/command-dialogs.tsx +832 -0
  362. package/src/tui/entry.mjs +74 -0
  363. package/src/tui/file-logger.ts +145 -0
  364. package/src/tui-compiled/plugin/command-data.ts +931 -0
  365. package/src/tui-compiled/rpc/port-file.ts +143 -0
  366. package/src/tui-compiled/rpc/protocol.ts +31 -0
  367. package/src/tui-compiled/rpc/rpc-client.ts +102 -0
  368. package/src/tui-compiled/rpc/rpc-dir.ts +24 -0
  369. package/src/tui-compiled/sidebar-state.ts +991 -0
  370. package/src/tui-compiled/tui/command-dialogs.tsx +779 -0
  371. package/src/tui-compiled/tui/file-logger.ts +145 -0
  372. package/src/tui-compiled/tui-preferences.ts +416 -0
  373. package/src/tui-compiled/tui.tsx +1193 -0
  374. package/src/tui-preferences.ts +416 -0
  375. package/src/tui.tsx +1171 -0
  376. package/dist/src/plugin.d.ts +0 -48
  377. package/dist/src/plugin.d.ts.map +0 -1
  378. package/dist/src/plugin.js +0 -3311
  379. 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
+ }