pi-provider-antigravity 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { createHash, randomBytes } from "node:crypto";
6
6
 
7
7
  // src/protocol.ts
8
8
  var DEFAULT_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
9
- var DEFAULT_USER_AGENT = "antigravity/cli/1.2.3 (aidev_client; os_type=linux; arch=amd64; cl=981443618; auth_method=consumer)";
9
+ var DEFAULT_USER_AGENT = "antigravity/cli/1.2.4 (aidev_client; os_type=linux; arch=amd64; cl=982146307; auth_method=consumer)";
10
10
  var PROVIDER_ID = "antigravity";
11
11
  var AntigravityHttpError = class extends Error {
12
12
  status;
@@ -691,247 +691,26 @@ function createModelCatalog() {
691
691
  };
692
692
  }
693
693
 
694
- // src/quota-status.ts
694
+ // src/config.ts
695
695
  import * as fs from "node:fs";
696
696
  import * as os from "node:os";
697
697
  import * as path from "node:path";
698
698
  import { SettingsList } from "@earendil-works/pi-tui";
699
- function renderProgressBar(fraction, width = 10) {
700
- const clamped = Math.max(0, Math.min(1, fraction));
701
- const filled = Math.round(clamped * width);
702
- return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
703
- }
704
- var QUOTA_WINDOW_RANK = {
705
- "5h": 0,
706
- weekly: 1,
707
- other: 2
708
- };
709
- function classifyQuotaWindow(bucket) {
710
- const w = (bucket.window || "").trim().toLowerCase();
711
- if (w === "5h") return "5h";
712
- if (w === "weekly") return "weekly";
713
- return "other";
714
- }
715
- function shortWindowLabel(bucket, kind) {
716
- if (kind === "5h") return "5h";
717
- if (kind === "weekly") return "Wk";
718
- return bucket.displayName || bucket.bucketId || "Quota";
719
- }
720
- function sortQuotaBuckets(buckets) {
721
- return buckets.slice().sort((a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]);
722
- }
723
- function formatResetRemaining(resetTime) {
724
- if (!resetTime) return null;
725
- const ts = Date.parse(resetTime);
726
- if (!Number.isFinite(ts)) return null;
727
- const delta = ts - Date.now();
728
- if (delta <= 0) return "ready";
729
- const totalMin = Math.ceil(delta / 6e4);
730
- const days = Math.floor(totalMin / (60 * 24));
731
- const hours = Math.floor(totalMin % (60 * 24) / 60);
732
- const mins = totalMin % 60;
733
- if (days > 0) return `in ${days}d ${hours}h`;
734
- if (hours > 0) return `in ${hours}h ${mins}m`;
735
- return `in ${mins}m`;
736
- }
737
- function parseQuotaSummary(data) {
738
- const groups = [];
739
- for (const g of data.groups || []) {
740
- const buckets = [];
741
- for (const b of g.buckets || []) {
742
- buckets.push({
743
- bucketId: b.bucketId || "unknown",
744
- displayName: b.displayName || b.bucketId || "Limit",
745
- window: b.window,
746
- resetTime: b.resetTime,
747
- description: b.description,
748
- remainingFraction: typeof b.remainingFraction === "number" ? b.remainingFraction : 1
749
- });
750
- }
751
- groups.push({
752
- displayName: g.displayName || "Quota Group",
753
- description: g.description,
754
- buckets
755
- });
756
- }
757
- return {
758
- groups,
759
- description: data.description
760
- };
761
- }
762
- function formatQuotaSummary(summary) {
763
- const lines = [];
764
- for (const group of summary.groups) {
765
- if (lines.length > 0) lines.push("");
766
- lines.push(`${group.displayName}`);
767
- for (const b of sortQuotaBuckets(group.buckets)) {
768
- const pct = Math.round(b.remainingFraction * 100);
769
- const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
770
- const reset = formatResetRemaining(b.resetTime);
771
- const suffix = reset && pct < 100 ? ` (${reset})` : "";
772
- lines.push(` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`);
773
- }
774
- }
775
- return lines.join("\n");
776
- }
777
- function isGeminiQuotaGroup(group) {
778
- return (group.buckets || []).some((b) => (b.bucketId || "").toLowerCase().includes("gemini"));
779
- }
780
- function selectQuotaGroup(groups, modelId) {
781
- if (groups.length === 0) return void 0;
782
- const family = classifyModelFamily(modelId);
783
- if (family === "claude" || family === "gpt") {
784
- const found = groups.find((g) => !isGeminiQuotaGroup(g));
785
- if (found) return found;
786
- }
787
- return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];
788
- }
789
- var DEFAULT_WEEKLY_TO_5H_RATIO = 6;
790
- function windowBucket(buckets, kind) {
791
- return buckets.find((b) => classifyQuotaWindow(b) === kind);
792
- }
793
- function poolKey(bucket) {
794
- const id = bucket.bucketId || "unknown";
795
- return id.replace(/[-_](5h|five-?hour|weekly)$/i, "") || id;
796
- }
797
- function extractWindowFractionPairs(summary) {
798
- const pairs = {};
799
- for (const g of summary?.groups || []) {
800
- const bucket5h = windowBucket(g.buckets, "5h");
801
- const bucketWeekly = windowBucket(g.buckets, "weekly");
802
- if (bucket5h && bucketWeekly) pairs[poolKey(bucket5h)] = { "5h": bucket5h.remainingFraction, weekly: bucketWeekly.remainingFraction };
803
- }
804
- return pairs;
805
- }
806
- function calibrateWeeklyTo5hRatio(previous, current, currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
807
- if (!previous || !current) return currentRatio;
808
- for (const key of Object.keys(current)) {
809
- const prev = previous[key];
810
- const curr = current[key];
811
- if (!prev || !curr) continue;
812
- const dFiveHour = prev["5h"] - curr["5h"];
813
- const dWeekly = prev.weekly - curr.weekly;
814
- if (dFiveHour > 1e-4 && dWeekly > 1e-5) {
815
- const observedRatio = dFiveHour / dWeekly;
816
- if (observedRatio >= 1 && observedRatio <= 20) {
817
- return Math.round(observedRatio * 100) / 100;
818
- }
819
- }
820
- }
821
- return currentRatio;
822
- }
823
- function selectUrgentBucket(group, weeklyTo5hRatio) {
824
- const buckets = group.buckets;
825
- if (buckets.length === 0) return void 0;
826
- const bucket5h = windowBucket(buckets, "5h");
827
- const bucketWeekly = windowBucket(buckets, "weekly");
828
- if (bucket5h && bucketWeekly) {
829
- return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction ? bucketWeekly : bucket5h;
830
- }
831
- let worst = buckets[0];
832
- for (const b of buckets) {
833
- if (b.remainingFraction < worst.remainingFraction) worst = b;
834
- }
835
- return worst;
836
- }
837
- function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
838
- const group = selectQuotaGroup(summary.groups, modelId);
839
- if (!group) return void 0;
840
- const urgent = selectUrgentBucket(group, weeklyTo5hRatio);
841
- return urgent ? formatQuotaWindowPart(urgent) : void 0;
842
- }
843
- function buildQuotaFooterBoth(summary, modelId) {
844
- const group = selectQuotaGroup(summary.groups, modelId);
845
- if (!group || group.buckets.length === 0) return void 0;
846
- return sortQuotaBuckets(group.buckets).map(formatQuotaWindowPart).join(" \xB7 ");
847
- }
848
- function formatQuotaWindowPart(bucket) {
849
- const kind = classifyQuotaWindow(bucket);
850
- const prefix = kind === "5h" ? "5h" : kind === "weekly" ? "Wk" : bucket.displayName || bucket.bucketId;
851
- const pct = Math.round(bucket.remainingFraction * 100);
852
- const short = formatResetRemaining(bucket.resetTime)?.replace(/^in /, "");
853
- if (short && pct < 100) return `${prefix} ${pct}% (${short})`;
854
- return `${prefix} ${pct}%`;
855
- }
856
- var ANSI_RED = "\x1B[31m";
857
- var ANSI_YELLOW = "\x1B[33m";
858
699
  var ANSI_FG_RESET = "\x1B[39m";
859
- var QUOTA_WARN_PCT = 30;
860
- var QUOTA_ALERT_PCT = 10;
861
- function colorizeQuotaFooter(footer) {
862
- if (!footer) return void 0;
863
- const match = footer.match(/(\d+)%/);
864
- if (!match) return footer;
865
- const pct = parseInt(match[1], 10);
866
- if (pct <= QUOTA_ALERT_PCT) return `${ANSI_RED}${footer}${ANSI_FG_RESET}`;
867
- if (pct <= QUOTA_WARN_PCT) return `${ANSI_YELLOW}${footer}${ANSI_FG_RESET}`;
868
- return footer;
869
- }
870
- function colorizeQuotaFooterBoth(footer) {
871
- if (!footer) return void 0;
872
- return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part) ?? part).join(" \xB7 ");
873
- }
874
- var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
875
- var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
876
- var MAX_OBSERVATION_AGE_MS = 60 * 60 * 1e3;
877
- function isFiniteNumber(value) {
878
- return typeof value === "number" && Number.isFinite(value);
879
- }
880
- function validPairs(raw) {
881
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
882
- const pairs = {};
883
- for (const [key, value] of Object.entries(raw)) {
884
- const rec = value;
885
- const fiveHour = rec?.["5h"];
886
- const weekly = rec?.weekly;
887
- if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { "5h": fiveHour, weekly };
888
- }
889
- return Object.keys(pairs).length > 0 ? pairs : void 0;
890
- }
891
- function isLiveCtx(ctx) {
892
- try {
893
- void ctx.ui;
894
- return true;
895
- } catch {
896
- return false;
897
- }
898
- }
899
- var FOOTER_MODES = Object.freeze({
900
- off: {
901
- key: "off",
902
- render: () => ({})
903
- },
904
- smart: {
905
- key: "smart",
906
- note: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
907
- render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO) => {
908
- if (!summary) return {};
909
- const plain = buildQuotaFooter(summary, modelId, ratio);
910
- return { plain, colored: colorizeQuotaFooter(plain) };
911
- }
912
- },
913
- all: {
914
- key: "all",
915
- note: "Lists every window of the pool backing the current model (5h or weekly)",
916
- render: (summary, modelId) => {
917
- if (!summary) return {};
918
- const plain = buildQuotaFooterBoth(summary, modelId);
919
- return { plain, colored: colorizeQuotaFooterBoth(plain) };
920
- }
921
- }
922
- });
700
+ var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
923
701
  var FOOTER_MODE_NOTES = Object.freeze({
924
- smart: FOOTER_MODES.smart.note,
925
- all: FOOTER_MODES.all.note
702
+ smart: "Picks whichever window runs out first (5h or weekly), weighting the weekly pool by a ratio learned from your usage",
703
+ all: "Lists every window of the pool backing the current model (5h or weekly)"
926
704
  });
927
- var FOOTER_MODE_OPTIONS = Object.freeze(
928
- Object.keys(FOOTER_MODES)
929
- );
705
+ var FOOTER_MODE_OPTIONS = Object.freeze([
706
+ "off",
707
+ "smart",
708
+ "all"
709
+ ]);
930
710
  function normalizeFooterMode(value) {
931
711
  const v = typeof value === "string" ? value.trim().toLowerCase() : "";
932
- return v in FOOTER_MODES ? v : void 0;
712
+ return FOOTER_MODE_OPTIONS.includes(v) ? v : void 0;
933
713
  }
934
- var PROVIDER_CONFIG_FILE = "pi-provider-antigravity.json";
935
714
  function defaultConfigFile(env = process.env) {
936
715
  const dir = (env.PI_CODING_AGENT_DIR || "").trim() || path.join(os.homedir(), ".pi", "agent");
937
716
  return path.join(dir, PROVIDER_CONFIG_FILE);
@@ -985,21 +764,23 @@ function updateProviderConfig(file, mutate) {
985
764
  function resolveFooterMode(config) {
986
765
  return normalizeFooterMode(config?.settings?.quotaFooter) ?? "off";
987
766
  }
767
+ function loadSubsystemState(subsystem, file = defaultConfigFile()) {
768
+ const state = loadProviderConfig(file)?.states?.[subsystem];
769
+ return state && typeof state === "object" && !Array.isArray(state) ? state : void 0;
770
+ }
771
+ function saveSubsystemState(subsystem, state, file = defaultConfigFile()) {
772
+ return updateProviderConfig(file, (config) => {
773
+ const states = config.states && typeof config.states === "object" && !Array.isArray(config.states) ? config.states : {};
774
+ const current = states[subsystem] && typeof states[subsystem] === "object" && !Array.isArray(states[subsystem]) ? states[subsystem] : {};
775
+ states[subsystem] = { ...current, ...state };
776
+ config.states = states;
777
+ });
778
+ }
988
779
  function fileQuotaStatusStore(file = defaultConfigFile()) {
989
780
  return {
990
781
  loadMode: () => resolveFooterMode(loadProviderConfig(file)),
991
- loadQuotaState: () => {
992
- const quota = loadProviderConfig(file)?.states?.quota;
993
- return quota && typeof quota === "object" && !Array.isArray(quota) ? quota : void 0;
994
- },
995
- // Merges state into the existing file, preserving settings and unknown
996
- // keys. Never clobbers a file we couldn't parse.
997
- saveQuotaState: (state) => updateProviderConfig(file, (config) => {
998
- const states = config.states && typeof config.states === "object" && !Array.isArray(config.states) ? config.states : {};
999
- const quota = states.quota && typeof states.quota === "object" && !Array.isArray(states.quota) ? states.quota : {};
1000
- states.quota = { ...quota, ...state };
1001
- config.states = states;
1002
- })
782
+ loadQuotaState: () => loadSubsystemState("quota", file),
783
+ saveQuotaState: (state) => saveSubsystemState("quota", state, file)
1003
784
  };
1004
785
  }
1005
786
  var SETTINGS_FIELDS = Object.freeze([
@@ -1039,11 +820,11 @@ async function applySettingValue(field, value, file, ctx, quotaStatus) {
1039
820
  function writeFailedMessage(file) {
1040
821
  return `Failed to write ${file}. If the file is not valid JSON, fix or delete it.`;
1041
822
  }
1042
- function previewQuotaFooterText(coord, modelId, mode) {
823
+ function previewQuotaFooterText(coord, modelId, mode, style) {
1043
824
  if (!coord) return void 0;
1044
825
  const normalized = normalizeFooterMode(mode);
1045
826
  if (!normalized || normalized === "off") return void 0;
1046
- const { colored } = coord.renderFooter(modelId, normalized);
827
+ const { colored } = coord.renderFooter(modelId, normalized, style);
1047
828
  if (!colored) return void 0;
1048
829
  return ANSI_FG_RESET + colored;
1049
830
  }
@@ -1117,7 +898,7 @@ async function openSettingsDialog(ctx, quotaStatus, file) {
1117
898
  const item = items.find((i) => i.id === "quotaFooter");
1118
899
  if (!item) return;
1119
900
  const base = quotaField?.description ?? "";
1120
- const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode);
901
+ const sample = mode === "off" ? "hidden" : previewQuotaFooterText(quotaStatus, ctx.model?.id, mode, theme);
1121
902
  const head = sample && base ? `${base}: ${sample}` : sample ?? base;
1122
903
  const note = quotaField?.optionNotes?.[mode];
1123
904
  item.description = note ? `${head}
@@ -1152,12 +933,262 @@ ${note}` : head;
1152
933
  render: (width) => [theme.fg("border", "\u2500".repeat(Math.max(1, width)))]
1153
934
  };
1154
935
  return {
1155
- render: (width) => [...border.render(width), ...list.render(width), ...border.render(width)],
936
+ render: (width) => [
937
+ ...border.render(width),
938
+ ...list.render(width),
939
+ ...border.render(width)
940
+ ],
1156
941
  handleInput: (data) => list.handleInput(data),
1157
942
  invalidate: () => list.invalidate()
1158
943
  };
1159
944
  });
1160
945
  }
946
+
947
+ // src/quota-status.ts
948
+ function renderProgressBar(fraction, width = 10) {
949
+ const clamped = Math.max(0, Math.min(1, fraction));
950
+ const filled = Math.round(clamped * width);
951
+ return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
952
+ }
953
+ var QUOTA_WINDOW_RANK = {
954
+ "5h": 0,
955
+ weekly: 1,
956
+ other: 2
957
+ };
958
+ function classifyQuotaWindow(bucket) {
959
+ const w = (bucket.window || "").trim().toLowerCase();
960
+ if (w === "5h") return "5h";
961
+ if (w === "weekly") return "weekly";
962
+ return "other";
963
+ }
964
+ function shortWindowLabel(bucket, kind) {
965
+ if (kind === "5h") return "5h";
966
+ if (kind === "weekly") return "Wk";
967
+ return bucket.displayName || bucket.bucketId || "Quota";
968
+ }
969
+ function sortQuotaBuckets(buckets) {
970
+ return buckets.slice().sort(
971
+ (a, b) => QUOTA_WINDOW_RANK[classifyQuotaWindow(a)] - QUOTA_WINDOW_RANK[classifyQuotaWindow(b)]
972
+ );
973
+ }
974
+ function formatResetRemaining(resetTime) {
975
+ if (!resetTime) return null;
976
+ const ts = Date.parse(resetTime);
977
+ if (!Number.isFinite(ts)) return null;
978
+ const delta = ts - Date.now();
979
+ if (delta <= 0) return "ready";
980
+ const totalMin = Math.ceil(delta / 6e4);
981
+ const days = Math.floor(totalMin / (60 * 24));
982
+ const hours = Math.floor(totalMin % (60 * 24) / 60);
983
+ const mins = totalMin % 60;
984
+ if (days > 0) return `in ${days}d ${hours}h`;
985
+ if (hours > 0) return `in ${hours}h ${mins}m`;
986
+ return `in ${mins}m`;
987
+ }
988
+ function parseQuotaSummary(data) {
989
+ const groups = [];
990
+ for (const g of data.groups || []) {
991
+ const buckets = [];
992
+ for (const b of g.buckets || []) {
993
+ buckets.push({
994
+ bucketId: b.bucketId || "unknown",
995
+ displayName: b.displayName || b.bucketId || "Limit",
996
+ window: b.window,
997
+ resetTime: b.resetTime,
998
+ description: b.description,
999
+ remainingFraction: typeof b.remainingFraction === "number" ? b.remainingFraction : 1
1000
+ });
1001
+ }
1002
+ groups.push({
1003
+ displayName: g.displayName || "Quota Group",
1004
+ description: g.description,
1005
+ buckets
1006
+ });
1007
+ }
1008
+ return {
1009
+ groups,
1010
+ description: data.description
1011
+ };
1012
+ }
1013
+ function formatQuotaSummary(summary) {
1014
+ const lines = [];
1015
+ for (const group of summary.groups) {
1016
+ if (lines.length > 0) lines.push("");
1017
+ lines.push(`${group.displayName}`);
1018
+ for (const b of sortQuotaBuckets(group.buckets)) {
1019
+ const pct = Math.round(b.remainingFraction * 100);
1020
+ const label = shortWindowLabel(b, classifyQuotaWindow(b)).padEnd(3);
1021
+ const reset = formatResetRemaining(b.resetTime);
1022
+ const suffix = reset && pct < 100 ? ` (${reset})` : "";
1023
+ lines.push(
1024
+ ` ${label} ${renderProgressBar(b.remainingFraction)} ${String(pct).padStart(3)}% left${suffix}`
1025
+ );
1026
+ }
1027
+ }
1028
+ return lines.join("\n");
1029
+ }
1030
+ function isGeminiQuotaGroup(group) {
1031
+ return (group.buckets || []).some((b) => (b.bucketId || "").toLowerCase().includes("gemini"));
1032
+ }
1033
+ function selectQuotaGroup(groups, modelId) {
1034
+ if (groups.length === 0) return void 0;
1035
+ const family = classifyModelFamily(modelId);
1036
+ if (family === "claude" || family === "gpt") {
1037
+ const found = groups.find((g) => !isGeminiQuotaGroup(g));
1038
+ if (found) return found;
1039
+ }
1040
+ return groups.find((g) => isGeminiQuotaGroup(g)) || groups[0];
1041
+ }
1042
+ var DEFAULT_WEEKLY_TO_5H_RATIO = 6;
1043
+ function windowBucket(buckets, kind) {
1044
+ return buckets.find((b) => classifyQuotaWindow(b) === kind);
1045
+ }
1046
+ function poolKey(bucket) {
1047
+ const id = bucket.bucketId || "unknown";
1048
+ return id.replace(/[-_](5h|five-?hour|weekly)$/i, "") || id;
1049
+ }
1050
+ function extractWindowFractionPairs(summary) {
1051
+ const pairs = {};
1052
+ for (const g of summary?.groups || []) {
1053
+ const bucket5h = windowBucket(g.buckets, "5h");
1054
+ const bucketWeekly = windowBucket(g.buckets, "weekly");
1055
+ if (bucket5h && bucketWeekly)
1056
+ pairs[poolKey(bucket5h)] = {
1057
+ "5h": bucket5h.remainingFraction,
1058
+ weekly: bucketWeekly.remainingFraction
1059
+ };
1060
+ }
1061
+ return pairs;
1062
+ }
1063
+ function calibrateWeeklyTo5hRatio(previous, current, currentRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
1064
+ if (!previous || !current) return currentRatio;
1065
+ for (const key of Object.keys(current)) {
1066
+ const prev = previous[key];
1067
+ const curr = current[key];
1068
+ if (!prev || !curr) continue;
1069
+ const dFiveHour = prev["5h"] - curr["5h"];
1070
+ const dWeekly = prev.weekly - curr.weekly;
1071
+ if (dFiveHour > 1e-4 && dWeekly > 1e-5) {
1072
+ const observedRatio = dFiveHour / dWeekly;
1073
+ if (observedRatio >= 1 && observedRatio <= 20) {
1074
+ return Math.round(observedRatio * 100) / 100;
1075
+ }
1076
+ }
1077
+ }
1078
+ return currentRatio;
1079
+ }
1080
+ function selectUrgentBucket(group, weeklyTo5hRatio) {
1081
+ const buckets = group.buckets;
1082
+ if (buckets.length === 0) return void 0;
1083
+ const bucket5h = windowBucket(buckets, "5h");
1084
+ const bucketWeekly = windowBucket(buckets, "weekly");
1085
+ if (bucket5h && bucketWeekly) {
1086
+ return bucketWeekly.remainingFraction * weeklyTo5hRatio < bucket5h.remainingFraction ? bucketWeekly : bucket5h;
1087
+ }
1088
+ let worst = buckets[0];
1089
+ for (const b of buckets) {
1090
+ if (b.remainingFraction < worst.remainingFraction) worst = b;
1091
+ }
1092
+ return worst;
1093
+ }
1094
+ function buildQuotaFooter(summary, modelId, weeklyTo5hRatio = DEFAULT_WEEKLY_TO_5H_RATIO) {
1095
+ const group = selectQuotaGroup(summary.groups, modelId);
1096
+ if (!group) return void 0;
1097
+ const urgent = selectUrgentBucket(group, weeklyTo5hRatio);
1098
+ return urgent ? formatQuotaWindowPart(urgent) : void 0;
1099
+ }
1100
+ function buildQuotaFooterBoth(summary, modelId) {
1101
+ const group = selectQuotaGroup(summary.groups, modelId);
1102
+ if (!group || group.buckets.length === 0) return void 0;
1103
+ return sortQuotaBuckets(group.buckets).map(formatQuotaWindowPart).join(" \xB7 ");
1104
+ }
1105
+ function formatQuotaWindowPart(bucket) {
1106
+ const kind = classifyQuotaWindow(bucket);
1107
+ const prefix = kind === "5h" ? "5h" : kind === "weekly" ? "Wk" : bucket.displayName || bucket.bucketId;
1108
+ const pct = Math.round(bucket.remainingFraction * 100);
1109
+ const short = formatResetRemaining(bucket.resetTime)?.replace(/^in /, "");
1110
+ if (short && pct < 100) return `${prefix} ${pct}% (${short})`;
1111
+ return `${prefix} ${pct}%`;
1112
+ }
1113
+ var ANSI_RED = "\x1B[31m";
1114
+ var ANSI_YELLOW = "\x1B[33m";
1115
+ var ANSI_DIM = "\x1B[90m";
1116
+ var ANSI_FG_RESET2 = "\x1B[39m";
1117
+ var QUOTA_WARN_PCT = 30;
1118
+ var QUOTA_ALERT_PCT = 10;
1119
+ function resolveColorizer(style) {
1120
+ if (typeof style === "function") return style;
1121
+ if (style && typeof style.fg === "function") return (tone, text) => style.fg(tone, text);
1122
+ return (tone, text) => {
1123
+ const code = tone === "error" ? ANSI_RED : tone === "warning" ? ANSI_YELLOW : ANSI_DIM;
1124
+ return `${code}${text}${ANSI_FG_RESET2}`;
1125
+ };
1126
+ }
1127
+ function colorizeQuotaFooter(footer, style) {
1128
+ if (!footer) return void 0;
1129
+ const color = resolveColorizer(style);
1130
+ const match = footer.match(/(\d+)%/);
1131
+ if (!match) return color("dim", footer);
1132
+ const pct = parseInt(match[1], 10);
1133
+ if (pct <= QUOTA_ALERT_PCT) return color("error", footer);
1134
+ if (pct <= QUOTA_WARN_PCT) return color("warning", footer);
1135
+ return color("dim", footer);
1136
+ }
1137
+ function colorizeQuotaFooterBoth(footer, style) {
1138
+ if (!footer) return void 0;
1139
+ const color = resolveColorizer(style);
1140
+ const sep = color("dim", " \xB7 ");
1141
+ return footer.split(" \xB7 ").map((part) => colorizeQuotaFooter(part, style) ?? part).join(sep);
1142
+ }
1143
+ var QUOTA_STATUS_KEY = "pi-provider-antigravity-footer-usage";
1144
+ var QUOTA_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
1145
+ var MAX_OBSERVATION_AGE_MS = 60 * 60 * 1e3;
1146
+ function isFiniteNumber(value) {
1147
+ return typeof value === "number" && Number.isFinite(value);
1148
+ }
1149
+ function validPairs(raw) {
1150
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1151
+ const pairs = {};
1152
+ for (const [key, value] of Object.entries(raw)) {
1153
+ const rec = value;
1154
+ const fiveHour = rec?.["5h"];
1155
+ const weekly = rec?.weekly;
1156
+ if (isFiniteNumber(fiveHour) && isFiniteNumber(weekly)) pairs[key] = { "5h": fiveHour, weekly };
1157
+ }
1158
+ return Object.keys(pairs).length > 0 ? pairs : void 0;
1159
+ }
1160
+ function isLiveCtx(ctx) {
1161
+ try {
1162
+ void ctx.ui;
1163
+ return true;
1164
+ } catch {
1165
+ return false;
1166
+ }
1167
+ }
1168
+ var FOOTER_MODES = Object.freeze({
1169
+ off: {
1170
+ key: "off",
1171
+ render: () => ({})
1172
+ },
1173
+ smart: {
1174
+ key: "smart",
1175
+ note: FOOTER_MODE_NOTES.smart,
1176
+ render: (summary, modelId, ratio = DEFAULT_WEEKLY_TO_5H_RATIO, style) => {
1177
+ if (!summary) return {};
1178
+ const plain = buildQuotaFooter(summary, modelId, ratio);
1179
+ return { plain, colored: colorizeQuotaFooter(plain, style) };
1180
+ }
1181
+ },
1182
+ all: {
1183
+ key: "all",
1184
+ note: FOOTER_MODE_NOTES.all,
1185
+ render: (summary, modelId, _ratio, style) => {
1186
+ if (!summary) return {};
1187
+ const plain = buildQuotaFooterBoth(summary, modelId);
1188
+ return { plain, colored: colorizeQuotaFooterBoth(plain, style) };
1189
+ }
1190
+ }
1191
+ });
1161
1192
  function isAntigravityModel(model) {
1162
1193
  return model?.provider === PROVIDER_ID;
1163
1194
  }
@@ -1187,8 +1218,8 @@ var QuotaStatusCoordinator = class {
1187
1218
  footerFor(modelId, mode = "smart") {
1188
1219
  return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio).plain;
1189
1220
  }
1190
- renderFooter(modelId, mode = "smart") {
1191
- return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio) ?? {};
1221
+ renderFooter(modelId, mode = "smart", style) {
1222
+ return FOOTER_MODES[mode]?.render(this.summary, modelId, this.weeklyTo5hRatio, style) ?? {};
1192
1223
  }
1193
1224
  get isFresh() {
1194
1225
  return !!this.summary && Date.now() - this.fetchedAt < QUOTA_REFRESH_INTERVAL_MS;
@@ -1218,7 +1249,8 @@ var QuotaStatusCoordinator = class {
1218
1249
  ctx.ui.setStatus(QUOTA_STATUS_KEY, void 0);
1219
1250
  return;
1220
1251
  }
1221
- const { colored } = this.renderFooter(ctx.model?.id, mode);
1252
+ const theme = ctx.ui?.theme;
1253
+ const { colored } = this.renderFooter(ctx.model?.id, mode, theme);
1222
1254
  ctx.ui.setStatus(QUOTA_STATUS_KEY, colored);
1223
1255
  }
1224
1256
  // Atomic inspect for /antigravity usage: forces fresh quota fetch, repaints
@@ -1231,10 +1263,6 @@ var QuotaStatusCoordinator = class {
1231
1263
  this.paint(ctx);
1232
1264
  return formatQuotaSummary(summary);
1233
1265
  }
1234
- // Opens provider settings dialog/menu and updates state atomically.
1235
- async openSettings(ctx) {
1236
- await openSettings(ctx, this);
1237
- }
1238
1266
  // One fetch for preview purposes even when the footer slot is off or the
1239
1267
  // current model belongs to another provider: opening quota settings is an
1240
1268
  // explicit look at quota (like /antigravity usage), and the preview is the
@@ -1270,7 +1298,11 @@ var QuotaStatusCoordinator = class {
1270
1298
  // Accepts an externally fetched summary into the shared cache: same
1271
1299
  // calibration, persistence, and freshness as fetch().
1272
1300
  ingest(summary) {
1273
- const calibrated = calibrateWeeklyTo5hRatio(this.lastPairs, extractWindowFractionPairs(summary), this.weeklyTo5hRatio);
1301
+ const calibrated = calibrateWeeklyTo5hRatio(
1302
+ this.lastPairs,
1303
+ extractWindowFractionPairs(summary),
1304
+ this.weeklyTo5hRatio
1305
+ );
1274
1306
  this.weeklyTo5hRatio = calibrated;
1275
1307
  this.lastPairs = extractWindowFractionPairs(summary);
1276
1308
  this.summary = summary;