@usagefleet/cli 1.2.69 → 1.2.71

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/dist/service.js CHANGED
@@ -2,9 +2,9 @@ import { execFileSync } from 'node:child_process';
2
2
  import { chmodSync, existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { homedir, tmpdir } from 'node:os';
4
4
  import { delimiter, join } from 'node:path';
5
- import { DEFAULT_ENDPOINT } from './config.js';
5
+ import { DEFAULT_ENDPOINT, loadConfig } from './config.js';
6
6
  import { installPromptHook, uninstallPromptHook } from './hook.js';
7
- import { readStore } from './store.js';
7
+ import { readStore, storePath, updateStore } from './store.js';
8
8
  import { fail, header, hint, host, row, step, tilde, warn } from './ui.js';
9
9
  const LABEL = 'dev.usagefleet.collector';
10
10
  /** Scheduled Task name on Windows (mirrors the launchd label / systemd unit). */
@@ -216,23 +216,40 @@ function xml(s) {
216
216
  .replaceAll("'", ''');
217
217
  }
218
218
  export function install() {
219
- // Pre-flight: refuse to install a service that can't resolve a token,
220
- // otherwise the baked `watch` process throws on every launch and the service
221
- // manager crash-loops it invisibly (only the log file shows it). Use the same
222
- // env-OR-file precedence loadConfig() uses so an earlier install's token is
223
- // honored; the endpoint needs no check, it falls back to the hosted default.
224
- const file = readStore();
225
- const endpoint = process.env.USAGEFLEET_ENDPOINT || file.endpoint || DEFAULT_ENDPOINT;
226
- const token = process.env.USAGEFLEET_TOKEN || file.token || '';
227
- if (!token) {
228
- console.error(fail('config', 'no device token resolved'));
229
- console.error(hint(' usagefleet install --token <device-token>'));
230
- console.error(hint(' or set USAGEFLEET_TOKEN'));
231
- process.exit(1);
219
+ // Pre-flight: refuse to install a service whose baked `watch` would throw on
220
+ // every launch, because the service manager crash-loops it invisibly (only the
221
+ // log file shows it). Resolving through loadConfig() is what makes this a real
222
+ // pre-flight rather than a lookalike: it is the same call `watch` makes, so a
223
+ // missing token or a non-https endpoint fails here or not at all.
224
+ let cfg;
225
+ try {
226
+ cfg = loadConfig();
227
+ }
228
+ catch (error) {
229
+ console.error(fail('config', error.message));
230
+ console.error(hint(' usagefleet install --endpoint <url> --token <device-token>'));
231
+ return process.exit(1);
232
+ }
233
+ // Config that only ever lived in this shell's env is lost to every later
234
+ // invocation: `usagefleet guard` runs from Claude Code's environment, which
235
+ // carries no USAGEFLEET_* vars (hook.ts bakes the command, not the env). The
236
+ // endpoint would fall back to the hosted default and send this device's token
237
+ // there; the token would be missing outright and the guard would fail open. So
238
+ // pin both to disk. The default endpoint is stored as absent rather than
239
+ // written out, so it can still move under an existing install.
240
+ const desiredEndpoint = cfg.endpoint === DEFAULT_ENDPOINT ? undefined : cfg.endpoint;
241
+ const stored = readStore();
242
+ if (stored.token !== cfg.token || stored.endpoint !== desiredEndpoint) {
243
+ // Only on a real change: `update` re-runs install every six hours, and this
244
+ // file is shared with the running collector's offset writes.
245
+ updateStore(storePath(), store => {
246
+ store.token = cfg.token;
247
+ store.endpoint = desiredEndpoint;
248
+ });
232
249
  }
233
250
  console.log(header());
234
251
  console.log('');
235
- console.log(step('configured', host(endpoint)));
252
+ console.log(step('configured', host(cfg.endpoint)));
236
253
  // Windows: stop a running task first, or `schtasks /run` below is ignored (the
237
254
  // task is IgnoreNew) — leaving the OLD version resident after an "update".
238
255
  if (process.platform === 'win32') {
package/dist/uploader.js CHANGED
@@ -44,7 +44,9 @@ export async function uploadBatch(payload, cfg) {
44
44
  }
45
45
  return { fatal: 'transient', ok: false };
46
46
  }
47
- /** Map a 4xx (never 429, the caller filters it) to a failure kind. */
47
+ /** Map a non-OK status to a failure kind. uploadBatch only ever passes a 4xx it
48
+ * has already decided not to retry; postLimits passes anything, and the 5xx/429
49
+ * fall-through to 'transient' is the answer it wants. */
48
50
  function classifyClientError(status) {
49
51
  if (status === 401 || status === 403) {
50
52
  return 'auth';
@@ -76,7 +78,11 @@ function retryAfterMs(header, fallback) {
76
78
  }
77
79
  return Math.min(Math.max(wait, 0), 60_000) + Math.floor(Math.random() * 500);
78
80
  }
79
- /** Report the account's real limit utilization to the server. */
81
+ /** Report the account's real limit utilization to the server. Shares uploadBatch's
82
+ * failure vocabulary so a plan wall reads as one on this leg too: it runs every
83
+ * cycle even when no usage records moved, so collapsing 402 to a bare failure
84
+ * would log an unactionable warning forever. Single-shot by design — a stale
85
+ * reading is worth less than the next cycle's fresh one. */
80
86
  export async function postLimits(report, cfg) {
81
87
  try {
82
88
  const res = await fetch(`${cfg.endpoint}/api/v1/limits`, {
@@ -88,9 +94,9 @@ export async function postLimits(report, cfg) {
88
94
  method: 'POST',
89
95
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
90
96
  });
91
- return res.ok;
97
+ return res.ok ? 'ok' : classifyClientError(res.status);
92
98
  }
93
99
  catch {
94
- return false;
100
+ return 'transient';
95
101
  }
96
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usagefleet/cli",
3
- "version": "1.2.69",
3
+ "version": "1.2.71",
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",
@@ -10,7 +10,7 @@
10
10
  "usage"
11
11
  ],
12
12
  "homepage": "https://usagefleet.com",
13
- "license": "MIT",
13
+ "license": "GPL-3.0-or-later",
14
14
  "repository": {
15
15
  "type": "git",
16
16
  "url": "git+https://github.com/rokartur/usagefleet.git",