@usagefleet/cli 1.2.169 → 1.2.170

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
@@ -78,8 +78,9 @@ shell once. `uninstall` removes both again, and self-update keeps them current.
78
78
  Desktop agent-mode (Cowork) sessions, and the
79
79
  [pi](https://github.com/badlogic/pi-mono) agent (`~/.pi/agent/sessions`,
80
80
  `anthropic` and `claude-bridge` provider records only; other providers don't
81
- touch Claude limits), and the jcode agent (`~/.jcode/sessions`) and its pj fork
82
- (`~/.pj/sessions`), Claude subscription turns only.
81
+ touch Claude limits), the jcode agent (`~/.jcode/sessions`) and its pj fork
82
+ (`~/.pj/sessions`), and the agent CLI (`~/.agents/sessions`), Claude
83
+ subscription turns only.
83
84
  A per-file byte offset in the config file means each line is sent once;
84
85
  rotation and truncation are handled and partial lines are never sent. Delivery
85
86
  is at-least-once — the server dedups on `uuid`.
@@ -160,6 +161,7 @@ it (`usagefleet config` prints this same table):
160
161
  | `piDir` | `USAGEFLEET_PI` | pi sessions dirs — string or array in the file, comma-separated in env; `off` to skip |
161
162
  | `jcodeDir` | `USAGEFLEET_JCODE` | override the jcode sessions dir (`~/.jcode/sessions`); `off` to skip it |
162
163
  | `pjDir` | `USAGEFLEET_PJ` | override the pj sessions dir (`~/.pj/sessions`); `off` to skip it |
164
+ | `agentDir` | `USAGEFLEET_AGENT` | override the agent CLI sessions dir (`~/.agents/sessions`); `off` to skip it |
163
165
  | `interval` | `USAGEFLEET_INTERVAL` | watch poll seconds (default 15) |
164
166
  | `limitsInterval` | `USAGEFLEET_LIMITS_INTERVAL` | seconds between limit reports (default 60; 300 on API keys, where each report costs a 1-token ping) |
165
167
  | `notifications` | `USAGEFLEET_NOTIFY` | desktop notifications, on by default (`false`/`0` disables) |
package/dist/collector.js CHANGED
@@ -43,6 +43,9 @@ export async function runOnce(cfg, log = () => {
43
43
  for (const dir of cfg.piDirs) {
44
44
  roots.push({ dir, onlyProjects: false, source: 'pi' });
45
45
  }
46
+ if (cfg.agentDir) {
47
+ roots.push({ dir: cfg.agentDir, onlyProjects: false, source: 'agent' });
48
+ }
46
49
  const files = [];
47
50
  for (const root of roots) {
48
51
  for (const fp of listJsonlFiles(root.dir, dirCache)) {
package/dist/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { defaultDesktopSessionsDir, defaultJcodeSessionsDir, defaultPiSessionsDirs, defaultPjSessionsDir, defaultProjectsDir, } from './paths.js';
1
+ import { defaultAgentSessionsDir, defaultDesktopSessionsDir, defaultJcodeSessionsDir, defaultPiSessionsDirs, defaultPjSessionsDir, defaultProjectsDir, } from './paths.js';
2
2
  import { readStore, storePath } from './store.js';
3
3
  /** Matches the server's BatchSchema `.max(1000)`. */
4
4
  const MAX_BATCH = 1000;
@@ -24,6 +24,7 @@ export function loadConfig() {
24
24
  // Kept even when the env names a token: the service unit bakes the default
25
25
  // one into USAGEFLEET_TOKEN, and bindings are what route around it.
26
26
  accountTokens: file.accountTokens ?? {},
27
+ agentDir: resolveOptionalDir(process.env.USAGEFLEET_AGENT, file.agentDir, defaultAgentSessionsDir()),
27
28
  batchSize,
28
29
  desktopDir: resolveOptionalDir(process.env.USAGEFLEET_DESKTOP, file.desktopDir, defaultDesktopSessionsDir()),
29
30
  jcodeDir: resolveOptionalDir(process.env.USAGEFLEET_JCODE, file.jcodeDir, defaultJcodeSessionsDir()),
package/dist/index.js CHANGED
@@ -89,7 +89,7 @@ async function cmdWatch() {
89
89
  const rawUpdate = positiveNumber(process.env.USAGEFLEET_UPDATE_INTERVAL, settings.updateInterval) ?? 6 * 60 * 60;
90
90
  const updateInterval = Math.max(60, rawUpdate) * 1000;
91
91
  let lastUpdateAt = 0;
92
- const watching = [cfg.projectsDir, cfg.desktopDir, ...cfg.piDirs, cfg.jcodeDir, cfg.pjDir].filter((d) => !!d);
92
+ const watching = [cfg.projectsDir, cfg.desktopDir, ...cfg.piDirs, cfg.jcodeDir, cfg.pjDir, cfg.agentDir].filter((d) => !!d);
93
93
  console.log(header(`watching every ${interval / 1000}s`));
94
94
  console.log(hint(`${watching.map(tilde).join(' · ')} → ${host(ENDPOINT)}`));
95
95
  console.log('');
@@ -193,7 +193,7 @@ async function cmdStatus() {
193
193
  console.log('');
194
194
  console.log(row('endpoint', host(ENDPOINT)));
195
195
  console.log(row('device', `${state.deviceId} · token ${tokenFor(cfg, detectClaudeAccount()?.extId).slice(0, 8)}…`));
196
- const watching = [cfg.projectsDir, cfg.desktopDir, ...cfg.piDirs, cfg.jcodeDir, cfg.pjDir].filter((d) => !!d);
196
+ const watching = [cfg.projectsDir, cfg.desktopDir, ...cfg.piDirs, cfg.jcodeDir, cfg.pjDir, cfg.agentDir].filter((d) => !!d);
197
197
  for (const [i, dir] of watching.entries()) {
198
198
  console.log(row(i === 0 ? 'watching' : '', tilde(dir)));
199
199
  }
@@ -285,6 +285,7 @@ function cmdConfig() {
285
285
  ['piDir', 'USAGEFLEET_PI', 'pi session dirs · string or array; env comma-separated'],
286
286
  ['jcodeDir', 'USAGEFLEET_JCODE', 'override ~/.jcode/sessions ("off" disables)'],
287
287
  ['pjDir', 'USAGEFLEET_PJ', 'override ~/.pj/sessions ("off" disables)'],
288
+ ['agentDir', 'USAGEFLEET_AGENT', 'override ~/.agents/sessions ("off" disables)'],
288
289
  ['interval', 'USAGEFLEET_INTERVAL', 'watch interval seconds (default 15)'],
289
290
  [
290
291
  'limitsInterval',
package/dist/parser.js CHANGED
@@ -71,6 +71,57 @@ function parsePiLine(o, sessionCwd) {
71
71
  version: null,
72
72
  };
73
73
  }
74
+ /** agent CLI session line: `{type:"usage", id, provider, account, model, timestamp,
75
+ * input, output, cache_read, cache_write}`, one per request, `timestamp` in epoch ms.
76
+ * Only provider "anthropic" with an `account` (the Claude account uuid; API keys
77
+ * have none) ran on a subscription, and each line books on its own account because
78
+ * the agent switches accounts between requests. Lines from agents that predate
79
+ * `id` and `account` cannot be told apart from API-key requests and are skipped.
80
+ * /clone and /worktree copy these lines into a new session, so `id` (the API's
81
+ * response id) is the dedup key, not anything per file. */
82
+ export function parseAgentLine(line, sessionCwd, sessionId) {
83
+ let parsed;
84
+ try {
85
+ parsed = JSON.parse(line);
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ if (typeof parsed !== 'object' || parsed === null) {
91
+ return null;
92
+ }
93
+ const o = parsed;
94
+ const id = str(o.id);
95
+ const account = str(o.account);
96
+ const at = new Date(typeof o.timestamp === 'number' ? o.timestamp : Number.NaN);
97
+ if (o.type !== 'usage' || o.provider !== 'anthropic' || !id || !account || Number.isNaN(at.getTime())) {
98
+ return null;
99
+ }
100
+ const tokens = (v) => (typeof v === 'number' ? v : 0);
101
+ return {
102
+ accountExtId: account,
103
+ record: {
104
+ // The agent does not log the cache-write TTL; the server prices it by the user's setting.
105
+ cacheCreation1h: null,
106
+ cacheCreation5m: null,
107
+ cacheCreationTokens: tokens(o.cache_write),
108
+ cacheReadTokens: tokens(o.cache_read),
109
+ cwd: sessionCwd,
110
+ gitBranch: null,
111
+ inputTokens: tokens(o.input),
112
+ messageId: id,
113
+ model: str(o.model),
114
+ outputTokens: tokens(o.output),
115
+ requestId: null,
116
+ serviceTier: null,
117
+ sessionId,
118
+ source: 'agent',
119
+ timestamp: at.toISOString(),
120
+ uuid: `agent:${id}`,
121
+ version: null,
122
+ },
123
+ };
124
+ }
74
125
  /**
75
126
  * Parse a single JSONL line. Returns a UsageRecord only for assistant messages
76
127
  * that carry a usage object; everything else (user/system/summary/tool/…) → null.
package/dist/paths.js CHANGED
@@ -72,6 +72,10 @@ export function defaultJcodeSessionsDir() {
72
72
  export function defaultPjSessionsDir() {
73
73
  return join(homedir(), '.pj', 'sessions');
74
74
  }
75
+ /** The agent CLI keeps every session flat in `~/.agents/sessions`. */
76
+ export function defaultAgentSessionsDir() {
77
+ return join(homedir(), '.agents', 'sessions');
78
+ }
75
79
  /** pi's agent dir, the root for its sessions and extensions.
76
80
  * PI_CODING_AGENT_DIR is pi's own relocation knob; unlike the service-side
77
81
  * scan above, the guard installer runs from the user's shell where that env
package/dist/release.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by .github/workflows/release.yml.
2
- export const RELEASE_VERSION = "1.2.169";
2
+ export const RELEASE_VERSION = "1.2.170";
package/dist/store.js CHANGED
@@ -48,6 +48,7 @@ export function freshWindow() {
48
48
  function normalize(raw) {
49
49
  return {
50
50
  accountTokens: raw.accountTokens,
51
+ agentDir: raw.agentDir,
51
52
  batch: raw.batch,
52
53
  desktopDir: raw.desktopDir,
53
54
  hook: raw.hook,
package/dist/tailer.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { closeSync, openSync, readSync, statSync } from 'node:fs';
2
- import { parseLine } from './parser.js';
2
+ import { basename } from 'node:path';
3
+ import { parseAgentLine, parseLine } from './parser.js';
3
4
  import { dim, line, tilde, yellow } from './ui.js';
4
5
  /** Max bytes read from a single file per cycle (bounds memory on huge backlogs). */
5
6
  const MAX_READ = 16 * 1024 * 1024;
@@ -8,7 +9,7 @@ const USAGE_KEY = Buffer.from('"usage"');
8
9
  // Unescaped quotes: the same text quoted inside a message is `\"customType\"` and never matches.
9
10
  const ACCOUNT_KEY = Buffer.from('"customType":"claude-account"');
10
11
  /**
11
- * The working directory of a pi session, read from the file's first line
12
+ * The working directory of a pi or agent session, read from the file's first line
12
13
  * (`{"type":"session",…,"cwd":"/path"}`). pi's message lines carry no cwd, and
13
14
  * tailing usually resumes past the header, so it is re-read per cycle rather
14
15
  * than remembered. The session dir name encodes the same path but lossily
@@ -68,7 +69,7 @@ export function tailFile(filePath, prev, source = 'cli') {
68
69
  let sessionCwd = null;
69
70
  try {
70
71
  readSync(fd, buf, 0, length, start);
71
- if (source === 'pi') {
72
+ if (source === 'pi' || source === 'agent') {
72
73
  sessionCwd = piSessionCwd(fd);
73
74
  }
74
75
  }
@@ -106,6 +107,12 @@ export function tailFile(filePath, prev, source = 'cli') {
106
107
  if (isAccountLine) {
107
108
  account = piSessionAccount(text);
108
109
  }
110
+ else if (source === 'agent') {
111
+ const usage = parseAgentLine(text, sessionCwd, basename(filePath, '.jsonl'));
112
+ if (usage) {
113
+ addRecord(segments, usage.accountExtId, usage.record);
114
+ }
115
+ }
109
116
  else {
110
117
  const rec = parseLine(text, source, sessionCwd);
111
118
  if (rec) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usagefleet/cli",
3
- "version": "1.2.169",
3
+ "version": "1.2.170",
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",