claude-use 5.4.0 → 5.5.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/README.md CHANGED
@@ -106,8 +106,8 @@ claude-use doctor [--json]
106
106
  claude-use headroom status [--json]
107
107
  claude-use codex status [--json]
108
108
  claude-use frontdoor status [--json]
109
- claude-use usage [--identity <name>] [--provider <name>] [--since <duration>] [--json]
110
- claude-use account show [<identity>] [--json]
109
+ claude-use usage [--identity <name>] [--provider <name>] [--since <duration>] [--refresh] [--json]
110
+ claude-use account show [<identity>] [--refresh] [--json]
111
111
  claude-use completion <bash|zsh|fish>
112
112
  claude-use shim enable [--dir <path>] [--force]
113
113
  claude-use shim disable [--dir <path>] [--force]
package/dist/cli.cjs CHANGED
@@ -273458,7 +273458,7 @@ function resolveLaunchIdentity(paths, cwd) {
273458
273458
  // package.json
273459
273459
  var package_default = {
273460
273460
  name: "claude-use",
273461
- version: "5.4.0",
273461
+ version: "5.5.0",
273462
273462
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
273463
273463
  license: "Apache-2.0",
273464
273464
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -277476,6 +277476,29 @@ var LimitEventSchema = LimitClassificationSchema.extend({
277476
277476
  observedAt: InstantSchema,
277477
277477
  status: external_exports.number().int()
277478
277478
  });
277479
+ var ProviderQuotaWindowSchema = external_exports.strictObject({
277480
+ /** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
277481
+ measures: external_exports.string(),
277482
+ /** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
277483
+ periodMs: external_exports.number().int().positive().optional(),
277484
+ /** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
277485
+ period: external_exports.string().optional(),
277486
+ /** The fraction of the window used (0 to 1). */
277487
+ utilization: external_exports.number().nonnegative(),
277488
+ resetsAt: InstantSchema.optional(),
277489
+ /** Absolute counts, when the provider reports them for this window. */
277490
+ limit: external_exports.number().nonnegative().optional(),
277491
+ used: external_exports.number().nonnegative().optional(),
277492
+ remaining: external_exports.number().nonnegative().optional()
277493
+ });
277494
+ var ProviderQuotaSchema = external_exports.strictObject({
277495
+ observedAt: InstantSchema,
277496
+ /** Which usage endpoint produced it (`z.ai`). */
277497
+ source: external_exports.string(),
277498
+ /** The subscription level the endpoint reports, when it does. */
277499
+ level: external_exports.string().optional(),
277500
+ windows: external_exports.array(ProviderQuotaWindowSchema)
277501
+ });
277479
277502
  var ProviderUsageStateSchema = external_exports.strictObject({
277480
277503
  lastRequestAt: InstantSchema,
277481
277504
  lastStatus: external_exports.number().int(),
@@ -277483,7 +277506,9 @@ var ProviderUsageStateSchema = external_exports.strictObject({
277483
277506
  /** The latest rate-limit state, from the last response that carried any rate-limit header. */
277484
277507
  rateLimit: RateLimitStateSchema.optional(),
277485
277508
  /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
277486
- lastLimit: LimitEventSchema.optional()
277509
+ lastLimit: LimitEventSchema.optional(),
277510
+ /** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
277511
+ quota: ProviderQuotaSchema.optional()
277487
277512
  });
277488
277513
  var AccountMetadataSchema = external_exports.strictObject({
277489
277514
  accountUuid: external_exports.string().optional(),
@@ -278095,8 +278120,264 @@ function createUsageMiddleware(deps) {
278095
278120
  };
278096
278121
  }
278097
278122
 
278098
- // src/usage/store.ts
278099
- var import_node_path27 = __toESM(require("node:path"), 1);
278123
+ // src/usage/providerQuota.ts
278124
+ var ProviderQuotaError = class extends Error {
278125
+ constructor(source, detail) {
278126
+ super(`${source} usage endpoint: ${detail}`);
278127
+ this.name = "ProviderQuotaError";
278128
+ }
278129
+ };
278130
+ var MS_PER_SECOND4 = 1e3;
278131
+ var SECONDS_PER_HOUR = 3600;
278132
+ var HOURS_PER_DAY = 24;
278133
+ var DAYS_PER_MONTH = 30;
278134
+ var PERCENT = 100;
278135
+ var HTTP_OK = 200;
278136
+ var ZAI_SOURCE = "z.ai";
278137
+ var ZAI_HOST = "api.z.ai";
278138
+ var ZAI_QUOTA_URL = `https://${ZAI_HOST}/api/monitor/usage/quota/limit`;
278139
+ var ZAI_UNIT_HOURS = 3;
278140
+ var ZAI_UNIT_MONTHS = 5;
278141
+ var ZAI_UNIT_MS = /* @__PURE__ */ new Map([
278142
+ [ZAI_UNIT_HOURS, SECONDS_PER_HOUR * MS_PER_SECOND4],
278143
+ [ZAI_UNIT_MONTHS, DAYS_PER_MONTH * HOURS_PER_DAY * SECONDS_PER_HOUR * MS_PER_SECOND4]
278144
+ ]);
278145
+ var ZAI_MEASURES = { TOKENS_LIMIT: "tokens", TIME_LIMIT: "tool-calls" };
278146
+ var ZaiLimitSchema = external_exports.object({
278147
+ type: external_exports.string(),
278148
+ unit: external_exports.number().int(),
278149
+ number: external_exports.number().int().positive(),
278150
+ percentage: external_exports.number().nonnegative(),
278151
+ nextResetTime: external_exports.number().positive().optional(),
278152
+ usage: external_exports.number().nonnegative().optional(),
278153
+ currentValue: external_exports.number().nonnegative().optional(),
278154
+ remaining: external_exports.number().nonnegative().optional()
278155
+ });
278156
+ var ZaiResponseSchema = external_exports.object({
278157
+ success: external_exports.boolean(),
278158
+ msg: external_exports.string().optional(),
278159
+ data: external_exports.object({ level: external_exports.string().optional(), limits: external_exports.array(ZaiLimitSchema) })
278160
+ });
278161
+ function zaiWindow(limit) {
278162
+ const unitMs = ZAI_UNIT_MS.get(limit.unit);
278163
+ return {
278164
+ measures: ZAI_MEASURES[limit.type] ?? limit.type.toLowerCase(),
278165
+ ...unitMs === void 0 ? { period: `unit ${String(limit.unit)} x ${String(limit.number)}` } : { periodMs: unitMs * limit.number },
278166
+ utilization: limit.percentage / PERCENT,
278167
+ ...limit.nextResetTime === void 0 ? {} : { resetsAt: new Date(limit.nextResetTime).toISOString() },
278168
+ ...limit.usage === void 0 ? {} : { limit: limit.usage },
278169
+ ...limit.currentValue === void 0 ? {} : { used: limit.currentValue },
278170
+ ...limit.remaining === void 0 ? {} : { remaining: limit.remaining }
278171
+ };
278172
+ }
278173
+ var zaiQuotaAdapter = {
278174
+ source: ZAI_SOURCE,
278175
+ matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === ZAI_HOST,
278176
+ fetch: async (apiKey, http5, signal, nowMs) => {
278177
+ const response = await http5(ZAI_QUOTA_URL, { authorization: apiKey, accept: "application/json" }, signal);
278178
+ if (response.status !== HTTP_OK) {
278179
+ throw new ProviderQuotaError(ZAI_SOURCE, `answered HTTP ${String(response.status)}`);
278180
+ }
278181
+ let body;
278182
+ try {
278183
+ body = JSON.parse(response.text);
278184
+ } catch {
278185
+ throw new ProviderQuotaError(ZAI_SOURCE, "answered something that is not JSON");
278186
+ }
278187
+ const parsed = ZaiResponseSchema.safeParse(body);
278188
+ if (!parsed.success) {
278189
+ throw new ProviderQuotaError(ZAI_SOURCE, `answered an unexpected shape: ${parsed.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ")}`);
278190
+ }
278191
+ if (!parsed.data.success) {
278192
+ throw new ProviderQuotaError(ZAI_SOURCE, `reported failure${parsed.data.msg === void 0 ? "" : `: ${parsed.data.msg}`}`);
278193
+ }
278194
+ return ProviderQuotaSchema.parse({
278195
+ observedAt: new Date(nowMs).toISOString(),
278196
+ source: ZAI_SOURCE,
278197
+ ...parsed.data.data.level === void 0 ? {} : { level: parsed.data.data.level },
278198
+ windows: parsed.data.data.limits.map(zaiWindow)
278199
+ });
278200
+ }
278201
+ };
278202
+ var OPENROUTER_SOURCE = "OpenRouter";
278203
+ var OPENROUTER_HOST = "openrouter.ai";
278204
+ var OPENROUTER_KEY_URL = `https://${OPENROUTER_HOST}/api/v1/key`;
278205
+ var OPENROUTER_CREDITS_URL = `https://${OPENROUTER_HOST}/api/v1/credits`;
278206
+ var HTTP_FORBIDDEN = 403;
278207
+ var MS_PER_DAY2 = HOURS_PER_DAY * SECONDS_PER_HOUR * MS_PER_SECOND4;
278208
+ var OpenRouterKeySchema = external_exports.object({
278209
+ data: external_exports.object({
278210
+ limit: external_exports.number().nonnegative().nullable(),
278211
+ limit_remaining: external_exports.number().nullable(),
278212
+ limit_reset: external_exports.string().nullable(),
278213
+ is_free_tier: external_exports.boolean().optional(),
278214
+ free_model_daily_requests: external_exports.object({ used: external_exports.number().nonnegative(), limit: external_exports.number().nonnegative(), remaining: external_exports.number() }).optional()
278215
+ })
278216
+ });
278217
+ var OpenRouterCreditsSchema = external_exports.object({ data: external_exports.object({ total_credits: external_exports.number().nonnegative(), total_usage: external_exports.number().nonnegative() }) });
278218
+ async function openRouterGet(url2, schema, apiKey, http5, signal) {
278219
+ const response = await http5(url2, { authorization: `Bearer ${apiKey}`, accept: "application/json" }, signal);
278220
+ if (response.status === HTTP_FORBIDDEN) {
278221
+ return "forbidden";
278222
+ }
278223
+ if (response.status !== HTTP_OK) {
278224
+ throw new ProviderQuotaError(OPENROUTER_SOURCE, `${new URL(url2).pathname} answered HTTP ${String(response.status)}`);
278225
+ }
278226
+ let body;
278227
+ try {
278228
+ body = JSON.parse(response.text);
278229
+ } catch {
278230
+ throw new ProviderQuotaError(OPENROUTER_SOURCE, `${new URL(url2).pathname} answered something that is not JSON`);
278231
+ }
278232
+ const parsed = schema.safeParse(body);
278233
+ if (!parsed.success) {
278234
+ throw new ProviderQuotaError(OPENROUTER_SOURCE, `${new URL(url2).pathname} answered an unexpected shape: ${parsed.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ")}`);
278235
+ }
278236
+ return parsed.data;
278237
+ }
278238
+ function nextUtcMidnight(nowMs) {
278239
+ return new Date((Math.floor(nowMs / MS_PER_DAY2) + 1) * MS_PER_DAY2).toISOString();
278240
+ }
278241
+ var openRouterQuotaAdapter = {
278242
+ source: OPENROUTER_SOURCE,
278243
+ matches: (baseUrl) => URL.canParse(baseUrl) && new URL(baseUrl).hostname === OPENROUTER_HOST,
278244
+ fetch: async (apiKey, http5, signal, nowMs) => {
278245
+ const key = await openRouterGet(OPENROUTER_KEY_URL, OpenRouterKeySchema, apiKey, http5, signal);
278246
+ if (key === "forbidden") {
278247
+ throw new ProviderQuotaError(OPENROUTER_SOURCE, "/api/v1/key refused the key (HTTP 403)");
278248
+ }
278249
+ const credits = await openRouterGet(OPENROUTER_CREDITS_URL, OpenRouterCreditsSchema, apiKey, http5, signal);
278250
+ const { limit, limit_remaining: limitRemaining, limit_reset: limitReset, free_model_daily_requests: free, is_free_tier: isFreeTier } = key.data;
278251
+ const windows = [];
278252
+ if (limit !== null && limit > 0) {
278253
+ const remaining = Math.max(limitRemaining ?? limit, 0);
278254
+ windows.push({ measures: "key-credits", period: limitReset ?? "no reset", utilization: (limit - remaining) / limit, limit, used: limit - remaining, remaining });
278255
+ }
278256
+ if (credits !== "forbidden" && credits.data.total_credits > 0) {
278257
+ const { total_credits: total, total_usage: used } = credits.data;
278258
+ windows.push({ measures: "account-credits", period: "prepaid", utilization: used / total, limit: total, used, remaining: Math.max(total - used, 0) });
278259
+ }
278260
+ if (free !== void 0 && free.limit > 0) {
278261
+ windows.push({ measures: "free-model-requests", periodMs: MS_PER_DAY2, utilization: free.used / free.limit, resetsAt: nextUtcMidnight(nowMs), limit: free.limit, used: free.used, remaining: Math.max(free.remaining, 0) });
278262
+ }
278263
+ return ProviderQuotaSchema.parse({
278264
+ observedAt: new Date(nowMs).toISOString(),
278265
+ source: OPENROUTER_SOURCE,
278266
+ ...isFreeTier === true ? { level: "free" } : {},
278267
+ windows
278268
+ });
278269
+ }
278270
+ };
278271
+ var PROVIDER_QUOTA_ADAPTERS = [zaiQuotaAdapter, openRouterQuotaAdapter];
278272
+ function adapterFor(baseUrl) {
278273
+ return PROVIDER_QUOTA_ADAPTERS.find((adapter) => adapter.matches(baseUrl));
278274
+ }
278275
+ function quotaFreshnessMs(quota) {
278276
+ const lengths = quota.windows.flatMap((window) => window.periodMs === void 0 ? [] : [window.periodMs]);
278277
+ return lengths.length === 0 ? Number.POSITIVE_INFINITY : Math.min(...lengths) / PERCENT;
278278
+ }
278279
+
278280
+ // src/usage/quotaRefresh.ts
278281
+ var QUOTA_REQUEST_TIMEOUT_MS = 1e4;
278282
+ function describeError2(error62) {
278283
+ return error62 instanceof Error ? error62.message : String(error62);
278284
+ }
278285
+ function createQuotaRefresher(deps) {
278286
+ const inFlight = /* @__PURE__ */ new Map();
278287
+ const failedAt = /* @__PURE__ */ new Map();
278288
+ const fetchNow = async (identity, provider, lookup, adapter) => {
278289
+ const key = `${identity}\0${provider}`;
278290
+ try {
278291
+ const apiKey = lookup.apiKey();
278292
+ const quota = await adapter.fetch(apiKey, deps.http, AbortSignal.timeout(QUOTA_REQUEST_TIMEOUT_MS), deps.now());
278293
+ if (!deps.record(identity, provider, quota)) {
278294
+ return { status: "no-usage" };
278295
+ }
278296
+ failedAt.delete(key);
278297
+ return { status: "refreshed", quota };
278298
+ } catch (error62) {
278299
+ failedAt.set(key, deps.now());
278300
+ const message = describeError2(error62);
278301
+ deps.log(`usage: quota refresh for ${identity} via ${provider} failed: ${message}`);
278302
+ return { status: "failed", message };
278303
+ }
278304
+ };
278305
+ return {
278306
+ refresh: async (identity, provider, options = {}) => {
278307
+ let lookup;
278308
+ try {
278309
+ lookup = deps.lookup(provider);
278310
+ } catch (error62) {
278311
+ const message = describeError2(error62);
278312
+ deps.log(`usage: quota refresh for ${identity} via ${provider} failed: ${message}`);
278313
+ return { status: "failed", message };
278314
+ }
278315
+ const adapter = lookup === void 0 ? void 0 : adapterFor(lookup.baseUrl);
278316
+ if (lookup === void 0 || adapter === void 0) {
278317
+ return { status: "unsupported" };
278318
+ }
278319
+ const key = `${identity}\0${provider}`;
278320
+ const running = inFlight.get(key);
278321
+ if (running !== void 0) {
278322
+ return await running;
278323
+ }
278324
+ if (options.force !== true) {
278325
+ const failed = failedAt.get(key);
278326
+ if (failed !== void 0 && deps.now() - failed < QUOTA_REQUEST_TIMEOUT_MS) {
278327
+ return { status: "fresh" };
278328
+ }
278329
+ let stored;
278330
+ try {
278331
+ stored = deps.readQuota(identity, provider);
278332
+ } catch (error62) {
278333
+ const message = describeError2(error62);
278334
+ deps.log(`usage: quota refresh for ${identity} via ${provider} failed: ${message}`);
278335
+ return { status: "failed", message };
278336
+ }
278337
+ if (stored !== void 0 && deps.now() - Date.parse(stored.observedAt) < quotaFreshnessMs(stored)) {
278338
+ return { status: "fresh" };
278339
+ }
278340
+ }
278341
+ const started = fetchNow(identity, provider, lookup, adapter).finally(() => {
278342
+ inFlight.delete(key);
278343
+ });
278344
+ inFlight.set(key, started);
278345
+ return await started;
278346
+ }
278347
+ };
278348
+ }
278349
+ function createProviderLookup(ports) {
278350
+ return (name) => {
278351
+ const definition = loadProvider(ports.providersDir, name, ports.fs);
278352
+ if (definition === void 0 || isCodexProvider(definition)) {
278353
+ return void 0;
278354
+ }
278355
+ return {
278356
+ baseUrl: definition.baseUrl,
278357
+ apiKey: () => {
278358
+ const resolution = resolveCredential({ credential: definition.credential, env: ports.env, port: ports.credentials, subject: `provider ${name}` });
278359
+ if (!resolution.ok) {
278360
+ throw new Error(resolution.message);
278361
+ }
278362
+ return resolution.credential.token;
278363
+ }
278364
+ };
278365
+ };
278366
+ }
278367
+ function withQuotaRefresh(record2, refresher) {
278368
+ return (usageRecord) => {
278369
+ record2(usageRecord);
278370
+ if (usageRecord.identity !== void 0 && usageRecord.provider !== ANTHROPIC_PROVIDER) {
278371
+ void refresher.refresh(usageRecord.identity, usageRecord.provider);
278372
+ }
278373
+ };
278374
+ }
278375
+
278376
+ // src/usage/quotaHttp.ts
278377
+ var realQuotaHttpGet = async (url2, headers, signal) => {
278378
+ const response = await fetch(url2, { method: "GET", headers: { ...headers }, signal });
278379
+ return { status: response.status, text: await response.text() };
278380
+ };
278100
278381
 
278101
278382
  // src/usage/read.ts
278102
278383
  var import_node_path26 = __toESM(require("node:path"), 1);
@@ -278251,7 +278532,30 @@ function summariseUsage(records) {
278251
278532
  return summaries.sort((left, right) => (left.identity ?? "").localeCompare(right.identity ?? "") || left.provider.localeCompare(right.provider));
278252
278533
  }
278253
278534
 
278535
+ // src/usage/realQuotaRefresher.ts
278536
+ function createRealQuotaRefresher(params) {
278537
+ const { paths, store, log } = params;
278538
+ return createQuotaRefresher({
278539
+ lookup: createProviderLookup({ fs: realFsPort, providersDir: paths.providersDir, env: process.env, credentials: realCredentialPort }),
278540
+ http: realQuotaHttpGet,
278541
+ readQuota: (identity, provider) => {
278542
+ try {
278543
+ return readUsageSnapshot(realFarmFs, paths.usageSnapshotsDir, identity)?.providers[provider]?.quota;
278544
+ } catch (error62) {
278545
+ if (error62 instanceof UsageSnapshotError) {
278546
+ return void 0;
278547
+ }
278548
+ throw error62;
278549
+ }
278550
+ },
278551
+ record: store.recordQuota,
278552
+ now: () => Date.now(),
278553
+ log
278554
+ });
278555
+ }
278556
+
278254
278557
  // src/usage/store.ts
278558
+ var import_node_path27 = __toESM(require("node:path"), 1);
278255
278559
  var DAY_MS2 = 864e5;
278256
278560
  var DAYS_IN_WEEK = 7;
278257
278561
  var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
@@ -278283,16 +278587,17 @@ function foldProviderState(current, record2) {
278283
278587
  lastStatus: newest ? record2.status : current.lastStatus,
278284
278588
  ...lastModel === void 0 ? {} : { lastModel },
278285
278589
  ...rateLimit === void 0 ? {} : { rateLimit },
278286
- ...lastLimit === void 0 ? {} : { lastLimit }
278590
+ ...lastLimit === void 0 ? {} : { lastLimit },
278591
+ ...current?.quota === void 0 ? {} : { quota: current.quota }
278287
278592
  };
278288
278593
  }
278289
278594
  function createUsageStore(deps) {
278290
278595
  const { fs: fs16, paths } = deps;
278291
278596
  let prunedDay;
278292
- const updateSnapshot = (record2, identity) => {
278597
+ const rewriteSnapshot = (identity, fold) => {
278293
278598
  if (!isIdentityName(identity)) {
278294
278599
  deps.log(`usage: no snapshot for identity "${identity}", which is not a valid identity name`);
278295
- return;
278600
+ return false;
278296
278601
  }
278297
278602
  let current;
278298
278603
  try {
@@ -278303,6 +278608,10 @@ function createUsageStore(deps) {
278303
278608
  }
278304
278609
  deps.log(`usage: replacing an unreadable snapshot: ${error62.message}`);
278305
278610
  }
278611
+ const providers = fold(current);
278612
+ if (providers === void 0) {
278613
+ return false;
278614
+ }
278306
278615
  let account;
278307
278616
  try {
278308
278617
  account = deps.readAccount(identity);
@@ -278317,11 +278626,12 @@ function createUsageStore(deps) {
278317
278626
  identity,
278318
278627
  updatedAt: new Date(deps.now()).toISOString(),
278319
278628
  ...account === void 0 ? {} : { account },
278320
- providers: { ...current?.providers, [record2.provider]: foldProviderState(current?.providers[record2.provider], record2) }
278629
+ providers
278321
278630
  });
278322
278631
  fs16.mkdirPrivate(paths.usageSnapshotsDir);
278323
278632
  fs16.writeFilePrivate(snapshotPath(paths.usageSnapshotsDir, identity), `${JSON.stringify(snapshot, null, 2)}
278324
278633
  `);
278634
+ return true;
278325
278635
  };
278326
278636
  return {
278327
278637
  record: (input2) => {
@@ -278337,9 +278647,17 @@ function createUsageStore(deps) {
278337
278647
  fs16.appendFilePrivate(import_node_path27.default.join(paths.usageLogDir, segmentName(day, deps.pid)), `${JSON.stringify(record2)}
278338
278648
  `);
278339
278649
  if (record2.identity !== void 0) {
278340
- updateSnapshot(record2, record2.identity);
278650
+ rewriteSnapshot(record2.identity, (current) => ({ ...current?.providers, [record2.provider]: foldProviderState(current?.providers[record2.provider], record2) }));
278341
278651
  }
278342
- }
278652
+ },
278653
+ recordQuota: (identity, provider, quota) => rewriteSnapshot(identity, (current) => {
278654
+ const state = current?.providers[provider];
278655
+ if (current === void 0 || state === void 0) {
278656
+ return void 0;
278657
+ }
278658
+ const keep = state.quota !== void 0 && state.quota.observedAt.localeCompare(quota.observedAt) > 0;
278659
+ return { ...current.providers, [provider]: { ...state, quota: keep ? state.quota : quota } };
278660
+ })
278343
278661
  };
278344
278662
  }
278345
278663
 
@@ -278368,8 +278686,9 @@ function realFrontDoorSupervisorPorts(paths) {
278368
278686
  readAccount: createAccountReader(realFarmFs, paths.identitiesDir),
278369
278687
  log
278370
278688
  });
278689
+ const quotaRefresher = createRealQuotaRefresher({ paths, store: usageStore, log });
278371
278690
  const usageMiddleware = createUsageMiddleware({
278372
- record: usageStore.record,
278691
+ record: withQuotaRefresh(usageStore.record, quotaRefresher),
278373
278692
  defer: (task) => {
278374
278693
  setImmediate(task);
278375
278694
  },
@@ -280113,12 +280432,12 @@ function collectUsageReport(fs16, paths, filters) {
280113
280432
  invalidLines: log.invalidLines
280114
280433
  };
280115
280434
  }
280116
- var PERCENT = 100;
280435
+ var PERCENT2 = 100;
280117
280436
  function formatWindow(label, window) {
280118
280437
  if (window === void 0) {
280119
280438
  return void 0;
280120
280439
  }
280121
- const used = window.utilization === void 0 ? "usage unknown" : `${String(Math.round(window.utilization * PERCENT))}% used`;
280440
+ const used = window.utilization === void 0 ? "usage unknown" : `${String(Math.round(window.utilization * PERCENT2))}% used`;
280122
280441
  return `${label} ${used}${window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`}${window.status === void 0 ? "" : ` (${window.status})`}`;
280123
280442
  }
280124
280443
  function formatRateLimit(state) {
@@ -280136,12 +280455,37 @@ function formatLimit(limit) {
280136
280455
  ];
280137
280456
  return `last limit: ${limit.kind} (${String(limit.status)}) at ${limit.observedAt}${limit.window === void 0 ? "" : ` on the ${limit.window} window`}${wait.length === 0 ? "" : `, ${wait.join(", ")}`}`;
280138
280457
  }
280458
+ var MONEY_DECIMALS = 2;
280459
+ var MS_PER_HOUR = 36e5;
280460
+ var HOURS_PER_DAY2 = 24;
280461
+ function formatPeriod(window) {
280462
+ if (window.periodMs === void 0) {
280463
+ return window.period ?? "unknown period";
280464
+ }
280465
+ const hours = window.periodMs / MS_PER_HOUR;
280466
+ return hours >= HOURS_PER_DAY2 && Number.isInteger(hours / HOURS_PER_DAY2) ? `${String(hours / HOURS_PER_DAY2)}d` : `${String(hours)}h`;
280467
+ }
280468
+ function formatCount(value) {
280469
+ return String(Number(value.toFixed(MONEY_DECIMALS)));
280470
+ }
280471
+ function formatQuota(quota) {
280472
+ return quota.windows.map((window, index) => {
280473
+ const counts = window.used !== void 0 && window.limit !== void 0 ? `, ${formatCount(window.used)} of ${formatCount(window.limit)}` : "";
280474
+ const resets = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
280475
+ const head = index === 0 ? `quota via ${quota.source}${quota.level === void 0 ? "" : ` (${quota.level})`}, observed ${quota.observedAt}: ` : " ";
280476
+ return ` ${head}${window.measures} ${formatPeriod(window)} ${String(Math.round(window.utilization * PERCENT2))}% used${counts}${resets}`;
280477
+ });
280478
+ }
280139
280479
  function snapshotLines(snapshot, provider) {
280140
280480
  const state = snapshot?.providers[provider];
280141
280481
  if (state === void 0) {
280142
280482
  return [];
280143
280483
  }
280144
- return [...state.rateLimit === void 0 ? [] : [` ${formatRateLimit(state.rateLimit)}`], ...state.lastLimit === void 0 ? [] : [` ${formatLimit(state.lastLimit)}`]];
280484
+ return [
280485
+ ...state.rateLimit === void 0 ? [] : [` ${formatRateLimit(state.rateLimit)}`],
280486
+ ...state.quota === void 0 ? [] : formatQuota(state.quota),
280487
+ ...state.lastLimit === void 0 ? [] : [` ${formatLimit(state.lastLimit)}`]
280488
+ ];
280145
280489
  }
280146
280490
  function formatUsageReport(report) {
280147
280491
  const lines = [`usage ${report.since === void 0 ? "over the retained log" : `since ${report.since}`} (${report.logDir})`];
@@ -280214,12 +280558,42 @@ function formatAccounts(views) {
280214
280558
  return lines;
280215
280559
  });
280216
280560
  }
280561
+ async function refreshQuotas(paths, filters) {
280562
+ if (filters.identity !== void 0) {
280563
+ requireIdentity(paths, filters.identity);
280564
+ }
280565
+ const store = createUsageStore({
280566
+ fs: realFarmFs,
280567
+ paths,
280568
+ pid: process.pid,
280569
+ now: () => Date.now(),
280570
+ readAccount: createAccountReader(realFarmFs, paths.identitiesDir),
280571
+ log: (line) => {
280572
+ console.error(line);
280573
+ }
280574
+ });
280575
+ const refresher = createRealQuotaRefresher({ paths, store, log: () => void 0 });
280576
+ const pairs = listUsageSnapshots(realFarmFs, paths.usageSnapshotsDir).filter((snapshot) => filters.identity === void 0 || snapshot.identity === filters.identity).flatMap((snapshot) => Object.keys(snapshot.providers).filter((provider) => provider !== ANTHROPIC_PROVIDER && (filters.provider === void 0 || provider === filters.provider)).map((provider) => ({ identity: snapshot.identity, provider })));
280577
+ const warnings = [];
280578
+ for (const { identity, provider } of pairs) {
280579
+ const outcome = await refresher.refresh(identity, provider, { force: true });
280580
+ if (outcome.status === "failed") {
280581
+ warnings.push(`warning: could not refresh ${identity} via ${provider}: ${outcome.message}`);
280582
+ }
280583
+ }
280584
+ return warnings;
280585
+ }
280217
280586
  function registerUsageCommands(program2, deps) {
280218
280587
  const { paths } = deps;
280219
280588
  withExamples(
280220
280589
  program2.command("usage").description(
280221
280590
  `Report recorded requests, token usage and the latest quota state per identity and provider, from the usage log the front door writes (the last ${String(USAGE_RETENTION_MS / DAY_MS3)} days). Read-only.`
280222
- ).option("--identity <name>", "Only this identity's requests and snapshot.").option("--provider <name>", "Only this provider's requests (anthropic for OAuth sessions).").option("--since <duration>", "Only requests from this long ago on: a count followed by m, h, d or w, such as 5h or 7d.", parseDurationOption).option("--json", "Print the report as JSON.").action((options) => {
280591
+ ).option("--identity <name>", "Only this identity's requests and snapshot.").option("--provider <name>", "Only this provider's requests (anthropic for OAuth sessions).").option("--since <duration>", "Only requests from this long ago on: a count followed by m, h, d or w, such as 5h or 7d.", parseDurationOption).option("--refresh", "First fetch the quota of providers that report it through their own usage endpoint (z.ai), regardless of how fresh the stored one is.").option("--json", "Print the report as JSON.").action(async (options) => {
280592
+ if (options.refresh === true) {
280593
+ for (const warning of await refreshQuotas(paths, { ...options.identity === void 0 ? {} : { identity: options.identity }, ...options.provider === void 0 ? {} : { provider: options.provider } })) {
280594
+ console.error(warning);
280595
+ }
280596
+ }
280223
280597
  const report = collectUsageReport(realFarmFs, paths, {
280224
280598
  ...options.identity === void 0 ? {} : { identity: options.identity },
280225
280599
  ...options.provider === void 0 ? {} : { provider: options.provider },
@@ -280233,13 +280607,18 @@ function registerUsageCommands(program2, deps) {
280233
280607
  console.log(line);
280234
280608
  }
280235
280609
  }),
280236
- ["claude-use usage", "claude-use usage --identity work --since 5h", "claude-use usage --provider z --json"]
280610
+ ["claude-use usage", "claude-use usage --identity work --since 5h", "claude-use usage --provider z --refresh --json"]
280237
280611
  );
280238
280612
  const account = withExamples(program2.command("account").description("Inspect the Claude account behind each identity: plan, tier and latest quota."), [
280239
280613
  "claude-use account show work"
280240
280614
  ]);
280241
280615
  withExamples(
280242
- account.command("show [identity]").description("Show an identity's account metadata (read live from its stored login) and its latest recorded quota; every identity when none is named. Read-only.").option("--json", "Print the accounts as JSON.").action((identity, options) => {
280616
+ account.command("show [identity]").description("Show an identity's account metadata (read live from its stored login) and its latest recorded quota; every identity when none is named. Read-only.").option("--refresh", "First fetch the quota of providers that report it through their own usage endpoint (z.ai), regardless of how fresh the stored one is.").option("--json", "Print the accounts as JSON.").action(async (identity, options) => {
280617
+ if (options.refresh === true) {
280618
+ for (const warning of await refreshQuotas(paths, identity === void 0 ? {} : { identity })) {
280619
+ console.error(warning);
280620
+ }
280621
+ }
280243
280622
  const views = collectAccounts(realFarmFs, paths, identity);
280244
280623
  if (options.json === true) {
280245
280624
  printJson(identity === void 0 ? views : views[0]);
@@ -280550,18 +280929,18 @@ function spawnClaude(params) {
280550
280929
  }
280551
280930
 
280552
280931
  // src/usage/preflight.ts
280553
- var MS_PER_SECOND4 = 1e3;
280932
+ var MS_PER_SECOND5 = 1e3;
280554
280933
  var SECONDS_PER_MINUTE = 60;
280555
280934
  var MINUTES_PER_HOUR = 60;
280556
- var HOURS_PER_DAY = 24;
280935
+ var HOURS_PER_DAY3 = 24;
280557
280936
  var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
280558
280937
  function formatAge(ms) {
280559
- const minutes = Math.floor(ms / (MS_PER_SECOND4 * SECONDS_PER_MINUTE));
280938
+ const minutes = Math.floor(ms / (MS_PER_SECOND5 * SECONDS_PER_MINUTE));
280560
280939
  if (minutes < MINUTES_PER_HOUR) {
280561
280940
  return `${String(Math.max(minutes, 0))}m`;
280562
280941
  }
280563
280942
  const hours = Math.floor(minutes / MINUTES_PER_HOUR);
280564
- return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
280943
+ return hours < HOURS_PER_DAY3 ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY3))}d`;
280565
280944
  }
280566
280945
  function quotaWarnings(snapshot, provider, nowMs) {
280567
280946
  const rateLimit = snapshot?.providers[provider]?.rateLimit;
package/dist/index.cjs CHANGED
@@ -3046,6 +3046,29 @@ var LimitEventSchema = LimitClassificationSchema.extend({
3046
3046
  observedAt: InstantSchema,
3047
3047
  status: import_zod5.z.number().int()
3048
3048
  });
3049
+ var ProviderQuotaWindowSchema = import_zod5.z.strictObject({
3050
+ /** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
3051
+ measures: import_zod5.z.string(),
3052
+ /** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
3053
+ periodMs: import_zod5.z.number().int().positive().optional(),
3054
+ /** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
3055
+ period: import_zod5.z.string().optional(),
3056
+ /** The fraction of the window used (0 to 1). */
3057
+ utilization: import_zod5.z.number().nonnegative(),
3058
+ resetsAt: InstantSchema.optional(),
3059
+ /** Absolute counts, when the provider reports them for this window. */
3060
+ limit: import_zod5.z.number().nonnegative().optional(),
3061
+ used: import_zod5.z.number().nonnegative().optional(),
3062
+ remaining: import_zod5.z.number().nonnegative().optional()
3063
+ });
3064
+ var ProviderQuotaSchema = import_zod5.z.strictObject({
3065
+ observedAt: InstantSchema,
3066
+ /** Which usage endpoint produced it (`z.ai`). */
3067
+ source: import_zod5.z.string(),
3068
+ /** The subscription level the endpoint reports, when it does. */
3069
+ level: import_zod5.z.string().optional(),
3070
+ windows: import_zod5.z.array(ProviderQuotaWindowSchema)
3071
+ });
3049
3072
  var ProviderUsageStateSchema = import_zod5.z.strictObject({
3050
3073
  lastRequestAt: InstantSchema,
3051
3074
  lastStatus: import_zod5.z.number().int(),
@@ -3053,7 +3076,9 @@ var ProviderUsageStateSchema = import_zod5.z.strictObject({
3053
3076
  /** The latest rate-limit state, from the last response that carried any rate-limit header. */
3054
3077
  rateLimit: RateLimitStateSchema.optional(),
3055
3078
  /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
3056
- lastLimit: LimitEventSchema.optional()
3079
+ lastLimit: LimitEventSchema.optional(),
3080
+ /** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
3081
+ quota: ProviderQuotaSchema.optional()
3057
3082
  });
3058
3083
  var AccountMetadataSchema = import_zod5.z.strictObject({
3059
3084
  accountUuid: import_zod5.z.string().optional(),
package/dist/index.mjs CHANGED
@@ -2972,6 +2972,29 @@ var LimitEventSchema = LimitClassificationSchema.extend({
2972
2972
  observedAt: InstantSchema,
2973
2973
  status: z5.number().int()
2974
2974
  });
2975
+ var ProviderQuotaWindowSchema = z5.strictObject({
2976
+ /** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
2977
+ measures: z5.string(),
2978
+ /** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
2979
+ periodMs: z5.number().int().positive().optional(),
2980
+ /** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
2981
+ period: z5.string().optional(),
2982
+ /** The fraction of the window used (0 to 1). */
2983
+ utilization: z5.number().nonnegative(),
2984
+ resetsAt: InstantSchema.optional(),
2985
+ /** Absolute counts, when the provider reports them for this window. */
2986
+ limit: z5.number().nonnegative().optional(),
2987
+ used: z5.number().nonnegative().optional(),
2988
+ remaining: z5.number().nonnegative().optional()
2989
+ });
2990
+ var ProviderQuotaSchema = z5.strictObject({
2991
+ observedAt: InstantSchema,
2992
+ /** Which usage endpoint produced it (`z.ai`). */
2993
+ source: z5.string(),
2994
+ /** The subscription level the endpoint reports, when it does. */
2995
+ level: z5.string().optional(),
2996
+ windows: z5.array(ProviderQuotaWindowSchema)
2997
+ });
2975
2998
  var ProviderUsageStateSchema = z5.strictObject({
2976
2999
  lastRequestAt: InstantSchema,
2977
3000
  lastStatus: z5.number().int(),
@@ -2979,7 +3002,9 @@ var ProviderUsageStateSchema = z5.strictObject({
2979
3002
  /** The latest rate-limit state, from the last response that carried any rate-limit header. */
2980
3003
  rateLimit: RateLimitStateSchema.optional(),
2981
3004
  /** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
2982
- lastLimit: LimitEventSchema.optional()
3005
+ lastLimit: LimitEventSchema.optional(),
3006
+ /** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
3007
+ quota: ProviderQuotaSchema.optional()
2983
3008
  });
2984
3009
  var AccountMetadataSchema = z5.strictObject({
2985
3010
  accountUuid: z5.string().optional(),
@@ -129,6 +129,35 @@ declare const LimitEventSchema: z.ZodObject<{
129
129
  status: z.ZodNumber;
130
130
  }, z.core.$strict>;
131
131
  export type LimitEvent = z.infer<typeof LimitEventSchema>;
132
+ /** One quota window a provider reports through its own usage endpoint (as opposed to headers on a response): how much of it is used and when it resets. */
133
+ declare const ProviderQuotaWindowSchema: z.ZodObject<{
134
+ measures: z.ZodString;
135
+ periodMs: z.ZodOptional<z.ZodNumber>;
136
+ period: z.ZodOptional<z.ZodString>;
137
+ utilization: z.ZodNumber;
138
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
139
+ limit: z.ZodOptional<z.ZodNumber>;
140
+ used: z.ZodOptional<z.ZodNumber>;
141
+ remaining: z.ZodOptional<z.ZodNumber>;
142
+ }, z.core.$strict>;
143
+ export type ProviderQuotaWindow = z.infer<typeof ProviderQuotaWindowSchema>;
144
+ /** A provider's quota as its usage endpoint last reported it. Pulled, not observed on a request, so it carries its own observation time. */
145
+ export declare const ProviderQuotaSchema: z.ZodObject<{
146
+ observedAt: z.ZodISODateTime;
147
+ source: z.ZodString;
148
+ level: z.ZodOptional<z.ZodString>;
149
+ windows: z.ZodArray<z.ZodObject<{
150
+ measures: z.ZodString;
151
+ periodMs: z.ZodOptional<z.ZodNumber>;
152
+ period: z.ZodOptional<z.ZodString>;
153
+ utilization: z.ZodNumber;
154
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
155
+ limit: z.ZodOptional<z.ZodNumber>;
156
+ used: z.ZodOptional<z.ZodNumber>;
157
+ remaining: z.ZodOptional<z.ZodNumber>;
158
+ }, z.core.$strict>>;
159
+ }, z.core.$strict>;
160
+ export type ProviderQuota = z.infer<typeof ProviderQuotaSchema>;
132
161
  /** One provider's latest state under one identity. */
133
162
  declare const ProviderUsageStateSchema: z.ZodObject<{
134
163
  lastRequestAt: z.ZodISODateTime;
@@ -166,6 +195,21 @@ declare const ProviderUsageStateSchema: z.ZodObject<{
166
195
  observedAt: z.ZodISODateTime;
167
196
  status: z.ZodNumber;
168
197
  }, z.core.$strict>>;
198
+ quota: z.ZodOptional<z.ZodObject<{
199
+ observedAt: z.ZodISODateTime;
200
+ source: z.ZodString;
201
+ level: z.ZodOptional<z.ZodString>;
202
+ windows: z.ZodArray<z.ZodObject<{
203
+ measures: z.ZodString;
204
+ periodMs: z.ZodOptional<z.ZodNumber>;
205
+ period: z.ZodOptional<z.ZodString>;
206
+ utilization: z.ZodNumber;
207
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
208
+ limit: z.ZodOptional<z.ZodNumber>;
209
+ used: z.ZodOptional<z.ZodNumber>;
210
+ remaining: z.ZodOptional<z.ZodNumber>;
211
+ }, z.core.$strict>>;
212
+ }, z.core.$strict>>;
169
213
  }, z.core.$strict>;
170
214
  export type ProviderUsageState = z.infer<typeof ProviderUsageStateSchema>;
171
215
  /**
@@ -249,6 +293,21 @@ export declare const UsageSnapshotSchema: z.ZodObject<{
249
293
  observedAt: z.ZodISODateTime;
250
294
  status: z.ZodNumber;
251
295
  }, z.core.$strict>>;
296
+ quota: z.ZodOptional<z.ZodObject<{
297
+ observedAt: z.ZodISODateTime;
298
+ source: z.ZodString;
299
+ level: z.ZodOptional<z.ZodString>;
300
+ windows: z.ZodArray<z.ZodObject<{
301
+ measures: z.ZodString;
302
+ periodMs: z.ZodOptional<z.ZodNumber>;
303
+ period: z.ZodOptional<z.ZodString>;
304
+ utilization: z.ZodNumber;
305
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
306
+ limit: z.ZodOptional<z.ZodNumber>;
307
+ used: z.ZodOptional<z.ZodNumber>;
308
+ remaining: z.ZodOptional<z.ZodNumber>;
309
+ }, z.core.$strict>>;
310
+ }, z.core.$strict>>;
252
311
  }, z.core.$strict>>;
253
312
  }, z.core.$strict>;
254
313
  export type UsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-use",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",