@cad0p/pi-usage 0.11.5-20261103.0 → 0.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,9 +40,7 @@ shuvquota, and it never writes to a credential file.
40
40
  * Antigravity's access token **may** be refreshed, but only in memory. Google
41
41
  does not rotate that refresh token, so Pi's stored copy stays valid. Claude and
42
42
  Codex are never refreshed, because they **do** rotate and persisting a rotated
43
- token would break Pi. Every surface reports the token the run *used*: a renewal
44
- that succeeded clears the stored "token expired" claim, and only a failed one
45
- leaves the expiry (and the warning) on screen.
43
+ token would break Pi.
46
44
  * The Claude Code store (`~/.claude/.credentials.json`) is opened read-only too, and
47
45
  its refresh token is never even read into memory, so nothing here can rotate a
48
46
  token the installed `claude` owns. `~/.claude.json` is read only for the account
@@ -302,20 +300,13 @@ dependencies to wrap the body in a `ScrollView`.
302
300
 
303
301
  `tab` (or `2`) opens **History**, the second page: the durable token history for
304
302
  one month, with the same foldable sections. A provider section lists the month's
305
- days, the prompt count for that month and for all time, the token split by
306
- model, and — for a multi-account provider — one row per account with its month
307
- and all-time totals (every account the family has in the month *or* all-time, so
308
- one busy key does not hide the others). `←`/`→` (or
303
+ days, the token split by model, and — for a multi-account provider — one row per
304
+ account with its month and all-time totals (every account the family has in the
305
+ month *or* all-time, so one busy key does not hide the others). `←`/`→` (or
309
306
  `[`/`]`) walk the browsable months — those with a file, plus the live month,
310
307
  which is where History opens even before its first freeze — and `tab` (or `1`)
311
308
  comes back to Now. The all-time
312
- figure is `totals.json` (frozen through its watermark) plus, when it is merged,
313
- the same open day the month counts, so today is never missing from it. A rollup
314
- that cannot cover the month on screen — a freeze whose totals write failed, a
315
- month file restored behind `totals.json` — is still printed, dimmed, with
316
- `~ all-time lags this month; run piquota stats --rebuild-totals` above the rows:
317
- the figure is the only answer to "how much has this burned ever", so it is
318
- explained rather than dropped. A family's open day
309
+ figure is `totals.json` (frozen through its watermark). A family's open day
319
310
  (today) is merged from the live report only when the report was *generated* today
320
311
  and the frozen month has no row for that family's day — per family, so one family
321
312
  frozen early never hides another's live row — and a day is counted exactly once.
@@ -330,11 +321,7 @@ the last keypress asked for.
330
321
  History reads `piquota stats --json [--month YYYY-MM]`: the month list, one
331
322
  month's days per family/slot/model, the all-time rollup and notes. That command
332
323
  never takes the writer lock and never writes, so it is safe to run while a scan
333
- is freezing a day. Everything it reports is frozen: `lastFrozenDay` is its
334
- watermark, and neither `piquota stats` nor its JSON moves an open day into a
335
- total. The History page merges the live day itself, for the month and for the
336
- all-time figures alike, and prints the same "comes from the live report" note
337
- under both.
324
+ is freezing a day.
338
325
 
339
326
  A provider with several accounts is shown as one header (`OP-Go · 7 of 14 accounts
340
327
  ok`) plus one line per account with every window and its reset, e.g.
@@ -452,12 +439,6 @@ Rp suite) without external network daemons or closed-source intermediaries:
452
439
  * **Percentages and Countdown**: `usedPercent` and `remainingPercent` report
453
440
  percentages (0..100); `resetsInSec` provides the exact integer seconds until
454
441
  reset, immune to local clock skew.
455
- * **`expiresInMin` and `tokenRenewedInMemory`**: the freshness of the credential the
456
- run actually used. Antigravity's token is renewed in memory, so its result
457
- carries `tokenRenewedInMemory: true` and `expiresInMin: null` instead of Pi's
458
- stale stored countdown; a renewal that failed keeps the stored value, which is
459
- what the panel's `token EXPIRED` badge and the warning line describe. The field
460
- is only present when a renewal happened.
461
442
  * **`notConfigured` vs `ok`**: unconfigured providers report `notConfigured: true`,
462
443
  allowing consumers to cleanly omit them without displaying false-positive
463
444
  error indicators.
@@ -845,7 +826,7 @@ extensions/quota-panel.ts Pi TUI line and /usage
845
826
  ## Tests
846
827
 
847
828
  ```bash
848
- node --test tests/*.test.mjs # 413 tests, fake tokens only, no network
829
+ node --test tests/*.test.mjs # 401 tests, fake tokens only, no network
849
830
  ```
850
831
 
851
832
  Modules covered: `auth.json` parsing and de-duplication, the Claude Code store
@@ -1036,33 +1036,9 @@ type HistoryFamily = {
1036
1036
  models: Map<string, number>;
1037
1037
  accounts: Map<string, { tokens: number; prompts: number; models: Map<string, number> }>;
1038
1038
  allTime: number;
1039
- allTimePrompts: number;
1040
- allTimeAccounts: Map<string, { tokens: number; prompts: number }>;
1041
- /** The live day's delta, added to both the month and the all-time figures. */
1042
- liveTokens: number;
1043
- livePrompts: number;
1044
- liveAccounts: Map<string, { tokens: number; prompts: number }>;
1039
+ allTimeAccounts: Map<string, number>;
1045
1040
  };
1046
1041
 
1047
- /** An all-time figure, and whether the rollup behind it lags the month on screen. */
1048
- type AllTimeFigure = { value: number; lags: boolean };
1049
-
1050
- /**
1051
- * The all-time figure for one scope. All time contains every frozen day, so it
1052
- * can never be smaller than a month of it: a smaller rollup means totals.json is
1053
- * behind the month file — a freeze whose totals write failed, a month restored
1054
- * behind the rollup. That is printed, dimmed and with a note, never hidden: the
1055
- * figure is the only answer to "how much has this burned ever", and dropping it
1056
- * silently is a worse lie than a number with a caveat.
1057
- */
1058
- function allTimeFigure(month: number, allTime: number): AllTimeFigure {
1059
- if (allTime <= 0) return { value: 0, lags: false };
1060
- return { value: allTime, lags: allTime < month };
1061
- }
1062
-
1063
- /** The note printed with an all-time figure that cannot cover the month on screen. */
1064
- const ALL_TIME_LAGS_NOTE = "all-time lags this month; run piquota stats --rebuild-totals";
1065
-
1066
1042
  /**
1067
1043
  * What outlives one overlay instance. Every bare `/usage` builds a fresh view,
1068
1044
  * so the folds and the page it was closed on live in the extension — for the pi
@@ -1843,19 +1819,7 @@ export class UsageOverlay {
1843
1819
  const ensure = (family: string): HistoryFamily => {
1844
1820
  let entry = families.get(family);
1845
1821
  if (!entry) {
1846
- entry = {
1847
- tokens: 0,
1848
- prompts: 0,
1849
- days: new Map(),
1850
- models: new Map(),
1851
- accounts: new Map(),
1852
- allTime: 0,
1853
- allTimePrompts: 0,
1854
- allTimeAccounts: new Map(),
1855
- liveTokens: 0,
1856
- livePrompts: 0,
1857
- liveAccounts: new Map(),
1858
- };
1822
+ entry = { tokens: 0, prompts: 0, days: new Map(), models: new Map(), accounts: new Map(), allTime: 0, allTimeAccounts: new Map() };
1859
1823
  families.set(family, entry);
1860
1824
  }
1861
1825
  return entry;
@@ -1920,10 +1884,6 @@ export class UsageOverlay {
1920
1884
  const entry = ensure(family);
1921
1885
  entry.tokens += tokens;
1922
1886
  entry.prompts += prompts;
1923
- // Kept apart from the frozen month: the all-time figures add this same
1924
- // delta below, and a live day may only ever be counted once in each.
1925
- entry.liveTokens += tokens;
1926
- entry.livePrompts += prompts;
1927
1887
  entry.days.set(today, (entry.days.get(today) ?? 0) + tokens);
1928
1888
  for (const [model, modelTokens] of Object.entries(familyStats.todayTokensByModel ?? {})) {
1929
1889
  entry.models.set(model, (entry.models.get(model) ?? 0) + Math.max(0, Math.round(Number(modelTokens) || 0)));
@@ -1931,12 +1891,8 @@ export class UsageOverlay {
1931
1891
  for (const [rawLabel, account] of Object.entries(familyStats.byAccount ?? {})) {
1932
1892
  const label = rawLabel === "#1" ? "" : rawLabel;
1933
1893
  const target = entry.accounts.get(label) ?? { tokens: 0, prompts: 0, models: new Map<string, number>() };
1934
- const accountTokens = Math.max(0, Math.round(Number(account?.todayTokens) || 0));
1935
- const accountPrompts = Math.max(0, Math.round(Number(account?.todayPrompts) || 0));
1936
- target.tokens += accountTokens;
1937
- target.prompts += accountPrompts;
1938
- const liveAccount = entry.liveAccounts.get(label) ?? { tokens: 0, prompts: 0 };
1939
- entry.liveAccounts.set(label, { tokens: liveAccount.tokens + accountTokens, prompts: liveAccount.prompts + accountPrompts });
1894
+ target.tokens += Math.max(0, Math.round(Number(account?.todayTokens) || 0));
1895
+ target.prompts += Math.max(0, Math.round(Number(account?.todayPrompts) || 0));
1940
1896
  // The account's own models for today: without them the merged row
1941
1897
  // shows a month total and no model, which reads as "no usage".
1942
1898
  for (const [model, modelTokens] of Object.entries(account?.todayTokensByModel ?? {})) {
@@ -1950,33 +1906,10 @@ export class UsageOverlay {
1950
1906
  for (const [family, totals] of Object.entries(payload.totals?.families ?? {})) {
1951
1907
  const entry = ensure(family);
1952
1908
  entry.allTime = Object.values(totals?.modelUsage ?? {}).reduce((sum, bucket) => sum + totalsModelTokens(bucket), 0);
1953
- entry.allTimePrompts = Math.max(0, Math.round(Number(totals?.totalPrompts) || 0));
1954
1909
  for (const [rawLabel, account] of Object.entries(totals?.accounts ?? {})) {
1955
1910
  const label = rawLabel === "#1" ? "" : rawLabel;
1956
1911
  const tokens = Object.values(account?.modelUsage ?? {}).reduce((sum, bucket) => sum + totalsModelTokens(bucket), 0);
1957
- const prompts = Math.max(0, Math.round(Number(account?.totalPrompts) || 0));
1958
- const seen = entry.allTimeAccounts.get(label) ?? { tokens: 0, prompts: 0 };
1959
- entry.allTimeAccounts.set(label, { tokens: seen.tokens + tokens, prompts: seen.prompts + prompts });
1960
- }
1961
- }
1962
-
1963
- // Today's live day is part of all time, not only of this month: it is the
1964
- // same usage the month row and the day row above already show, so it is
1965
- // added here exactly once — the single place the live delta is folded into
1966
- // a rollup, and only under the merge guard above (the day frozen, or a
1967
- // report from another day, never produces a delta at all).
1968
- //
1969
- // A family with no rollup in totals.json gets nothing: there is no all-time
1970
- // figure to extend, and calling today's tokens "all-time" would hide every
1971
- // frozen day before it. The page already carries the CLI's note for that.
1972
- for (const [family, entry] of families) {
1973
- if (!payload.totals?.families?.[family]) continue;
1974
- if (entry.liveTokens === 0 && entry.livePrompts === 0) continue;
1975
- entry.allTime += entry.liveTokens;
1976
- entry.allTimePrompts += entry.livePrompts;
1977
- for (const [label, delta] of entry.liveAccounts) {
1978
- const seen = entry.allTimeAccounts.get(label) ?? { tokens: 0, prompts: 0 };
1979
- entry.allTimeAccounts.set(label, { tokens: seen.tokens + delta.tokens, prompts: seen.prompts + delta.prompts });
1912
+ entry.allTimeAccounts.set(label, (entry.allTimeAccounts.get(label) ?? 0) + tokens);
1980
1913
  }
1981
1914
  }
1982
1915
 
@@ -2003,29 +1936,10 @@ export class UsageOverlay {
2003
1936
  // Multi-account families use the same short name as the Now panel header.
2004
1937
  const display = accountLabels.length > 1 ? SHORT_NAME[family] ?? FAMILY_LABEL[family] ?? family : FAMILY_LABEL[family] ?? family;
2005
1938
  const name = paint(BRAND[family] ?? "#8B949E", display);
2006
- // The month first, then the all-time rollup with today's live day folded
2007
- // in: every figure here counts a live day once. Prompts are scope-labelled
2008
- // in their own row below, because an unlabelled count reads as the wrong
2009
- // one of the two.
2010
- //
2011
- // Every all-time figure is computed before the first line is built: the
2012
- // note a lagging rollup earns belongs at the top of the section, where it
2013
- // explains the header, the prompts row and the account rows at once.
2014
1939
  const meta = [`${formatTokens(entry.tokens)} in ${month}`];
2015
- const familyAllTime = allTimeFigure(entry.tokens, entry.allTime);
2016
- if (familyAllTime.value > 0) meta.push(`${formatTokens(familyAllTime.value)} all-time`);
2017
- const promptsAllTime = allTimeFigure(entry.prompts, entry.allTimePrompts);
2018
- const accountAllTime = new Map<string, AllTimeFigure>();
2019
- for (const accountLabel of accountLabels) {
2020
- accountAllTime.set(
2021
- accountLabel,
2022
- allTimeFigure(entry.accounts.get(accountLabel)?.tokens ?? 0, entry.allTimeAccounts.get(accountLabel)?.tokens ?? 0),
2023
- );
2024
- }
2025
- const lagsMonth =
2026
- familyAllTime.lags || promptsAllTime.lags || Array.from(accountAllTime.values()).some((figure) => figure.lags);
1940
+ if (entry.allTime > 0) meta.push(`${formatTokens(entry.allTime)} all-time`);
1941
+ if (entry.prompts > 0) meta.push(`${entry.prompts} prompt${entry.prompts === 1 ? "" : "s"}`);
2027
1942
  const body: string[] = [];
2028
- if (lagsMonth) body.push(` ${this.theme.fg("warning", `~ ${ALL_TIME_LAGS_NOTE}`)}`);
2029
1943
  const label = (text: string) => ` ${this.theme.fg("dim", text.padEnd(6))} `;
2030
1944
  const rest = ` ${" ".repeat(8)}`;
2031
1945
  const dayCells = Array.from(entry.days.entries())
@@ -2037,22 +1951,6 @@ export class UsageOverlay {
2037
1951
  rest,
2038
1952
  bodyWidth,
2039
1953
  ));
2040
- // Both scopes, always: the live day is merged into each of them once, so
2041
- // the month count and the all-time count may never disagree about it.
2042
- const promptCells: string[] = [];
2043
- if (entry.prompts > 0) promptCells.push(`${entry.prompts} in ${month}`);
2044
- if (promptsAllTime.value > 0) {
2045
- // A figure that lags the month is still a figure: it is printed, dimmed
2046
- // whole, and the note above the rows says why it is small.
2047
- promptCells.push(
2048
- promptsAllTime.lags
2049
- ? this.theme.fg("dim", `${promptsAllTime.value} all-time`)
2050
- : `${promptsAllTime.value} ${this.theme.fg("dim", "all-time")}`,
2051
- );
2052
- }
2053
- if (promptCells.length > 0) {
2054
- body.push(...wrapCells(promptCells, label("prompts"), rest, bodyWidth));
2055
- }
2056
1954
  if (entry.models.size > 0) {
2057
1955
  const top = Array.from(entry.models.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
2058
1956
  const shown = top.slice(0, 5).map(([model, tokens]) => `${shortModelName(model)} ${formatTokens(tokens)}`);
@@ -2064,14 +1962,8 @@ export class UsageOverlay {
2064
1962
  for (const accountLabel of accountLabels) {
2065
1963
  const account = entry.accounts.get(accountLabel) ?? { tokens: 0, prompts: 0, models: new Map<string, number>() };
2066
1964
  const parts = [`${this.theme.fg("dim", "month")} ${formatTokens(account.tokens)}`];
2067
- const accountFigure = accountAllTime.get(accountLabel) ?? { value: 0, lags: false };
2068
- if (accountFigure.value > 0) {
2069
- parts.push(
2070
- accountFigure.lags
2071
- ? this.theme.fg("dim", `all-time ${formatTokens(accountFigure.value)}`)
2072
- : `${this.theme.fg("dim", "all-time")} ${formatTokens(accountFigure.value)}`,
2073
- );
2074
- }
1965
+ const allTime = entry.allTimeAccounts.get(accountLabel) ?? 0;
1966
+ if (allTime > 0) parts.push(`${this.theme.fg("dim", "all-time")} ${formatTokens(allTime)}`);
2075
1967
  const best = Array.from(account.models.entries()).sort((a, b) => b[1] - a[1])[0];
2076
1968
  if (best) parts.push(this.theme.fg("dim", shortModelName(best[0])));
2077
1969
  body.push(` ${this.theme.fg("dim", accountLabel.padEnd(labelWidth))} ${parts.join(this.theme.fg("dim", " · "))}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cad0p/pi-usage",
3
- "version": "0.11.5-20261103.0",
3
+ "version": "0.11.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/engine.js CHANGED
@@ -404,11 +404,7 @@ export async function collectQuota(options = {}) {
404
404
  const results = [];
405
405
  const familyWarnList = [];
406
406
  for (const { result, warning, crashed } of outcomes) {
407
- // The stored-credential warning is a claim about this run, and a provider
408
- // that renewed the token in memory for it knows better than the store:
409
- // "the Pi token expired 271316m ago" next to two healthy Antigravity
410
- // windows was reading Pi's stale copy, not the token that was used.
411
- if (warning && result.tokenRenewedInMemory !== true) familyWarnList.push(warning);
407
+ if (warning) familyWarnList.push(warning);
412
408
  if (!crashed) {
413
409
  // Bookkeeping must degrade like the provider call it follows: a
414
410
  // failing backoff write is a warning, never a rejected family.
package/src/model.js CHANGED
@@ -33,10 +33,6 @@
33
33
  * @property {string} source Credential source description.
34
34
  * @property {"pi" | "claude-code"} [sourceKind] Which kind of store produced it.
35
35
  * @property {string | null} expiresInMin Credential freshness, when known.
36
- * @property {true} [tokenRenewedInMemory] Present only when the provider renewed
37
- * this token in memory for this run: the
38
- * stored credential's freshness then no
39
- * longer describes the request made.
40
36
  */
41
37
 
42
38
  /**
@@ -251,7 +247,6 @@ export function humanReset(seconds) {
251
247
  * source: string,
252
248
  * now?: number,
253
249
  * expiresInMin?: number | null,
254
- * tokenRenewedInMemory?: boolean,
255
250
  * windows?: QuotaWindow[],
256
251
  * }} input
257
252
  * @returns {QuotaResult}
@@ -261,7 +256,7 @@ export function degradedResult(input) {
261
256
  input.notConfigured ??
262
257
  (typeof input.error === "string" && input.error.startsWith("no ") && input.error.includes("credential in the Pi store")),
263
258
  );
264
- const result = {
259
+ return {
265
260
  family: input.family,
266
261
  label: input.label,
267
262
  account: input.account ?? "unknown",
@@ -275,27 +270,6 @@ export function degradedResult(input) {
275
270
  sourceKind: input.sourceKind,
276
271
  expiresInMin: input.expiresInMin ?? null,
277
272
  };
278
- // Only ever present when a provider renewed the token in memory: a field on
279
- // every result would be one more thing the JSON feed promises to keep.
280
- if (input.tokenRenewedInMemory === true) result.tokenRenewedInMemory = true;
281
- return result;
282
- }
283
-
284
- /**
285
- * Which field `displayIdentity` would print for this credential.
286
- *
287
- * A provider that adds a second identifier of its own (Antigravity's Cloud Code
288
- * project) needs to know whether the identity already *is* that identifier, or
289
- * it prints the same value twice.
290
- *
291
- * @param {{ email?: string | null, accountId?: string | null, projectId?: string | null, identity?: string }} credential
292
- * @returns {"email" | "accountId" | "projectId" | "identity" | "none"}
293
- */
294
- export function identityKind(credential) {
295
- if (credential.email) return "email";
296
- if (credential.accountId) return "accountId";
297
- if (credential.projectId) return "projectId";
298
- return credential.identity ? "identity" : "none";
299
273
  }
300
274
 
301
275
  /**
@@ -305,18 +279,10 @@ export function identityKind(credential) {
305
279
  * @returns {string}
306
280
  */
307
281
  export function displayIdentity(credential) {
308
- switch (identityKind(credential)) {
309
- case "email":
310
- return /** @type {string} */ (credential.email);
311
- case "accountId":
312
- return `${/** @type {string} */ (credential.accountId).slice(0, 8)}…`;
313
- case "projectId":
314
- return `project ${/** @type {string} */ (credential.projectId).slice(0, 8)}…`;
315
- case "identity":
316
- return `id ${/** @type {string} */ (credential.identity).slice(0, 8)}…`;
317
- default:
318
- return "unknown";
319
- }
282
+ if (credential.email) return credential.email;
283
+ if (credential.accountId) return `${credential.accountId.slice(0, 8)}…`;
284
+ if (credential.projectId) return `project ${credential.projectId.slice(0, 8)}…`;
285
+ return credential.identity ? `id ${credential.identity.slice(0, 8)}…` : "unknown";
320
286
  }
321
287
 
322
288
  /**
@@ -89,15 +89,11 @@ export function resolveOAuthClient(options = {}) {
89
89
  }
90
90
 
91
91
  /**
92
- * Describe the OAuth client a refresh used, resolved the same way the refresh
93
- * resolved it: a description of the environment's client while the request went
94
- * out with the installed package's would name the wrong credential source.
95
- *
96
- * @param {{ client?: { clientId: string, clientSecret: string }, env?: Record<string, string | undefined>, sourcePath?: string }} [options]
92
+ * @param {{ client?: { clientId: string, clientSecret: string }, fetchFn?: typeof fetch, timeoutMs?: number }} [options]
97
93
  * @returns {string}
98
94
  */
99
95
  export function describeOAuthClient(options = {}) {
100
- const client = options.client ?? resolveOAuthClient({ env: options.env, sourcePath: options.sourcePath });
96
+ const client = options.client ?? resolveOAuthClient();
101
97
  return client.source === "env"
102
98
  ? "client from ANTIGRAVITY_CLIENT_ID/SECRET"
103
99
  : client.source === "pi-antigravity"
@@ -112,15 +108,12 @@ export function describeOAuthClient(options = {}) {
112
108
  * fetchFn?: typeof fetch,
113
109
  * timeoutMs?: number,
114
110
  * now?: number,
115
- * env?: Record<string, string | undefined>,
116
111
  * }} [options]
117
112
  * @returns {Promise<{ access: string, refresh: string, expires: number } | { error: string }>}
118
113
  */
119
114
  export async function refreshAccessToken(refreshToken, options = {}) {
120
115
  if (!refreshToken) return { error: "no refresh token available" };
121
- // The caller's environment decides the client, so the one that describes a
122
- // failed refresh (describeOAuthClient) is the one that was actually used.
123
- const client = options.client ?? resolveOAuthClient({ env: options.env });
116
+ const client = options.client ?? resolveOAuthClient();
124
117
 
125
118
  const response = await requestJson(GOOGLE_TOKEN_URL, {
126
119
  method: "POST",
@@ -159,7 +152,6 @@ export async function refreshAccessToken(refreshToken, options = {}) {
159
152
  * now?: number,
160
153
  * bufferMs?: number,
161
154
  * enabled?: boolean,
162
- * env?: Record<string, string | undefined>,
163
155
  * }} [options]
164
156
  * @returns {Promise<{ ok: boolean, refreshed: boolean, error?: string }>}
165
157
  */
@@ -15,7 +15,7 @@
15
15
  * project is read-only, so no refresh is performed here either.
16
16
  */
17
17
 
18
- import { buildWindow, clampPercent, degradedResult, displayIdentity, finiteNumber, identityKind, stringValue, windowFromName } from "../model.js";
18
+ import { buildWindow, clampPercent, degradedResult, displayIdentity, finiteNumber, stringValue, windowFromName } from "../model.js";
19
19
  import { requestJson } from "../http.js";
20
20
  import { describeOAuthClient, ensureFreshAccessToken } from "./antigravity-oauth.js";
21
21
 
@@ -109,24 +109,6 @@ function windowFromBucket(bucket, context, now) {
109
109
  });
110
110
  }
111
111
 
112
- /**
113
- * The account line for a resolved project.
114
- *
115
- * Pi stores no e-mail for Antigravity, so the project id *is* the display
116
- * identity; appending it again printed "project rising-f… · rising-f…". An
117
- * account that has a better identity (an e-mail or an id) still gets the
118
- * project, because the same login can hold several.
119
- *
120
- * @param {import("../auth/pi-auth.js").PiCredential} credential
121
- * @param {string | null} projectId
122
- * @returns {string}
123
- */
124
- function accountLabel(credential, projectId) {
125
- const identity = displayIdentity(credential);
126
- if (!projectId || identityKind(credential) === "projectId") return identity;
127
- return `${identity} · ${projectId.slice(0, 8)}…`;
128
- }
129
-
130
112
  /**
131
113
  * @param {import("../auth/pi-auth.js").PiCredential} credential
132
114
  * @param {{ now?: number, fetchFn?: typeof fetch, timeoutMs?: number, expiresInMin?: number | null, env?: Record<string, string | undefined>, refresh?: boolean }} [options]
@@ -157,19 +139,7 @@ export async function fetchQuota(credential, options = {}) {
157
139
  timeoutMs: options.timeoutMs,
158
140
  now,
159
141
  enabled: options.refresh,
160
- env: options.env,
161
142
  });
162
- // `expiresInMin` arrives from the engine as the freshness of the *stored*
163
- // credential, and that is only the truth about this run when the refresh
164
- // above did not happen: a token renewed in memory expires in an hour, so
165
- // repeating the stored value made every surface print "token expired" (and
166
- // the engine warn about it) next to healthy windows. A refresh that failed
167
- // keeps the stored value — then the expiry claim is what the user must see.
168
- const renewed = freshness.refreshed === true;
169
- const expiresInMin = renewed ? null : options.expiresInMin ?? null;
170
- // Present only when a renewal happened: an always-false field would be one
171
- // more thing the JSON feed promises to keep.
172
- const renewal = renewed ? { tokenRenewedInMemory: true } : {};
173
143
  let projectId = credential.projectId ?? null;
174
144
 
175
145
  /** @type {string | null} */
@@ -241,9 +211,7 @@ export async function fetchQuota(credential, options = {}) {
241
211
 
242
212
  return {
243
213
  ...base,
244
- account: accountLabel(credential, projectId),
245
- expiresInMin,
246
- ...renewal,
214
+ account: projectId ? `${displayIdentity(credential)} · ${projectId.slice(0, 8)}…` : displayIdentity(credential),
247
215
  plan: stringValue(body.paidTier) ?? stringValue(body.currentTier),
248
216
  windows,
249
217
  error: null,
@@ -253,11 +221,5 @@ export async function fetchQuota(credential, options = {}) {
253
221
  }
254
222
 
255
223
  const suffix = authFailed ? ` (${describeOAuthClient({ env: options.env })})` : "";
256
- return degradedResult({
257
- ...base,
258
- account: accountLabel(credential, projectId),
259
- expiresInMin,
260
- ...renewal,
261
- error: `${lastError ?? "Antigravity quota unavailable"}${suffix}`,
262
- });
224
+ return degradedResult({ ...base, error: `${lastError ?? "Antigravity quota unavailable"}${suffix}` });
263
225
  }