sidekick-shared 0.28.1 → 0.28.3

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 (58) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +29 -13
  3. package/dist/browser.d.ts +2 -0
  4. package/dist/browser.js +10 -1
  5. package/dist/codexQuota.js +17 -4
  6. package/dist/esm/browser.js +2 -0
  7. package/dist/esm/codexQuota.js +18 -5
  8. package/dist/esm/index.js +3 -3
  9. package/dist/esm/modelContext.js +32 -0
  10. package/dist/esm/modelInfo.js +186 -0
  11. package/dist/esm/multiProviderQuotaService.js +1 -1
  12. package/dist/esm/quota.js +356 -24
  13. package/dist/esm/quotaHistory.js +33 -8
  14. package/dist/esm/quotaPoller.js +5 -2
  15. package/dist/esm/quotaResolver.js +31 -6
  16. package/dist/esm/quotaSnapshots.js +65 -10
  17. package/dist/esm/quotaThresholds.js +7 -2
  18. package/dist/esm/schemas/index.js +2 -2
  19. package/dist/esm/schemas/quota.js +30 -0
  20. package/dist/esm/schemas/quotaHistory.js +10 -2
  21. package/dist/esm/schemas/stateFile.js +9 -0
  22. package/dist/esm/stateFile.js +18 -3
  23. package/dist/esm/statusline/claudeStatuslinePayload.js +16 -4
  24. package/dist/esm/statusline/formatter.js +57 -18
  25. package/dist/esm/statusline/index.js +2 -2
  26. package/dist/esm/zaiQuotaApi.js +4 -2
  27. package/dist/index.d.ts +6 -6
  28. package/dist/index.js +18 -5
  29. package/dist/modelContext.js +32 -0
  30. package/dist/modelInfo.js +186 -0
  31. package/dist/multiProviderQuotaService.js +1 -1
  32. package/dist/quota.d.ts +131 -1
  33. package/dist/quota.js +366 -24
  34. package/dist/quotaHistory.d.ts +10 -0
  35. package/dist/quotaHistory.js +34 -8
  36. package/dist/quotaPoller.d.ts +11 -3
  37. package/dist/quotaPoller.js +5 -2
  38. package/dist/quotaResolver.js +30 -5
  39. package/dist/quotaSnapshots.js +64 -9
  40. package/dist/quotaThresholds.js +7 -2
  41. package/dist/schemas/index.d.ts +3 -3
  42. package/dist/schemas/index.js +5 -2
  43. package/dist/schemas/quota.d.ts +258 -0
  44. package/dist/schemas/quota.js +31 -1
  45. package/dist/schemas/quotaHistory.d.ts +2 -0
  46. package/dist/schemas/quotaHistory.js +10 -2
  47. package/dist/schemas/stateFile.d.ts +34 -0
  48. package/dist/schemas/stateFile.js +10 -1
  49. package/dist/stateFile.d.ts +18 -0
  50. package/dist/stateFile.js +17 -2
  51. package/dist/statusline/claudeStatuslinePayload.d.ts +4 -3
  52. package/dist/statusline/claudeStatuslinePayload.js +16 -4
  53. package/dist/statusline/formatter.d.ts +5 -0
  54. package/dist/statusline/formatter.js +56 -17
  55. package/dist/statusline/index.d.ts +4 -4
  56. package/dist/statusline/index.js +5 -1
  57. package/dist/zaiQuotaApi.js +3 -1
  58. package/package.json +1 -1
@@ -24,8 +24,16 @@ exports.quotaHistorySampleSchema = zod_1.z.object({
24
24
  runtimeProvider: exports.quotaHistoryRuntimeProviderSchema,
25
25
  providerId: zod_1.z.string(),
26
26
  workspaceId: zod_1.z.string(),
27
- fiveHour: zod_1.z.object({ utilization: zod_1.z.number(), resetsAt: zod_1.z.string() }),
28
- sevenDay: zod_1.z.object({ utilization: zod_1.z.number(), resetsAt: zod_1.z.string() }),
27
+ fiveHour: zod_1.z.object({
28
+ utilization: zod_1.z.number(),
29
+ resetsAt: zod_1.z.string(),
30
+ absent: zod_1.z.boolean().optional(),
31
+ }),
32
+ sevenDay: zod_1.z.object({
33
+ utilization: zod_1.z.number(),
34
+ resetsAt: zod_1.z.string(),
35
+ absent: zod_1.z.boolean().optional(),
36
+ }),
29
37
  available: zod_1.z.boolean(),
30
38
  error: zod_1.z.string().optional(),
31
39
  source: zod_1.z.enum(['session', 'cache', 'api', 'statusline']).optional(),
@@ -16,15 +16,24 @@ export declare const stateFileWriterSchema: z.ZodEnum<{
16
16
  export declare const stateFileQuotaWindowSchema: z.ZodObject<{
17
17
  utilization: z.ZodNumber;
18
18
  resetsAt: z.ZodString;
19
+ absent: z.ZodOptional<z.ZodBoolean>;
20
+ }, z.core.$strip>;
21
+ export declare const stateFileQuotaLimitSchema: z.ZodObject<{
22
+ kind: z.ZodString;
23
+ label: z.ZodString;
24
+ utilization: z.ZodNumber;
25
+ resetsAt: z.ZodString;
19
26
  }, z.core.$strip>;
20
27
  export declare const stateFileQuotaSchema: z.ZodObject<{
21
28
  fiveHour: z.ZodObject<{
22
29
  utilization: z.ZodNumber;
23
30
  resetsAt: z.ZodString;
31
+ absent: z.ZodOptional<z.ZodBoolean>;
24
32
  }, z.core.$strip>;
25
33
  sevenDay: z.ZodObject<{
26
34
  utilization: z.ZodNumber;
27
35
  resetsAt: z.ZodString;
36
+ absent: z.ZodOptional<z.ZodBoolean>;
28
37
  }, z.core.$strip>;
29
38
  source: z.ZodNullable<z.ZodEnum<{
30
39
  cache: "cache";
@@ -44,6 +53,13 @@ export declare const stateFileQuotaSchema: z.ZodObject<{
44
53
  aging: "aging";
45
54
  stale: "stale";
46
55
  }>>;
56
+ planLabel: z.ZodOptional<z.ZodString>;
57
+ extraLimits: z.ZodOptional<z.ZodArray<z.ZodObject<{
58
+ kind: z.ZodString;
59
+ label: z.ZodString;
60
+ utilization: z.ZodNumber;
61
+ resetsAt: z.ZodString;
62
+ }, z.core.$strip>>>;
47
63
  }, z.core.$strip>;
48
64
  export declare const stateFileAccountSchema: z.ZodObject<{
49
65
  providerId: z.ZodEnum<{
@@ -109,10 +125,12 @@ export declare const sidekickStateFileSchema: z.ZodObject<{
109
125
  fiveHour: z.ZodObject<{
110
126
  utilization: z.ZodNumber;
111
127
  resetsAt: z.ZodString;
128
+ absent: z.ZodOptional<z.ZodBoolean>;
112
129
  }, z.core.$strip>;
113
130
  sevenDay: z.ZodObject<{
114
131
  utilization: z.ZodNumber;
115
132
  resetsAt: z.ZodString;
133
+ absent: z.ZodOptional<z.ZodBoolean>;
116
134
  }, z.core.$strip>;
117
135
  source: z.ZodNullable<z.ZodEnum<{
118
136
  cache: "cache";
@@ -132,15 +150,24 @@ export declare const sidekickStateFileSchema: z.ZodObject<{
132
150
  aging: "aging";
133
151
  stale: "stale";
134
152
  }>>;
153
+ planLabel: z.ZodOptional<z.ZodString>;
154
+ extraLimits: z.ZodOptional<z.ZodArray<z.ZodObject<{
155
+ kind: z.ZodString;
156
+ label: z.ZodString;
157
+ utilization: z.ZodNumber;
158
+ resetsAt: z.ZodString;
159
+ }, z.core.$strip>>>;
135
160
  }, z.core.$strip>>;
136
161
  codex: z.ZodNullable<z.ZodObject<{
137
162
  fiveHour: z.ZodObject<{
138
163
  utilization: z.ZodNumber;
139
164
  resetsAt: z.ZodString;
165
+ absent: z.ZodOptional<z.ZodBoolean>;
140
166
  }, z.core.$strip>;
141
167
  sevenDay: z.ZodObject<{
142
168
  utilization: z.ZodNumber;
143
169
  resetsAt: z.ZodString;
170
+ absent: z.ZodOptional<z.ZodBoolean>;
144
171
  }, z.core.$strip>;
145
172
  source: z.ZodNullable<z.ZodEnum<{
146
173
  cache: "cache";
@@ -160,6 +187,13 @@ export declare const sidekickStateFileSchema: z.ZodObject<{
160
187
  aging: "aging";
161
188
  stale: "stale";
162
189
  }>>;
190
+ planLabel: z.ZodOptional<z.ZodString>;
191
+ extraLimits: z.ZodOptional<z.ZodArray<z.ZodObject<{
192
+ kind: z.ZodString;
193
+ label: z.ZodString;
194
+ utilization: z.ZodNumber;
195
+ resetsAt: z.ZodString;
196
+ }, z.core.$strip>>>;
163
197
  }, z.core.$strip>>;
164
198
  }, z.core.$strip>;
165
199
  context: z.ZodNullable<z.ZodObject<{
@@ -9,7 +9,7 @@
9
9
  * @module schemas/stateFile
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.sidekickStateFileSchema = exports.stateFileBillingBlockSchema = exports.stateFileSessionSchema = exports.stateFileContextSchema = exports.stateFileAccountSchema = exports.stateFileQuotaSchema = exports.stateFileQuotaWindowSchema = exports.stateFileWriterSchema = void 0;
12
+ exports.sidekickStateFileSchema = exports.stateFileBillingBlockSchema = exports.stateFileSessionSchema = exports.stateFileContextSchema = exports.stateFileAccountSchema = exports.stateFileQuotaSchema = exports.stateFileQuotaLimitSchema = exports.stateFileQuotaWindowSchema = exports.stateFileWriterSchema = void 0;
13
13
  const zod_1 = require("zod");
14
14
  exports.stateFileWriterSchema = zod_1.z.enum([
15
15
  'statusline',
@@ -19,6 +19,13 @@ exports.stateFileWriterSchema = zod_1.z.enum([
19
19
  exports.stateFileQuotaWindowSchema = zod_1.z.object({
20
20
  utilization: zod_1.z.number(),
21
21
  resetsAt: zod_1.z.string(),
22
+ absent: zod_1.z.boolean().optional(),
23
+ });
24
+ exports.stateFileQuotaLimitSchema = zod_1.z.object({
25
+ kind: zod_1.z.string(),
26
+ label: zod_1.z.string(),
27
+ utilization: zod_1.z.number(),
28
+ resetsAt: zod_1.z.string(),
22
29
  });
23
30
  exports.stateFileQuotaSchema = zod_1.z.object({
24
31
  fiveHour: exports.stateFileQuotaWindowSchema,
@@ -28,6 +35,8 @@ exports.stateFileQuotaSchema = zod_1.z.object({
28
35
  capturedAt: zod_1.z.string().nullable(),
29
36
  ageMs: zod_1.z.number().nullable(),
30
37
  freshness: zod_1.z.enum(['fresh', 'aging', 'stale']).nullable(),
38
+ planLabel: zod_1.z.string().optional(),
39
+ extraLimits: zod_1.z.array(exports.stateFileQuotaLimitSchema).optional(),
31
40
  });
32
41
  exports.stateFileAccountSchema = zod_1.z.object({
33
42
  providerId: zod_1.z.enum(['claude-code', 'codex']),
@@ -23,6 +23,20 @@ export interface StateFileQuotaWindow {
23
23
  utilization: number;
24
24
  /** ISO timestamp when the window resets; empty when unknown. */
25
25
  resetsAt: string;
26
+ /**
27
+ * Present (true) only when the plan has no such window — e.g. Claude Ultra
28
+ * has no five-hour limit. `utilization`/`resetsAt` are then 0/''.
29
+ */
30
+ absent?: boolean;
31
+ }
32
+ /** An extra usage meter beyond the two headline windows, e.g. a Fable weekly cap. */
33
+ export interface StateFileQuotaLimit {
34
+ /** Server meter kind, e.g. `weekly_scoped`. */
35
+ kind: string;
36
+ /** Display label, e.g. `7-Day (Fable)`. */
37
+ label: string;
38
+ utilization: number;
39
+ resetsAt: string;
26
40
  }
27
41
  export interface StateFileQuota {
28
42
  fiveHour: StateFileQuotaWindow;
@@ -34,6 +48,10 @@ export interface StateFileQuota {
34
48
  capturedAt: string | null;
35
49
  ageMs: number | null;
36
50
  freshness: 'fresh' | 'aging' | 'stale' | null;
51
+ /** Subscription plan name when known, e.g. `Ultra`. Omitted when unknown. */
52
+ planLabel?: string;
53
+ /** Extra meters (per-model weekly caps and similar). Omitted when there are none. */
54
+ extraLimits?: StateFileQuotaLimit[];
37
55
  }
38
56
  export interface StateFileAccount {
39
57
  providerId: 'claude-code' | 'codex';
package/dist/stateFile.js CHANGED
@@ -131,14 +131,29 @@ function quotaToStateFile(quota, now = Date.now()) {
131
131
  freshness ??= (0, quota_1.classifyQuotaFreshness)(ageMs);
132
132
  }
133
133
  }
134
+ const extraLimits = (0, quota_1.extraQuotaRows)(quota).map((row) => ({
135
+ kind: row.kind,
136
+ label: row.label,
137
+ utilization: row.utilization,
138
+ resetsAt: row.resetsAt,
139
+ }));
134
140
  return {
135
- fiveHour: { utilization: quota.fiveHour.utilization, resetsAt: quota.fiveHour.resetsAt },
136
- sevenDay: { utilization: quota.sevenDay.utilization, resetsAt: quota.sevenDay.resetsAt },
141
+ fiveHour: stateFileWindow(quota.fiveHour),
142
+ sevenDay: stateFileWindow(quota.sevenDay),
137
143
  source: quota.source ?? null,
138
144
  capturedSource: quota.capturedSource ?? null,
139
145
  capturedAt: quota.capturedAt ?? null,
140
146
  ageMs,
141
147
  freshness,
148
+ ...(quota.planLabel ? { planLabel: quota.planLabel } : {}),
149
+ ...(extraLimits.length > 0 ? { extraLimits } : {}),
150
+ };
151
+ }
152
+ function stateFileWindow(window) {
153
+ return {
154
+ utilization: window.utilization,
155
+ resetsAt: window.resetsAt,
156
+ ...(window.absent ? { absent: true } : {}),
142
157
  };
143
158
  }
144
159
  /** Project a billing block onto the state-file shape (totals only). */
@@ -74,9 +74,10 @@ export interface QuotaFromStatuslineOptions {
74
74
  /** Capture time; defaults to now. */
75
75
  now?: Date;
76
76
  /**
77
- * A previously known quota (typically the persisted snapshot). Windows the
78
- * payload omits are carried over from it, since Claude Code drops a window
79
- * from the document once its reset time has passed.
77
+ * A previously known quota (typically the persisted snapshot). A window the
78
+ * payload omits is carried over from it while the fallback window has not
79
+ * reset yet, or when the fallback marks it `absent` (the plan has no such
80
+ * window, e.g. Ultra's missing five-hour limit).
80
81
  */
81
82
  fallback?: QuotaState | null;
82
83
  }
@@ -119,14 +119,26 @@ function parseClaudeStatuslinePayload(text) {
119
119
  raw,
120
120
  };
121
121
  }
122
- function toWindow(live, fallback) {
122
+ /**
123
+ * Claude Code leaves a window out of the document once its reset has passed,
124
+ * and always for a window the plan does not have (Ultra has no five-hour
125
+ * limit). A fallback is carried over only while it can still be current: an
126
+ * `absent` marker from an earlier API read, or a window that has not reset yet.
127
+ * An expired fallback would otherwise be re-saved as a fresh sample forever.
128
+ */
129
+ function toWindow(live, fallback, nowMs) {
123
130
  if (live) {
124
131
  return {
125
132
  utilization: Math.max(0, live.usedPercentage),
126
133
  resetsAt: new Date(live.resetsAt * 1000).toISOString(),
127
134
  };
128
135
  }
129
- return fallback ?? { utilization: 0, resetsAt: '' };
136
+ if (fallback?.absent)
137
+ return { utilization: 0, resetsAt: '', absent: true };
138
+ const fallbackResetMs = fallback ? Date.parse(fallback.resetsAt) : NaN;
139
+ if (fallback && Number.isFinite(fallbackResetMs) && fallbackResetMs > nowMs)
140
+ return fallback;
141
+ return { utilization: 0, resetsAt: '' };
130
142
  }
131
143
  /**
132
144
  * Build a `QuotaState` from the official rate-limit block. Returns `null`
@@ -141,8 +153,8 @@ function quotaFromStatuslinePayload(payload, options = {}) {
141
153
  const now = options.now ?? new Date();
142
154
  const fallback = options.fallback ?? undefined;
143
155
  return {
144
- fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour),
145
- sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay),
156
+ fiveHour: toWindow(limits.fiveHour, fallback?.fiveHour, now.getTime()),
157
+ sevenDay: toWindow(limits.sevenDay, fallback?.sevenDay, now.getTime()),
146
158
  available: true,
147
159
  providerId: 'claude-code',
148
160
  source: 'statusline',
@@ -23,6 +23,11 @@ export declare function selectStatuslineAccount(input: StatuslineInput): Statusl
23
23
  * One-line status. Segments, in order, each omitted when unknown:
24
24
  * `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
25
25
  *
26
+ * Plans without a five-hour window (Claude Ultra) lead with the weekly window
27
+ * instead: `acct:<label> · 7d 17% resets Fri 01:00 · ~2d3h left · …`. Window
28
+ * labels follow the reported window length, so a Codex weekly primary window
29
+ * reads `7d`, not `5h`.
30
+ *
26
31
  * The age segment appears only for cached quota older than five minutes, so
27
32
  * a snapshot from yesterday can no longer read like a live figure.
28
33
  */
@@ -27,6 +27,11 @@ function selectStatuslineAccount(input) {
27
27
  * One-line status. Segments, in order, each omitted when unknown:
28
28
  * `acct:<label> · 5h 42% resets 14:00 · ~1h20m left · 7d 61% · (2h ago) · ctx 37% · $0.42 · cache 92%`
29
29
  *
30
+ * Plans without a five-hour window (Claude Ultra) lead with the weekly window
31
+ * instead: `acct:<label> · 7d 17% resets Fri 01:00 · ~2d3h left · …`. Window
32
+ * labels follow the reported window length, so a Codex weekly primary window
33
+ * reads `7d`, not `5h`.
34
+ *
30
35
  * The age segment appears only for cached quota older than five minutes, so
31
36
  * a snapshot from yesterday can no longer read like a live figure.
32
37
  */
@@ -41,20 +46,35 @@ function formatStatusline(input) {
41
46
  if (!quota?.available) {
42
47
  return joinSegments([`acct:${account}`, 'quota unavailable', ...liveSegments]);
43
48
  }
44
- const utilization = clampPercent(quota.fiveHour.utilization);
45
- const reset = formatReset(quota.fiveHour.resetsAt, now);
46
- const eta = estimateWindowEta(quota, now);
47
- const segments = [`acct:${account}`, `5h ${Math.round(utilization)}% resets ${reset}`];
49
+ const first = describeWindow(quota.fiveHour, '5h', quota_1.FIVE_HOUR_WINDOW_MS);
50
+ const second = describeWindow(quota.sevenDay, '7d', quota_1.SEVEN_DAY_WINDOW_MS);
51
+ // Lead with the first window whenever the plan has it (even idle); fall back
52
+ // to the second window when the first does not exist for this plan.
53
+ const leadWithFirst = (0, quota_1.quotaWindowApplies)(quota.fiveHour) || !(0, quota_1.quotaWindowHasData)(quota.sevenDay);
54
+ const lead = leadWithFirst ? first : second;
55
+ const segments = [`acct:${account}`, formatLeadSegment(lead, now)];
56
+ const eta = estimateWindowEta(lead, quota.capturedAt, now);
48
57
  if (eta != null)
49
58
  segments.push(`~${formatMinutes(eta)} left`);
50
- if (quota.sevenDay.resetsAt && quota.sevenDay.utilization > 0) {
51
- segments.push(`7d ${Math.round(clampPercent(quota.sevenDay.utilization))}%`);
59
+ if (leadWithFirst && (0, quota_1.quotaWindowHasData)(quota.sevenDay) && quota.sevenDay.utilization > 0) {
60
+ segments.push(`${second.label} ${Math.round(clampPercent(quota.sevenDay.utilization))}%`);
52
61
  }
53
62
  if (quota.freshness && quota.freshness !== 'fresh') {
54
63
  segments.push(`(${(0, quota_1.formatQuotaAge)(quota.ageMs)})`);
55
64
  }
56
65
  return joinSegments([...segments, ...liveSegments]);
57
66
  }
67
+ function describeWindow(window, label, defaultMs) {
68
+ const windowMs = typeof window.windowMs === 'number' && Number.isFinite(window.windowMs) && window.windowMs > 0
69
+ ? window.windowMs
70
+ : defaultMs;
71
+ return { label: (0, quota_1.quotaWindowShortLabel)(window.windowMs, label), window, windowMs };
72
+ }
73
+ function formatLeadSegment(lead, now) {
74
+ const percent = `${lead.label} ${Math.round(clampPercent(lead.window.utilization))}%`;
75
+ const reset = formatReset(lead.window.resetsAt, now);
76
+ return reset ? `${percent} resets ${reset}` : percent;
77
+ }
58
78
  function formatLiveSegments(live) {
59
79
  if (!live)
60
80
  return [];
@@ -80,22 +100,25 @@ function formatCost(value) {
80
100
  return value.toFixed(1);
81
101
  return value.toFixed(2);
82
102
  }
83
- function estimateWindowEta(quota, now) {
84
- const resetAt = Date.parse(quota.fiveHour.resetsAt);
103
+ function estimateWindowEta(lead, capturedAtIso, now) {
104
+ const { window, windowMs } = lead;
105
+ if (window.absent)
106
+ return null;
107
+ const resetAt = Date.parse(window.resetsAt);
85
108
  if (!Number.isFinite(resetAt))
86
109
  return null;
87
110
  const nowMs = now.getTime();
88
111
  if (resetAt <= nowMs)
89
112
  return null;
90
- const capturedAt = quota.capturedAt ? Date.parse(quota.capturedAt) : now.getTime();
113
+ const capturedAt = capturedAtIso ? Date.parse(capturedAtIso) : now.getTime();
91
114
  const sampleAt = Number.isFinite(capturedAt) ? capturedAt : now.getTime();
92
115
  if (sampleAt >= resetAt)
93
116
  return null;
94
- const elapsedMinutes = (sampleAt - (resetAt - quota_1.FIVE_HOUR_WINDOW_MS)) / 60_000;
95
- if (elapsedMinutes <= 0 || quota.fiveHour.utilization <= 0)
117
+ const elapsedMinutes = (sampleAt - (resetAt - windowMs)) / 60_000;
118
+ if (elapsedMinutes <= 0 || window.utilization <= 0)
96
119
  return null;
97
- const burnRate = quota.fiveHour.utilization / elapsedMinutes;
98
- const eta = (0, BurnRateCalculator_1.estimateTimeToQuota)(quota.fiveHour.utilization, 100, burnRate);
120
+ const burnRate = window.utilization / elapsedMinutes;
121
+ const eta = (0, BurnRateCalculator_1.estimateTimeToQuota)(window.utilization, 100, burnRate);
99
122
  const minutesUntilReset = (resetAt - nowMs) / 60_000;
100
123
  return eta !== null && eta < minutesUntilReset ? eta : null;
101
124
  }
@@ -107,20 +130,36 @@ function compactLabel(value) {
107
130
  function clampPercent(value) {
108
131
  return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;
109
132
  }
110
- function formatReset(value, fallback) {
133
+ /**
134
+ * Reset time as `HH:MM`, or `Fri 01:00` when it is more than a day away.
135
+ * Returns '' for an empty or unparseable value — never the current time.
136
+ */
137
+ function formatReset(value, now) {
138
+ if (!value)
139
+ return '';
111
140
  const reset = new Date(value);
112
- const date = Number.isNaN(reset.getTime()) ? fallback : reset;
113
- return new Intl.DateTimeFormat(undefined, {
141
+ if (Number.isNaN(reset.getTime()))
142
+ return '';
143
+ const time = new Intl.DateTimeFormat(undefined, {
114
144
  hour: '2-digit',
115
145
  minute: '2-digit',
116
146
  hour12: false,
117
- }).format(date);
147
+ }).format(reset);
148
+ if (reset.getTime() - now.getTime() <= 86_400_000)
149
+ return time;
150
+ const weekday = new Intl.DateTimeFormat(undefined, { weekday: 'short' }).format(reset);
151
+ return `${weekday} ${time}`;
118
152
  }
119
153
  function formatMinutes(value) {
120
154
  const minutes = Math.max(0, Math.round(value));
121
155
  if (minutes < 60)
122
156
  return `${minutes}min`;
123
157
  const hours = Math.floor(minutes / 60);
158
+ if (hours >= 48) {
159
+ const days = Math.floor(hours / 24);
160
+ const remainderHours = hours % 24;
161
+ return remainderHours ? `${days}d${remainderHours}h` : `${days}d`;
162
+ }
124
163
  const remainder = minutes % 60;
125
164
  return remainder ? `${hours}h${remainder}m` : `${hours}h`;
126
165
  }
@@ -2,14 +2,14 @@ export { getActiveAccountStatus } from '../accountStatus';
2
2
  export type { ActiveAccountStatus, ActiveAccountStatusOptions, ActiveProviderAccountStatus, } from '../accountStatus';
3
3
  export { readQuotaSnapshot, writeQuotaSnapshot } from '../quotaSnapshots';
4
4
  export type { QuotaSnapshotProviderId } from '../quotaSnapshots';
5
- export { appendQuotaHistorySample, getWorkspaceIdFromPath } from '../quotaHistory';
5
+ export { appendQuotaHistorySample, getWorkspaceIdFromPath, quotaHistoryWindow, } from '../quotaHistory';
6
6
  export type { QuotaHistorySample } from '../quotaHistory';
7
- export { classifyQuotaFreshness, formatQuotaAge } from '../quota';
8
- export type { QuotaFreshness, QuotaState } from '../quota';
7
+ export { classifyQuotaFreshness, formatQuotaAge, quotaWindowApplies, quotaWindowHasData, quotaWindowShortLabel, } from '../quota';
8
+ export type { QuotaFreshness, QuotaState, QuotaWindow } from '../quota';
9
9
  export { BurnRateCalculator, estimateTimeToQuota } from './BurnRateCalculator';
10
10
  export { formatStatusline, selectStatuslineAccount } from './formatter';
11
11
  export type { StatuslineInput, StatuslineSelection } from './formatter';
12
12
  export { parseClaudeStatuslinePayload, quotaFromStatuslinePayload, } from './claudeStatuslinePayload';
13
13
  export type { ClaudeStatuslinePayload, ClaudeStatuslineRateLimitWindow, QuotaFromStatuslineOptions, } from './claudeStatuslinePayload';
14
14
  export { STATE_FILE_NAME, STATE_FILE_SCHEMA_VERSION, billingBlockToStateFile, getStateFilePath, quotaToStateFile, readStateFile, writeStateFile, } from '../stateFile';
15
- export type { SidekickStateFile, SidekickStateInput, StateFileAccount, StateFileBillingBlock, StateFileContext, StateFileQuota, StateFileQuotaWindow, StateFileSession, StateFileWriter, WriteStateFileOptions, } from '../stateFile';
15
+ export type { SidekickStateFile, SidekickStateInput, StateFileAccount, StateFileBillingBlock, StateFileContext, StateFileQuota, StateFileQuotaLimit, StateFileQuotaWindow, StateFileSession, StateFileWriter, WriteStateFileOptions, } from '../stateFile';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.formatQuotaAge = exports.classifyQuotaFreshness = exports.getWorkspaceIdFromPath = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
3
+ exports.writeStateFile = exports.readStateFile = exports.quotaToStateFile = exports.getStateFilePath = exports.billingBlockToStateFile = exports.STATE_FILE_SCHEMA_VERSION = exports.STATE_FILE_NAME = exports.quotaFromStatuslinePayload = exports.parseClaudeStatuslinePayload = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.quotaWindowShortLabel = exports.quotaWindowHasData = exports.quotaWindowApplies = exports.formatQuotaAge = exports.classifyQuotaFreshness = exports.quotaHistoryWindow = exports.getWorkspaceIdFromPath = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
4
4
  var accountStatus_1 = require("../accountStatus");
5
5
  Object.defineProperty(exports, "getActiveAccountStatus", { enumerable: true, get: function () { return accountStatus_1.getActiveAccountStatus; } });
6
6
  var quotaSnapshots_1 = require("../quotaSnapshots");
@@ -9,9 +9,13 @@ Object.defineProperty(exports, "writeQuotaSnapshot", { enumerable: true, get: fu
9
9
  var quotaHistory_1 = require("../quotaHistory");
10
10
  Object.defineProperty(exports, "appendQuotaHistorySample", { enumerable: true, get: function () { return quotaHistory_1.appendQuotaHistorySample; } });
11
11
  Object.defineProperty(exports, "getWorkspaceIdFromPath", { enumerable: true, get: function () { return quotaHistory_1.getWorkspaceIdFromPath; } });
12
+ Object.defineProperty(exports, "quotaHistoryWindow", { enumerable: true, get: function () { return quotaHistory_1.quotaHistoryWindow; } });
12
13
  var quota_1 = require("../quota");
13
14
  Object.defineProperty(exports, "classifyQuotaFreshness", { enumerable: true, get: function () { return quota_1.classifyQuotaFreshness; } });
14
15
  Object.defineProperty(exports, "formatQuotaAge", { enumerable: true, get: function () { return quota_1.formatQuotaAge; } });
16
+ Object.defineProperty(exports, "quotaWindowApplies", { enumerable: true, get: function () { return quota_1.quotaWindowApplies; } });
17
+ Object.defineProperty(exports, "quotaWindowHasData", { enumerable: true, get: function () { return quota_1.quotaWindowHasData; } });
18
+ Object.defineProperty(exports, "quotaWindowShortLabel", { enumerable: true, get: function () { return quota_1.quotaWindowShortLabel; } });
15
19
  var BurnRateCalculator_1 = require("./BurnRateCalculator");
16
20
  Object.defineProperty(exports, "BurnRateCalculator", { enumerable: true, get: function () { return BurnRateCalculator_1.BurnRateCalculator; } });
17
21
  Object.defineProperty(exports, "estimateTimeToQuota", { enumerable: true, get: function () { return BurnRateCalculator_1.estimateTimeToQuota; } });
@@ -267,7 +267,9 @@ async function resolveZaiQuota(options = {}) {
267
267
  return enrichZaiQuota(apiQuota);
268
268
  }
269
269
  const cached = readSnapshot('zai', accountId);
270
- if (cached) {
270
+ // Every window of the cached sample has reset since it was taken: it would
271
+ // only show old percentages with "resets now", so report the API failure.
272
+ if (cached && !(0, quota_1.quotaSampleExpired)(cached)) {
271
273
  return enrichZaiQuota({
272
274
  ...cached,
273
275
  providerId: 'zai',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidekick-shared",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
4
4
  "description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
5
5
  "keywords": [
6
6
  "claude-code",