@cortexkit/opencode-antigravity-auth 2.0.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 (73) hide show
  1. package/dist/cli.js +308 -81
  2. package/dist/cli.js.map +4 -4
  3. package/dist/index.js +16634 -15416
  4. package/dist/index.js.map +4 -4
  5. package/dist/src/plugin/account-command-oauth.d.ts +34 -0
  6. package/dist/src/plugin/account-command-oauth.d.ts.map +1 -0
  7. package/dist/src/plugin/account-command-oauth.js +156 -0
  8. package/dist/src/plugin/account-command-oauth.js.map +1 -0
  9. package/dist/src/plugin/auth-loader.d.ts +16 -1
  10. package/dist/src/plugin/auth-loader.d.ts.map +1 -1
  11. package/dist/src/plugin/auth-loader.js +113 -32
  12. package/dist/src/plugin/auth-loader.js.map +1 -1
  13. package/dist/src/plugin/background-quota-refresh.d.ts +146 -0
  14. package/dist/src/plugin/background-quota-refresh.d.ts.map +1 -0
  15. package/dist/src/plugin/background-quota-refresh.js +347 -0
  16. package/dist/src/plugin/background-quota-refresh.js.map +1 -0
  17. package/dist/src/plugin/command-data.d.ts +61 -8
  18. package/dist/src/plugin/command-data.d.ts.map +1 -1
  19. package/dist/src/plugin/command-data.js +231 -39
  20. package/dist/src/plugin/command-data.js.map +1 -1
  21. package/dist/src/plugin/commands.d.ts +12 -9
  22. package/dist/src/plugin/commands.d.ts.map +1 -1
  23. package/dist/src/plugin/commands.js +140 -33
  24. package/dist/src/plugin/commands.js.map +1 -1
  25. package/dist/src/plugin/config/schema.d.ts +2 -0
  26. package/dist/src/plugin/config/schema.d.ts.map +1 -1
  27. package/dist/src/plugin/config/schema.js +24 -0
  28. package/dist/src/plugin/config/schema.js.map +1 -1
  29. package/dist/src/plugin/debug.d.ts +7 -0
  30. package/dist/src/plugin/debug.d.ts.map +1 -1
  31. package/dist/src/plugin/debug.js +25 -0
  32. package/dist/src/plugin/debug.js.map +1 -1
  33. package/dist/src/plugin/fetch-interceptor.d.ts.map +1 -1
  34. package/dist/src/plugin/fetch-interceptor.js +13 -1
  35. package/dist/src/plugin/fetch-interceptor.js.map +1 -1
  36. package/dist/src/plugin/index.d.ts +7 -0
  37. package/dist/src/plugin/index.d.ts.map +1 -1
  38. package/dist/src/plugin/index.js +127 -24
  39. package/dist/src/plugin/index.js.map +1 -1
  40. package/dist/src/plugin/killswitch.d.ts.map +1 -1
  41. package/dist/src/plugin/killswitch.js +12 -9
  42. package/dist/src/plugin/killswitch.js.map +1 -1
  43. package/dist/src/plugin/oauth-methods.d.ts.map +1 -1
  44. package/dist/src/plugin/oauth-methods.js +2 -10
  45. package/dist/src/plugin/oauth-methods.js.map +1 -1
  46. package/dist/src/plugin/quota.d.ts +42 -1
  47. package/dist/src/plugin/quota.d.ts.map +1 -1
  48. package/dist/src/plugin/quota.js +185 -41
  49. package/dist/src/plugin/quota.js.map +1 -1
  50. package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -1
  51. package/dist/src/plugin/ui/auth-menu.js +2 -4
  52. package/dist/src/plugin/ui/auth-menu.js.map +1 -1
  53. package/dist/src/plugin/ui/quota-status.d.ts.map +1 -1
  54. package/dist/src/plugin/ui/quota-status.js +3 -5
  55. package/dist/src/plugin/ui/quota-status.js.map +1 -1
  56. package/dist/src/sidebar-state.d.ts +126 -9
  57. package/dist/src/sidebar-state.d.ts.map +1 -1
  58. package/dist/src/sidebar-state.js +185 -25
  59. package/dist/src/sidebar-state.js.map +1 -1
  60. package/dist/src/tui/command-dialogs.js +101 -3
  61. package/dist/src/tui/command-dialogs.js.map +1 -1
  62. package/dist/src/tui.d.ts.map +1 -1
  63. package/dist/src/tui.js +55 -20
  64. package/dist/src/tui.js.map +1 -1
  65. package/package.json +6 -6
  66. package/src/plugin/command-data.ts +331 -51
  67. package/src/sidebar-state.ts +306 -32
  68. package/src/tui/command-dialogs.tsx +136 -3
  69. package/src/tui-compiled/plugin/command-data.ts +331 -51
  70. package/src/tui-compiled/sidebar-state.ts +306 -32
  71. package/src/tui-compiled/tui/command-dialogs.tsx +189 -69
  72. package/src/tui-compiled/tui.tsx +73 -30
  73. package/src/tui.tsx +82 -28
@@ -1,8 +1,8 @@
1
- import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
2
- import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
3
1
  import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
4
2
  import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
5
3
  import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
4
+ import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
5
+ import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
6
6
  import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
7
7
  import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
8
8
  /** @jsxImportSource @opentui/solid */
@@ -228,27 +228,153 @@ function renderAccountDialog(api, initialPayload, apply) {
228
228
  renderMain();
229
229
  return null;
230
230
  };
231
+ const updateAccounts = knobs => {
232
+ const accounts = knobs.accounts;
233
+ if (!Array.isArray(accounts)) return;
234
+ payload.knobs = {
235
+ ...payload.knobs,
236
+ accounts: accounts
237
+ };
238
+ };
239
+ const openOAuthLabelPrompt = (code, oauthUrl) => {
240
+ const DialogPrompt = api.ui.DialogPrompt;
241
+ api.ui.dialog.setSize('xlarge');
242
+ api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
243
+ title: "OAuth sign-in \u2014 label",
244
+ description: () => (() => {
245
+ var _el$ = _$createElement("text");
246
+ _$insertNode(_el$, _$createTextNode(`A short name for this account (optional).`));
247
+ return _el$;
248
+ })(),
249
+ placeholder: "e.g. work",
250
+ value: "",
251
+ onConfirm: value => {
252
+ const label = value.trim();
253
+ const args = label ? `add-oauth-finish ${code} --label ${label}` : `add-oauth-finish ${code}`;
254
+ void apply('antigravity-account', args, {
255
+ timeoutMs: 120_000
256
+ }).then(result => {
257
+ api.ui.toast({
258
+ message: result.text
259
+ });
260
+ updateAccounts(result.knobs);
261
+ renderMain();
262
+ }).catch(() => {
263
+ api.ui.toast({
264
+ message: 'OAuth account add failed'
265
+ });
266
+ renderMain();
267
+ });
268
+ },
269
+ onCancel: () => openOAuthCodePrompt(oauthUrl)
270
+ }));
271
+ };
272
+ const openOAuthCodePrompt = oauthUrl => {
273
+ const DialogPrompt = api.ui.DialogPrompt;
274
+ api.ui.dialog.setSize('xlarge');
275
+ api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
276
+ title: "OAuth sign-in \u2014 enter code",
277
+ description: () => (() => {
278
+ var _el$3 = _$createElement("text");
279
+ _$insertNode(_el$3, _$createTextNode(`After signing in, paste the full callback URL or authorization code.`));
280
+ return _el$3;
281
+ })(),
282
+ placeholder: "Paste callback URL or code here",
283
+ value: "",
284
+ onConfirm: value => {
285
+ const code = value.trim();
286
+ if (!code) {
287
+ // Empty submit must keep the user inside the flow, not
288
+ // silently drop them back to the account list. Surface the
289
+ // validation message and reopen the same prompt so the
290
+ // user can paste again.
291
+ api.ui.toast({
292
+ message: 'Please paste the callback URL or authorization code.'
293
+ });
294
+ openOAuthCodePrompt(oauthUrl);
295
+ return;
296
+ }
297
+ openOAuthLabelPrompt(code, oauthUrl);
298
+ },
299
+ onCancel: () => openOAuthUrlScreen(oauthUrl)
300
+ }));
301
+ };
302
+ const openOAuthUrlScreen = oauthUrl => {
303
+ const DialogSelect = api.ui.DialogSelect;
304
+ api.ui.dialog.setSize('xlarge');
305
+ api.ui.dialog.replace(() => _$createComponent(DialogSelect, {
306
+ title: "OAuth sign-in",
307
+ options: [{
308
+ title: 'Copy URL to clipboard',
309
+ value: 'copy',
310
+ description: oauthUrl
311
+ }, {
312
+ title: 'Enter sign-in code',
313
+ value: 'code',
314
+ description: 'Open the URL in your browser, sign in, then paste the callback URL or code.'
315
+ }, {
316
+ title: 'Cancel',
317
+ value: 'cancel'
318
+ }],
319
+ onSelect: option => {
320
+ if (option.value === 'cancel') {
321
+ renderMain();
322
+ return;
323
+ }
324
+ if (option.value === 'copy') {
325
+ const copied = api.renderer.copyToClipboardOSC52(oauthUrl);
326
+ api.ui.toast({
327
+ message: copied ? 'URL copied to clipboard' : 'Copy unavailable — select the URL text above to copy'
328
+ });
329
+ openOAuthUrlScreen(oauthUrl);
330
+ return;
331
+ }
332
+ openOAuthCodePrompt(oauthUrl);
333
+ }
334
+ }));
335
+ };
336
+ const openAddOAuthStart = () => {
337
+ void apply('antigravity-account', 'add-oauth-start', {
338
+ timeoutMs: 120_000
339
+ }).then(result => {
340
+ updateAccounts(result.knobs);
341
+ const oauthUrl = result.knobs.oauthUrl;
342
+ if (typeof oauthUrl === 'string' && oauthUrl.length > 0) {
343
+ openOAuthUrlScreen(oauthUrl);
344
+ return;
345
+ }
346
+ api.ui.toast({
347
+ message: result.text
348
+ });
349
+ renderMain();
350
+ }).catch(() => {
351
+ api.ui.toast({
352
+ message: 'OAuth account add failed'
353
+ });
354
+ renderMain();
355
+ });
356
+ };
231
357
  const renderManageRow = row => {
232
358
  const DialogSelect = api.ui.DialogSelect;
233
359
  api.ui.dialog.setSize('xlarge');
234
360
  api.ui.dialog.replace(() => (() => {
235
- var _el$ = _$createElement("box"),
236
- _el$2 = _$createElement("text"),
237
- _el$3 = _$createElement("text"),
238
- _el$4 = _$createElement("box");
239
- _$insertNode(_el$, _el$2);
240
- _$insertNode(_el$, _el$3);
241
- _$insertNode(_el$, _el$4);
242
- _$setProp(_el$, "flexDirection", 'column');
243
- _$setProp(_el$, "padding", 1);
244
- _$setProp(_el$, "width", '100%');
245
- _$insert(_el$2, () => row.label);
246
- _$insert(_el$3, (() => {
361
+ var _el$5 = _$createElement("box"),
362
+ _el$6 = _$createElement("text"),
363
+ _el$7 = _$createElement("text"),
364
+ _el$8 = _$createElement("box");
365
+ _$insertNode(_el$5, _el$6);
366
+ _$insertNode(_el$5, _el$7);
367
+ _$insertNode(_el$5, _el$8);
368
+ _$setProp(_el$5, "flexDirection", 'column');
369
+ _$setProp(_el$5, "padding", 1);
370
+ _$setProp(_el$5, "width", '100%');
371
+ _$insert(_el$6, () => row.label);
372
+ _$insert(_el$7, (() => {
247
373
  var _c$ = _$memo(() => row.quota.length === 0);
248
374
  return () => _c$() ? 'no cached quota' : row.quota.map(q => q.remainingPercent == null ? `${q.label} –%` : `${q.label} ${q.remainingPercent}%`).join(', ');
249
375
  })());
250
- _$setProp(_el$4, "marginTop", 1);
251
- _$insert(_el$4, _$createComponent(DialogSelect, {
376
+ _$setProp(_el$8, "marginTop", 1);
377
+ _$insert(_el$8, _$createComponent(DialogSelect, {
252
378
  get title() {
253
379
  return `Manage ${row.label}`;
254
380
  },
@@ -297,7 +423,7 @@ function renderAccountDialog(api, initialPayload, apply) {
297
423
  });
298
424
  }
299
425
  }));
300
- return _el$;
426
+ return _el$5;
301
427
  })());
302
428
  };
303
429
  const promptRemove = index => {
@@ -335,19 +461,19 @@ function renderAccountDialog(api, initialPayload, apply) {
335
461
  const rows = rowsFromKnobs();
336
462
  const bodyLines = rows.length ? rows.map(formatQuotaRowLine) : ['No accounts configured. Add one via the menu below.'];
337
463
  api.ui.dialog.replace(() => (() => {
338
- var _el$5 = _$createElement("box"),
339
- _el$6 = _$createElement("box");
340
- _$insertNode(_el$5, _el$6);
341
- _$setProp(_el$5, "flexDirection", 'column');
342
- _$setProp(_el$5, "padding", 1);
343
- _$setProp(_el$5, "width", '100%');
344
- _$insert(_el$5, () => bodyLines.map(line => (() => {
345
- var _el$7 = _$createElement("text");
346
- _$insert(_el$7, line);
347
- return _el$7;
348
- })()), _el$6);
349
- _$setProp(_el$6, "marginTop", 1);
350
- _$insert(_el$6, _$createComponent(DialogSelect, {
464
+ var _el$9 = _$createElement("box"),
465
+ _el$0 = _$createElement("box");
466
+ _$insertNode(_el$9, _el$0);
467
+ _$setProp(_el$9, "flexDirection", 'column');
468
+ _$setProp(_el$9, "padding", 1);
469
+ _$setProp(_el$9, "width", '100%');
470
+ _$insert(_el$9, () => bodyLines.map(line => (() => {
471
+ var _el$1 = _$createElement("text");
472
+ _$insert(_el$1, line);
473
+ return _el$1;
474
+ })()), _el$0);
475
+ _$setProp(_el$0, "marginTop", 1);
476
+ _$insert(_el$0, _$createComponent(DialogSelect, {
351
477
  title: "Antigravity accounts",
352
478
  get options() {
353
479
  return [{
@@ -370,13 +496,7 @@ function renderAccountDialog(api, initialPayload, apply) {
370
496
  return;
371
497
  }
372
498
  if (raw === 'add') {
373
- void runApply('add', {
374
- timeoutMs: 120_000
375
- }).catch(() => {
376
- api.ui.toast({
377
- message: 'Account add failed'
378
- });
379
- });
499
+ openAddOAuthStart();
380
500
  return;
381
501
  }
382
502
  if (raw.startsWith('__manage__ ')) {
@@ -390,7 +510,7 @@ function renderAccountDialog(api, initialPayload, apply) {
390
510
  });
391
511
  }
392
512
  }));
393
- return _el$5;
513
+ return _el$9;
394
514
  })());
395
515
  };
396
516
  renderMain();
@@ -452,19 +572,19 @@ function renderRoutingDialog(api, initialPayload, apply) {
452
572
  } = readRouting();
453
573
  const bodyLines = [`cli_first: ${cliFirst ? 'on' : 'off'}`, `quota_style_fallback: ${quotaFallback ? 'on' : 'off'}`];
454
574
  api.ui.dialog.replace(() => (() => {
455
- var _el$8 = _$createElement("box"),
456
- _el$9 = _$createElement("box");
457
- _$insertNode(_el$8, _el$9);
458
- _$setProp(_el$8, "flexDirection", 'column');
459
- _$setProp(_el$8, "padding", 1);
460
- _$setProp(_el$8, "width", '100%');
461
- _$insert(_el$8, () => bodyLines.map(line => (() => {
462
- var _el$0 = _$createElement("text");
463
- _$insert(_el$0, line);
464
- return _el$0;
465
- })()), _el$9);
466
- _$setProp(_el$9, "marginTop", 1);
467
- _$insert(_el$9, _$createComponent(DialogSelect, {
575
+ var _el$10 = _$createElement("box"),
576
+ _el$11 = _$createElement("box");
577
+ _$insertNode(_el$10, _el$11);
578
+ _$setProp(_el$10, "flexDirection", 'column');
579
+ _$setProp(_el$10, "padding", 1);
580
+ _$setProp(_el$10, "width", '100%');
581
+ _$insert(_el$10, () => bodyLines.map(line => (() => {
582
+ var _el$12 = _$createElement("text");
583
+ _$insert(_el$12, line);
584
+ return _el$12;
585
+ })()), _el$11);
586
+ _$setProp(_el$11, "marginTop", 1);
587
+ _$insert(_el$11, _$createComponent(DialogSelect, {
468
588
  title: "Antigravity routing",
469
589
  options: [{
470
590
  title: `${cliFirst ? '●' : '○'} cli_first: ${cliFirst}`,
@@ -498,7 +618,7 @@ function renderRoutingDialog(api, initialPayload, apply) {
498
618
  });
499
619
  }
500
620
  }));
501
- return _el$8;
621
+ return _el$10;
502
622
  })());
503
623
  };
504
624
  renderMain();
@@ -559,9 +679,9 @@ function renderKillswitchDialog(api, initialPayload, apply) {
559
679
  api.ui.dialog.replace(() => _$createComponent(DialogPrompt, {
560
680
  title: "Antigravity killswitch \u2014 set threshold",
561
681
  description: () => (() => {
562
- var _el$1 = _$createElement("text");
563
- _$insertNode(_el$1, _$createTextNode(`Enter a new minimum_remaining_percent (0-100). Empty input cancels.`));
564
- return _el$1;
682
+ var _el$13 = _$createElement("text");
683
+ _$insertNode(_el$13, _$createTextNode(`Enter a new minimum_remaining_percent (0-100). Empty input cancels.`));
684
+ return _el$13;
565
685
  })(),
566
686
  placeholder: `${currentMinimum}`,
567
687
  value: `${currentMinimum}`,
@@ -602,19 +722,19 @@ function renderKillswitchDialog(api, initialPayload, apply) {
602
722
  const accountKeys = Object.keys(accounts);
603
723
  const bodyLines = [`Killswitch: ${enabled ? 'enabled' : 'disabled'}`, `Minimum remaining percent: ${minimum}%`, ...(accountKeys.length > 0 ? ['Per-account overrides:', ...accountKeys.map(key => ` ${key}: ${accounts[key] ?? 0}%`)] : [])];
604
724
  api.ui.dialog.replace(() => (() => {
605
- var _el$11 = _$createElement("box"),
606
- _el$12 = _$createElement("box");
607
- _$insertNode(_el$11, _el$12);
608
- _$setProp(_el$11, "flexDirection", 'column');
609
- _$setProp(_el$11, "padding", 1);
610
- _$setProp(_el$11, "width", '100%');
611
- _$insert(_el$11, () => bodyLines.map(line => (() => {
612
- var _el$13 = _$createElement("text");
613
- _$insert(_el$13, line);
614
- return _el$13;
615
- })()), _el$12);
616
- _$setProp(_el$12, "marginTop", 1);
617
- _$insert(_el$12, _$createComponent(DialogSelect, {
725
+ var _el$15 = _$createElement("box"),
726
+ _el$16 = _$createElement("box");
727
+ _$insertNode(_el$15, _el$16);
728
+ _$setProp(_el$15, "flexDirection", 'column');
729
+ _$setProp(_el$15, "padding", 1);
730
+ _$setProp(_el$15, "width", '100%');
731
+ _$insert(_el$15, () => bodyLines.map(line => (() => {
732
+ var _el$17 = _$createElement("text");
733
+ _$insert(_el$17, line);
734
+ return _el$17;
735
+ })()), _el$16);
736
+ _$setProp(_el$16, "marginTop", 1);
737
+ _$insert(_el$16, _$createComponent(DialogSelect, {
618
738
  title: "Antigravity killswitch",
619
739
  options: [{
620
740
  title: `${enabled ? '●' : '○'} Killswitch: ${enabled ? 'enabled' : 'disabled'}`,
@@ -652,7 +772,7 @@ function renderKillswitchDialog(api, initialPayload, apply) {
652
772
  });
653
773
  }
654
774
  }));
655
- return _el$11;
775
+ return _el$15;
656
776
  })());
657
777
  };
658
778
  renderMain();
@@ -17,9 +17,9 @@ import { createElement as _$createElement } from "opentui:runtime-module:%40open
17
17
  * `AccountBlock` per visible account, a Routing section that surfaces the
18
18
  * most recent session route, and a Health section that only appears when
19
19
  * something is wrong. The Antigravity data model is richer than the
20
- * Claude/Codex one (per-account Claude + Gemini Pro + Gemini Flash quota
21
- * groups, a health score, and per-session routing decisions), so the
22
- * fleet components are adapted rather than copied verbatim:
20
+ * Claude/Codex one (per-account Gemini + Non-Gemini quota pools, a health
21
+ * score, and per-session routing decisions), so the fleet components are
22
+ * adapted rather than copied verbatim:
23
23
  *
24
24
  * - `QuotaRow` reads `remainingPercent + resetAt` from the Antigravity
25
25
  * `SidebarQuotaEntry` shape and colors via `quotaTone` (remaining
@@ -99,12 +99,14 @@ let rpcInFlight = false;
99
99
  const GLOBAL_NOTIFICATION_CURSOR = '__global__';
100
100
  const MAX_NOTIFICATION_CURSORS = 256;
101
101
  const QUOTA_LABELS = {
102
- claude: 'Cl',
103
- 'gemini-pro': 'GP',
104
- 'gemini-flash': 'GF',
105
- 'gpt-oss': 'GPT'
102
+ gemini: 'Gm',
103
+ 'non-gemini': 'NG'
104
+ };
105
+ const QUOTA_ORDER = ['gemini', 'non-gemini'];
106
+ const WINDOW_GUTTER = {
107
+ weekly: '7d',
108
+ '5h': '5h'
106
109
  };
107
- const QUOTA_ORDER = ['claude', 'gemini-pro', 'gemini-flash', 'gpt-oss'];
108
110
 
109
111
  // --- Theme tokens ----------------------------------------------------------
110
112
  //
@@ -440,7 +442,7 @@ function QuotaRow(props) {
440
442
  _$setProp(_el$14, "width", '100%');
441
443
  _$setProp(_el$14, "flexDirection", 'row');
442
444
  _$setProp(_el$14, "justifyContent", 'space-between');
443
- _$insert(_el$15, () => props.label.padEnd(3));
445
+ _$insert(_el$15, () => props.label.padEnd(5));
444
446
  _$insertNode(_el$16, _$createTextNode(`—`));
445
447
  _$effect(_p$ => {
446
448
  var _v$6 = props.theme().textMuted,
@@ -469,7 +471,7 @@ function QuotaRow(props) {
469
471
  _$insertNode(_el$1, _el$11);
470
472
  _$insertNode(_el$1, _el$12);
471
473
  _$setProp(_el$1, "flexDirection", 'row');
472
- _$setProp(_el$10, "width", 3);
474
+ _$setProp(_el$10, "width", 5);
473
475
  _$setProp(_el$10, "flexShrink", 0);
474
476
  _$insert(_el$10, () => props.label);
475
477
  _$setProp(_el$11, "flexShrink", 0);
@@ -572,23 +574,49 @@ function AccountBlock(props) {
572
574
  _$insert(_el$24, statusWord);
573
575
  _$insert(_el$19, _$createComponent(For, {
574
576
  each: QUOTA_ORDER,
575
- children: key => _$createComponent(QuotaRow, {
576
- get theme() {
577
- return props.theme;
578
- },
579
- get appearance() {
580
- return props.appearance;
581
- },
582
- get label() {
583
- return QUOTA_LABELS[key];
584
- },
585
- get entry() {
586
- return props.account.quota[key];
587
- },
588
- get now() {
589
- return props.now;
577
+ children: key => {
578
+ const poolEntry = props.account.quota[key];
579
+ const windows = poolEntry?.windows;
580
+ // Legacy: no windows array — render a single row with the pool label.
581
+ const hasWindows = windows && windows.length > 0;
582
+ if (!hasWindows) {
583
+ return _$createComponent(QuotaRow, {
584
+ get theme() {
585
+ return props.theme;
586
+ },
587
+ get appearance() {
588
+ return props.appearance;
589
+ },
590
+ get label() {
591
+ return QUOTA_LABELS[key];
592
+ },
593
+ entry: poolEntry,
594
+ get now() {
595
+ return props.now;
596
+ }
597
+ });
590
598
  }
591
- })
599
+ return _$memo(() => windows.map(w => _$createComponent(QuotaRow, {
600
+ get theme() {
601
+ return props.theme;
602
+ },
603
+ get appearance() {
604
+ return props.appearance;
605
+ },
606
+ get label() {
607
+ return `${QUOTA_LABELS[key]} ${WINDOW_GUTTER[w.window] ?? w.window}`;
608
+ },
609
+ get entry() {
610
+ return {
611
+ remainingPercent: w.remainingPercent,
612
+ resetAt: w.resetAt
613
+ };
614
+ },
615
+ get now() {
616
+ return props.now;
617
+ }
618
+ })));
619
+ }
592
620
  }), _el$25);
593
621
  _$insertNode(_el$25, _el$26);
594
622
  _$setProp(_el$25, "width", '100%');
@@ -837,8 +865,23 @@ export function SidebarPanel(props) {
837
865
  return (() => {
838
866
  const account = () => visibleAccounts().find(entry => entry.current) ?? visibleAccounts()[0];
839
867
  const used = () => {
840
- const entry = account()?.quota.claude;
841
- return entry ? 100 - clamp(entry.remainingPercent, 0, 100) : null;
868
+ const q = account()?.quota;
869
+ // Derive tone from the WORST (most-constrained) pool so a
870
+ // critical non-gemini pool doesn't hide behind a healthy
871
+ // gemini tone. Worst = highest used-percent across available pools.
872
+ const geminiUsed = q?.gemini != null ? 100 - clamp(q.gemini.remainingPercent, 0, 100) : null;
873
+ const nonGeminiUsed = q?.['non-gemini'] != null ? 100 - clamp(q['non-gemini'].remainingPercent, 0, 100) : null;
874
+ if (geminiUsed === null && nonGeminiUsed === null) return null;
875
+ return Math.max(geminiUsed ?? 0, nonGeminiUsed ?? 0);
876
+ };
877
+ const poolText = key => {
878
+ const entry = account()?.quota[key];
879
+ if (!entry) return `${QUOTA_LABELS[key]}: —`;
880
+ const pct = Math.round(100 - clamp(entry.remainingPercent, 0, 100));
881
+ // Show binding window when available, else just pool label.
882
+ const bindingWindow = entry.windows?.reduce((best, w) => best === null || w.remainingPercent < best.remainingPercent ? w : best, null);
883
+ const gutter = bindingWindow ? ` ${WINDOW_GUTTER[bindingWindow.window] ?? bindingWindow.window}` : '';
884
+ return `${QUOTA_LABELS[key]}${gutter}: ${pct}%`;
842
885
  };
843
886
  const unavailable = () => {
844
887
  const selected = account();
@@ -863,8 +906,8 @@ export function SidebarPanel(props) {
863
906
  _$setProp(_el$43, "flexDirection", 'row');
864
907
  _$insertNode(_el$44, _el$45);
865
908
  _$insert(_el$45, (() => {
866
- var _c$ = _$memo(() => used() == null);
867
- return () => _c$() ? '—' : `Cl: ${Math.round(used() ?? 0)}%`;
909
+ var _c$ = _$memo(() => !!(account()?.quota.gemini == null && account()?.quota['non-gemini'] == null));
910
+ return () => _c$() ? '—' : `${poolText('gemini')} · ${poolText('non-gemini')}`;
868
911
  })());
869
912
  _$insert(_el$46, () => unavailable() ? ' ⊘' : ' ●');
870
913
  _$effect(_p$ => {
package/src/tui.tsx CHANGED
@@ -9,9 +9,9 @@
9
9
  * `AccountBlock` per visible account, a Routing section that surfaces the
10
10
  * most recent session route, and a Health section that only appears when
11
11
  * something is wrong. The Antigravity data model is richer than the
12
- * Claude/Codex one (per-account Claude + Gemini Pro + Gemini Flash quota
13
- * groups, a health score, and per-session routing decisions), so the
14
- * fleet components are adapted rather than copied verbatim:
12
+ * Claude/Codex one (per-account Gemini + Non-Gemini quota pools, a health
13
+ * score, and per-session routing decisions), so the fleet components are
14
+ * adapted rather than copied verbatim:
15
15
  *
16
16
  * - `QuotaRow` reads `remainingPercent + resetAt` from the Antigravity
17
17
  * `SidebarQuotaEntry` shape and colors via `quotaTone` (remaining
@@ -128,18 +128,16 @@ const GLOBAL_NOTIFICATION_CURSOR = '__global__'
128
128
  const MAX_NOTIFICATION_CURSORS = 256
129
129
 
130
130
  const QUOTA_LABELS: Record<SidebarQuotaKey, string> = {
131
- claude: 'Cl',
132
- 'gemini-pro': 'GP',
133
- 'gemini-flash': 'GF',
134
- 'gpt-oss': 'GPT',
131
+ gemini: 'Gm',
132
+ 'non-gemini': 'NG',
135
133
  }
136
134
 
137
- const QUOTA_ORDER: readonly SidebarQuotaKey[] = [
138
- 'claude',
139
- 'gemini-pro',
140
- 'gemini-flash',
141
- 'gpt-oss',
142
- ]
135
+ const QUOTA_ORDER: readonly SidebarQuotaKey[] = ['gemini', 'non-gemini']
136
+
137
+ const WINDOW_GUTTER: Record<string, string> = {
138
+ weekly: '7d',
139
+ '5h': '5h',
140
+ }
143
141
 
144
142
  // --- Theme tokens ----------------------------------------------------------
145
143
  //
@@ -517,14 +515,14 @@ function QuotaRow(props: {
517
515
  when={used() != null}
518
516
  fallback={
519
517
  <box width='100%' flexDirection='row' justifyContent='space-between'>
520
- <text fg={props.theme().textMuted}>{props.label.padEnd(3)}</text>
518
+ <text fg={props.theme().textMuted}>{props.label.padEnd(5)}</text>
521
519
  <text fg={props.theme().textMuted}>{'\u2014'}</text>
522
520
  </box>
523
521
  }
524
522
  >
525
523
  <box width='100%' flexDirection='row' justifyContent='space-between'>
526
524
  <box flexDirection='row'>
527
- <text width={3} flexShrink={0} fg={props.theme().textMuted}>
525
+ <text width={5} flexShrink={0} fg={props.theme().textMuted}>
528
526
  {props.label}
529
527
  </text>
530
528
  <box
@@ -610,15 +608,39 @@ function AccountBlock(props: {
610
608
  </text>
611
609
  </box>
612
610
  <For each={QUOTA_ORDER}>
613
- {(key) => (
614
- <QuotaRow
615
- theme={props.theme}
616
- appearance={props.appearance}
617
- label={QUOTA_LABELS[key]}
618
- entry={props.account.quota[key]}
619
- now={props.now}
620
- />
621
- )}
611
+ {(key) => {
612
+ const poolEntry = props.account.quota[key]
613
+ const windows = poolEntry?.windows
614
+ // Legacy: no windows array — render a single row with the pool label.
615
+ const hasWindows = windows && windows.length > 0
616
+ if (!hasWindows) {
617
+ return (
618
+ <QuotaRow
619
+ theme={props.theme}
620
+ appearance={props.appearance}
621
+ label={QUOTA_LABELS[key]}
622
+ entry={poolEntry}
623
+ now={props.now}
624
+ />
625
+ )
626
+ }
627
+ return (
628
+ <>
629
+ {windows.map((w) => (
630
+ <QuotaRow
631
+ theme={props.theme}
632
+ appearance={props.appearance}
633
+ label={`${QUOTA_LABELS[key]} ${WINDOW_GUTTER[w.window] ?? w.window}`}
634
+ entry={{
635
+ remainingPercent: w.remainingPercent,
636
+ resetAt: w.resetAt,
637
+ }}
638
+ now={props.now}
639
+ />
640
+ ))}
641
+ </>
642
+ )
643
+ }}
622
644
  </For>
623
645
  <box width='100%' flexDirection='row'>
624
646
  <text fg={props.theme().textMuted}>{` ${healthText()}`}</text>
@@ -835,8 +857,39 @@ export function SidebarPanel(props: SidebarPanelProps): JSX.Element {
835
857
  visibleAccounts().find((entry) => entry.current) ??
836
858
  visibleAccounts()[0]
837
859
  const used = () => {
838
- const entry = account()?.quota.claude
839
- return entry ? 100 - clamp(entry.remainingPercent, 0, 100) : null
860
+ const q = account()?.quota
861
+ // Derive tone from the WORST (most-constrained) pool so a
862
+ // critical non-gemini pool doesn't hide behind a healthy
863
+ // gemini tone. Worst = highest used-percent across available pools.
864
+ const geminiUsed =
865
+ q?.gemini != null
866
+ ? 100 - clamp(q.gemini.remainingPercent, 0, 100)
867
+ : null
868
+ const nonGeminiUsed =
869
+ q?.['non-gemini'] != null
870
+ ? 100 - clamp(q['non-gemini'].remainingPercent, 0, 100)
871
+ : null
872
+ if (geminiUsed === null && nonGeminiUsed === null) return null
873
+ return Math.max(geminiUsed ?? 0, nonGeminiUsed ?? 0)
874
+ }
875
+ const poolText = (key: 'gemini' | 'non-gemini') => {
876
+ const entry = account()?.quota[key]
877
+ if (!entry) return `${QUOTA_LABELS[key]}: —`
878
+ const pct = Math.round(100 - clamp(entry.remainingPercent, 0, 100))
879
+ // Show binding window when available, else just pool label.
880
+ const bindingWindow = entry.windows?.reduce<
881
+ (typeof entry.windows)[0] | null
882
+ >(
883
+ (best, w) =>
884
+ best === null || w.remainingPercent < best.remainingPercent
885
+ ? w
886
+ : best,
887
+ null,
888
+ )
889
+ const gutter = bindingWindow
890
+ ? ` ${WINDOW_GUTTER[bindingWindow.window] ?? bindingWindow.window}`
891
+ : ''
892
+ return `${QUOTA_LABELS[key]}${gutter}: ${pct}%`
840
893
  }
841
894
  const unavailable = () => {
842
895
  const selected = account()
@@ -861,9 +914,10 @@ export function SidebarPanel(props: SidebarPanelProps): JSX.Element {
861
914
  )}
862
915
  >
863
916
  <b>
864
- {used() == null
917
+ {account()?.quota.gemini == null &&
918
+ account()?.quota['non-gemini'] == null
865
919
  ? '—'
866
- : `Cl: ${Math.round(used() ?? 0)}%`}
920
+ : `${poolText('gemini')} · ${poolText('non-gemini')}`}
867
921
  </b>
868
922
  </text>
869
923
  <text