@usagefleet/cli 1.2.181 → 1.2.183

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.
@@ -86,10 +86,10 @@ export function agentClaudeLogins(accountsPath) {
86
86
  }
87
87
  return logins;
88
88
  }
89
- /** The accountUuid Anthropic says owns this OAuth token, or null when it cannot
89
+ /** The account Anthropic says owns this OAuth token, or null when it cannot
90
90
  * say. Null fails open to the file's identity: the endpoint is undocumented, and
91
91
  * losing it must not stop every collector from reporting limits. */
92
- export async function tokenOwner(token) {
92
+ export async function tokenAccount(token) {
93
93
  try {
94
94
  const res = await fetch(OAUTH_PROFILE_URL, {
95
95
  headers: {
@@ -103,8 +103,13 @@ export async function tokenOwner(token) {
103
103
  return null;
104
104
  }
105
105
  const body = (await res.json());
106
- const uuid = body?.account?.uuid;
107
- return typeof uuid === 'string' && uuid !== '' ? uuid.slice(0, 100) : null;
106
+ return parseClaudeAccount({
107
+ oauthAccount: {
108
+ accountUuid: body?.account?.uuid,
109
+ emailAddress: body?.account?.email,
110
+ organizationName: body?.organization?.name,
111
+ },
112
+ });
108
113
  }
109
114
  catch {
110
115
  return null;
@@ -198,9 +198,31 @@ export function oauthLimitsReport(body) {
198
198
  if (account.fiveHourPct == null && account.sevenDayPct == null) {
199
199
  return null;
200
200
  }
201
- const report = { ...account, modelLimits: parseOauthUsage(body), source: 'sub' };
201
+ const surfaces = parseSurfaces(body);
202
+ const report = {
203
+ ...account,
204
+ modelLimits: parseOauthUsage(body),
205
+ source: 'sub',
206
+ ...(surfaces.length > 0 && { surfaces }),
207
+ };
202
208
  return account.fiveHourPct == null && listsNoSessionLimit(body) ? { ...report, noFiveHourWindow: true } : report;
203
209
  }
210
+ export function parseSurfaces(body) {
211
+ const rows = body?.seven_day_breakdown?.rows;
212
+ if (!Array.isArray(rows)) {
213
+ return [];
214
+ }
215
+ const out = [];
216
+ for (const row of rows) {
217
+ const pct = typeof row?.percent === 'number' ? clampPct(row.percent) : null;
218
+ if (typeof row?.key === 'string' && row.key !== '' && pct !== null) {
219
+ const label = typeof row.display_name === 'string' && row.display_name !== '' ? row.display_name : row.key;
220
+ // Cut to the server's ceilings: an over-long row would get the whole limits post rejected.
221
+ out.push({ key: row.key.slice(0, 64), label: label.slice(0, 64), pct });
222
+ }
223
+ }
224
+ return out.slice(0, 16);
225
+ }
204
226
  // Ultra plans have no 5h window. Max lists a `kind: "session"` entry in limits[];
205
227
  // a payload without limits[] says nothing either way.
206
228
  function listsNoSessionLimit(body) {
package/dist/collector.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { hostname } from 'node:os';
3
3
  import { dirname, join, sep } from 'node:path';
4
- import { activeJcodeToken, agentClaudeLogins, detectClaudeAccount, piClaudeToken, tokenOwner, } from './claude-account.js';
4
+ import { activeJcodeToken, agentClaudeLogins, detectClaudeAccount, piClaudeToken, tokenAccount, } from './claude-account.js';
5
5
  import { detectClaudeCreds, macKeychainDenied } from './claude-creds.js';
6
6
  import { fetchLimits } from './claude-limits.js';
7
7
  import { ENDPOINT } from './config.js';
@@ -276,7 +276,7 @@ async function jcodeAccount(sessionsDir, owners) {
276
276
  if (cached) {
277
277
  return cached;
278
278
  }
279
- const owner = await tokenOwner(token);
279
+ const owner = (await tokenAccount(token))?.extId;
280
280
  if (!owner) {
281
281
  return undefined;
282
282
  }
@@ -340,11 +340,11 @@ export async function reportLimitsOnce(cfg, log = () => {
340
340
  */
341
341
  async function reportOtherLogins(cfg, logins, reportedExtId, log) {
342
342
  const reported = new Set(reportedExtId ? [reportedExtId] : []);
343
- for (const { extId, label, token } of logins) {
344
- if (reported.has(extId)) {
343
+ for (const { account, label, token } of logins) {
344
+ if (reported.has(account.extId)) {
345
345
  continue;
346
346
  }
347
- reported.add(extId);
347
+ reported.add(account.extId);
348
348
  let report;
349
349
  try {
350
350
  report = await fetchLimits({ source: 'sub', token });
@@ -353,7 +353,7 @@ async function reportOtherLogins(cfg, logins, reportedExtId, log) {
353
353
  log('warn', `limits fetch failed for the ${label} login · ${error.message}`);
354
354
  continue;
355
355
  }
356
- const outcome = await postLimits({ ...report, account: { email: null, extId, org: null } }, cfg);
356
+ const outcome = await postLimits({ ...report, account }, cfg);
357
357
  if (outcome !== 'ok') {
358
358
  log('warn', `limits upload failed for the ${label} login · ${outcome}`);
359
359
  }
@@ -367,22 +367,25 @@ async function otherLogins(cfg) {
367
367
  continue;
368
368
  }
369
369
  const token = activeJcodeToken(join(dirname(sessionsDir), 'auth.json'));
370
- const extId = token ? await tokenOwner(token) : null;
371
- if (token && extId) {
372
- logins.push({ extId, label: tilde(dirname(sessionsDir)), token });
370
+ const account = token ? await tokenAccount(token) : null;
371
+ if (token && account) {
372
+ logins.push({ account, label: tilde(dirname(sessionsDir)), token });
373
373
  }
374
374
  }
375
375
  for (const piDir of new Set(cfg.piDirs.map(dir => dirname(dir)))) {
376
376
  const token = piClaudeToken(join(piDir, 'auth.json'));
377
- const extId = token ? await tokenOwner(token) : null;
378
- if (token && extId) {
379
- logins.push({ extId, label: tilde(piDir), token });
377
+ const account = token ? await tokenAccount(token) : null;
378
+ if (token && account) {
379
+ logins.push({ account, label: tilde(piDir), token });
380
380
  }
381
381
  }
382
382
  if (cfg.agentDir) {
383
383
  const accountsPath = join(dirname(cfg.agentDir), 'accounts.json');
384
- for (const login of agentClaudeLogins(accountsPath)) {
385
- logins.push({ ...login, label: `${tilde(dirname(cfg.agentDir))} ${login.name}` });
384
+ for (const { extId, name, token } of agentClaudeLogins(accountsPath)) {
385
+ // accounts.json has no email; the profile adds it when it agrees on the account.
386
+ const profile = await tokenAccount(token);
387
+ const account = profile?.extId === extId ? profile : { email: null, extId, org: null };
388
+ logins.push({ account, label: `${tilde(dirname(cfg.agentDir))} ${name}`, token });
386
389
  }
387
390
  }
388
391
  return logins;
@@ -429,7 +432,7 @@ async function reportClaudeCodeLimits(cfg, hasOtherLogin, log) {
429
432
  const account = report.source === 'sub' ? detectClaudeAccount() : null;
430
433
  // Mid-/login the token and ~/.claude.json name two accounts; see docs/collector.md.
431
434
  // ponytail: one profile call per limits cycle, cache it per token if Anthropic starts 429ing.
432
- const owner = account ? await tokenOwner(creds.token) : null;
435
+ const owner = account ? ((await tokenAccount(creds.token))?.extId ?? null) : null;
433
436
  if (account && owner !== null && owner !== account.extId) {
434
437
  log('warn', 'limits skipped · claude login is mid-switch between accounts · retrying next cycle');
435
438
  return null;
package/dist/release.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by .github/workflows/release.yml.
2
- export const RELEASE_VERSION = "1.2.181";
2
+ export const RELEASE_VERSION = "1.2.183";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usagefleet/cli",
3
- "version": "1.2.181",
3
+ "version": "1.2.183",
4
4
  "description": "Tails Claude Code, Claude Desktop, and pi agent JSONL logs and reports token usage to a UsageFleet server.",
5
5
  "keywords": [
6
6
  "claude",