@usagefleet/cli 1.2.129 → 1.2.131

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
@@ -21,6 +21,25 @@ first report, not at login), sets the collector to start with your session and
21
21
  writes `~/.config/usagefleet/config.json` (mode `600`). The dashboard fills in
22
22
  within a minute.
23
23
 
24
+ ### One machine, several Claude accounts
25
+
26
+ If you switch this machine between Claude accounts that belong to different
27
+ fleets (a work subscription and a personal one), run `login` once per account,
28
+ each time while signed into that account in Claude Code:
29
+
30
+ ```bash
31
+ claude /login # sign into the work account
32
+ usagefleet login uf_work # token from the work fleet
33
+ claude /login # sign into the personal account
34
+ usagefleet login uf_personal # token from your own fleet
35
+ ```
36
+
37
+ `login` binds its token to the Claude account signed in at that moment, and
38
+ the collector reports each account's usage and limits with its own token. An
39
+ account without a binding uses the last token you logged in with. Without this,
40
+ whatever you do on the personal account lands in the work fleet and vanishes
41
+ from yours. `usagefleet status` shows which token the current account uses.
42
+
24
43
  `login` takes the token and nothing else. The collector reports to
25
44
  `usagefleet.com` and there is no way to redirect it: the request carries your
26
45
  device token and a log of what this machine is working on.
package/dist/config.js CHANGED
@@ -21,6 +21,9 @@ export function loadConfig() {
21
21
  const parsedBatch = Math.floor(positiveNumber(process.env.USAGEFLEET_BATCH, file.batch) ?? 100);
22
22
  const batchSize = Number.isFinite(parsedBatch) && parsedBatch > 0 ? Math.min(parsedBatch, MAX_BATCH) : 100;
23
23
  return {
24
+ // Kept even when the env names a token: the service unit bakes the default
25
+ // one into USAGEFLEET_TOKEN, and bindings are what route around it.
26
+ accountTokens: file.accountTokens ?? {},
24
27
  batchSize,
25
28
  desktopDir: resolveOptionalDir(process.env.USAGEFLEET_DESKTOP, file.desktopDir, defaultDesktopSessionsDir()),
26
29
  piDirs: resolvePiDirs(process.env.USAGEFLEET_PI, file.piDir),
@@ -29,6 +32,12 @@ export function loadConfig() {
29
32
  token,
30
33
  };
31
34
  }
35
+ /** The token that reports what `extId` (a Claude accountUuid) metered: the one
36
+ * `login` bound to that account, else the default. Without this a machine that
37
+ * switches Claude accounts files one account's usage in the other's fleet. */
38
+ export function tokenFor(cfg, extId) {
39
+ return (extId && cfg.accountTokens[extId]) || cfg.token;
40
+ }
32
41
  /** env → file → nothing, for the numeric knobs (intervals, batch): an env
33
42
  * value wins when present (empty string counts as unset, same `||` rule as
34
43
  * loadConfig), and a non-positive or non-numeric candidate is skipped rather
package/dist/guard.js CHANGED
@@ -1,4 +1,5 @@
1
- import { ENDPOINT, loadConfig } from './config.js';
1
+ import { detectClaudeAccount } from './claude-account.js';
2
+ import { ENDPOINT, loadConfig, tokenFor } from './config.js';
2
3
  /** Hooks run on the interactive path — a slow/hung server must not stall a
3
4
  * prompt for long. On timeout we fail open (see {@link runGuard}). */
4
5
  const TIMEOUT_MS = 5000;
@@ -39,7 +40,7 @@ export async function runGuard() {
39
40
  let view;
40
41
  try {
41
42
  const res = await fetch(`${ENDPOINT}/api/v1/limits`, {
42
- headers: { 'x-api-key': cfg.token },
43
+ headers: { 'x-api-key': tokenFor(cfg, detectClaudeAccount()?.extId) },
43
44
  signal: AbortSignal.timeout(TIMEOUT_MS),
44
45
  });
45
46
  if (!res.ok) {
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ import { detectClaudeAccount } from './claude-account.js';
2
3
  import { detectClaudeCreds } from './claude-creds.js';
3
4
  import { reportLimitsOnce, runOnce } from './collector.js';
4
5
  import { commands, completionScript, installCompletions, removeCompletions, shells, suggest } from './completion.js';
5
- import { ENDPOINT, loadConfig, positiveNumber } from './config.js';
6
+ import { ENDPOINT, loadConfig, positiveNumber, tokenFor } from './config.js';
6
7
  import { runGuard } from './guard.js';
7
8
  import { loadNotifyConfig } from './notifier.js';
8
9
  import { sendNotification } from './notify.js';
9
10
  import { detectOs } from './os.js';
10
11
  import { RELEASE_VERSION } from './release.js';
11
12
  import { serviceStatus } from './service.js';
12
- import { readStore, storePath } from './store.js';
13
+ import { readStore, storePath, updateStore } from './store.js';
13
14
  import { ago, bar, blue, dim, fail, green, header, hint, host, line, note, pct, row, state as stateLine, step, tilde, warn, yellow, } from './ui.js';
14
15
  import { checkForUpdate } from './update.js';
15
16
  function flag(name) {
@@ -185,7 +186,7 @@ async function cmdStatus() {
185
186
  : stateLine('warn', 'limits', `no reading yet ${dim('· run `usagefleet limits`')}`));
186
187
  console.log('');
187
188
  console.log(row('endpoint', host(ENDPOINT)));
188
- console.log(row('device', `${state.deviceId} · token ${cfg.token.slice(0, 8)}…`));
189
+ console.log(row('device', `${state.deviceId} · token ${tokenFor(cfg, detectClaudeAccount()?.extId).slice(0, 8)}…`));
189
190
  const watching = [cfg.projectsDir, cfg.desktopDir, ...cfg.piDirs].filter((d) => !!d);
190
191
  for (const [i, dir] of watching.entries()) {
191
192
  console.log(row(i === 0 ? 'watching' : '', tilde(dir)));
@@ -222,6 +223,16 @@ async function cmdLogin() {
222
223
  const token = process.argv.slice(3).find(a => !a.startsWith('-')) ?? flag('token');
223
224
  if (token) {
224
225
  process.env.USAGEFLEET_TOKEN = token;
226
+ // Before install(), so the service it starts already routes this account here.
227
+ // Logging in once per Claude account is how a machine that switches between
228
+ // accounts in different fleets keeps each one's usage in its own fleet.
229
+ const account = detectClaudeAccount();
230
+ if (account) {
231
+ updateStore(storePath(), store => {
232
+ store.accountTokens = { ...store.accountTokens, [account.extId]: token };
233
+ });
234
+ console.log(step('claude account', `${account.email ?? account.extId} reports with this token`));
235
+ }
225
236
  }
226
237
  const { install } = await import('./service.js');
227
238
  install();
package/dist/release.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by .github/workflows/release.yml.
2
- export const RELEASE_VERSION = "1.2.129";
2
+ export const RELEASE_VERSION = "1.2.131";
package/dist/store.js CHANGED
@@ -47,6 +47,7 @@ export function freshWindow() {
47
47
  * `updateStore` silently erases it. */
48
48
  function normalize(raw) {
49
49
  return {
50
+ accountTokens: raw.accountTokens,
50
51
  batch: raw.batch,
51
52
  desktopDir: raw.desktopDir,
52
53
  hook: raw.hook,
package/dist/uploader.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ENDPOINT } from './config.js';
1
+ import { ENDPOINT, tokenFor } from './config.js';
2
2
  const MAX_ATTEMPTS = 6;
3
3
  const REQUEST_TIMEOUT_MS = 15_000;
4
4
  function sleep(ms) {
@@ -19,7 +19,7 @@ export async function uploadBatch(payload, cfg) {
19
19
  body: JSON.stringify(payload),
20
20
  headers: {
21
21
  'content-type': 'application/json',
22
- 'x-api-key': cfg.token,
22
+ 'x-api-key': tokenFor(cfg, payload.accountExtId),
23
23
  },
24
24
  method: 'POST',
25
25
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
@@ -90,7 +90,7 @@ export async function postLimits(report, cfg) {
90
90
  body: JSON.stringify(report),
91
91
  headers: {
92
92
  'content-type': 'application/json',
93
- 'x-api-key': cfg.token,
93
+ 'x-api-key': tokenFor(cfg, report.account?.extId),
94
94
  },
95
95
  method: 'POST',
96
96
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usagefleet/cli",
3
- "version": "1.2.129",
3
+ "version": "1.2.131",
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",