lumira 1.4.1 → 1.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
@@ -34,6 +34,7 @@ Interactive wizard — preset, theme, icons — previewed live before write.
34
34
  - [Install](#install)
35
35
  - [Display modes](#display)
36
36
  - [Themes](#themes)
37
+ - [Stats CLI](#stats-cli)
37
38
  - [Powerline](#powerline)
38
39
  - [Configuration](#configuration)
39
40
  - [Architecture](#architecture)
@@ -190,6 +191,28 @@ lumira themes preview --all --powerline # the powerline grid (great for scr
190
191
 
191
192
  Adding a theme is a single new file plus a one-line registration. Every PR runs the **WCAG AA contrast guard** — if any powerline cell drops below 4.5:1 against the foreground, CI rejects it. See [CONTRIBUTING.md → Adding a theme](CONTRIBUTING.md#adding-a-theme) for the walkthrough.
192
193
 
194
+ ## Stats CLI
195
+
196
+ `lumira stats` reads a Claude Code or Qwen Code transcript `.jsonl` and prints a one-shot analytics summary — session duration, total cost, token totals, cache hit rate, tool call frequency, and burn rate (`$/h`). Useful for post-session review, scripting, and CI dashboards.
197
+
198
+ ```bash
199
+ # Just works — auto-discovers the newest transcript for the current cwd.
200
+ lumira stats
201
+ # Session: 2h 15m — $4.23 — 156k tokens — 87% cache
202
+ # Tools: Bash×45 Read×32 Write×18 Edit×12 Agent×8
203
+ # Burn: $1.88/h
204
+ ```
205
+
206
+ **Auto-discovery:** with no flags, `lumira stats` derives the Claude Code project slug from `cwd` (`/home/me/proj` → `-home-me-proj`) and reads the newest `.jsonl` under `~/.claude/projects/<slug>/`. If the current directory has no matching project dir, it falls back to the globally most-recently-modified transcript under `~/.claude/projects/` and prints a notice to stderr ("reading most recent session from …") so JSON pipelines on stdout stay clean.
207
+
208
+ **Flags:**
209
+
210
+ - `--session-id <path-or-uuid>` — override auto-discovery. A path (anything containing `/` or ending in `.jsonl`) is used as-is. A bare uuid is resolved first under `~/.claude/projects/<cwd-slug>/<uuid>.jsonl`, then by scanning every project dir for that filename.
211
+ - `--no-color` — strip ANSI escapes (also honored when the `NO_COLOR` env var is set, per [no-color.org](https://no-color.org)).
212
+ - `--json` — emit the raw `SessionStats` object as pretty-printed JSON for `jq` / CI composability.
213
+
214
+ **Qwen Code sessions** are parsed the same way, but cost and burn-rate lines are suppressed when the transcript lacks usage blocks (`hasCostData: false` in the JSON output) — no misleading `$0.00`.
215
+
193
216
  ## Powerline
194
217
 
195
218
  `style: "powerline"` (or `--powerline`) renders the statusline with colored segment backgrounds and glyph separators inspired by powerline-go / oh-my-posh. Available separator presets via `powerline.style` (or `--powerline-style=<name>`):
@@ -0,0 +1,71 @@
1
+ import { type SessionStats } from '../parsers/transcript-stats.js';
2
+ export interface StatsArgs {
3
+ sessionId?: string;
4
+ noColor: boolean;
5
+ json: boolean;
6
+ }
7
+ /**
8
+ * Result of invoking the stats subcommand. Mirrors `ThemesCommandResult` so
9
+ * the dispatcher in `index.ts` can treat both subcommands uniformly.
10
+ */
11
+ export interface StatsCommandResult {
12
+ stdout: string;
13
+ stderr: string;
14
+ exitCode: number;
15
+ }
16
+ /**
17
+ * Optional overrides for `runStatsCommand`. `cwd` and `homeDir` are injected
18
+ * here (rather than read from `process`) so tests can build isolated fake
19
+ * `~/.claude/projects/` trees in tmpdir without touching the real one.
20
+ */
21
+ export interface StatsCommandOpts {
22
+ cwd?: string;
23
+ homeDir?: string;
24
+ }
25
+ /**
26
+ * Parse argv for `lumira stats [...flags]`. argv is the full process.argv;
27
+ * the 'stats' command starts at argv[2], flags from argv[3].
28
+ *
29
+ * Per https://no-color.org, the NO_COLOR env var (any non-empty value)
30
+ * disables color. An empty string is *not* a trigger — the variable being
31
+ * unset and the variable being empty must behave identically, otherwise
32
+ * shell environments that pre-export NO_COLOR='' would silently disable
33
+ * color everywhere.
34
+ *
35
+ * Unknown flags are ignored to stay forward-compatible: a future minor that
36
+ * adds a new flag shouldn't crash older binaries piped against new shells.
37
+ */
38
+ export declare function parseStatsArgs(argv: string[]): StatsArgs;
39
+ /**
40
+ * Render a SessionStats either as human-readable text or as pretty JSON.
41
+ *
42
+ * In `json` mode we emit `JSON.stringify(stats, null, 2)` — the 2-space
43
+ * indent makes the output diff-friendly without being absurdly verbose, and
44
+ * matches the convention used elsewhere in the tree for CLI JSON output.
45
+ *
46
+ * In human mode we always render the `Session:` and `Tools:` lines so the
47
+ * user gets a consistent skeleton regardless of which renderer produced the
48
+ * transcript. Cost and burn-rate lines are gated on `hasCostData` so Qwen
49
+ * (and other no-usage backends) never surface a misleading "$0.00".
50
+ */
51
+ export declare function formatStatsOutput(stats: SessionStats, opts: {
52
+ noColor: boolean;
53
+ json: boolean;
54
+ }): string;
55
+ /**
56
+ * Execute `lumira stats [...]`. Resolution order for the transcript:
57
+ * 1. `--session-id <path>` (contains `/` or ends in `.jsonl`) — used as-is.
58
+ * 2. `--session-id <uuid>` — looked up under cwd-slug, then globally.
59
+ * 3. No flag — auto-discover newest in cwd-slug dir, then globally.
60
+ *
61
+ * The parser's own allow-list check (LUMIRA_ALLOWED_ROOTS) remains the
62
+ * security boundary — anything we hand it must pass `isUnderAllowedRoot`.
63
+ * Discovered paths live under `~/.claude/projects/` (covered by the home
64
+ * root) or under tmpdir in tests (also covered).
65
+ *
66
+ * `cols` is accepted for parity with `runThemesCommand` but the human
67
+ * renderer here doesn't wrap or align to terminal width — keep it in the
68
+ * signature so a future widened renderer (sparklines, multi-column tool
69
+ * frequency) can opt in without breaking the dispatcher contract.
70
+ */
71
+ export declare function runStatsCommand(argv: string[], _cols?: number, opts?: StatsCommandOpts): Promise<StatsCommandResult>;
@@ -0,0 +1,371 @@
1
+ /**
2
+ * `lumira stats` subcommand (issue #114) — print an aggregate summary of a
3
+ * Claude Code transcript session. Consumes `aggregateStats` from the parser
4
+ * layer, formats either human-readable or JSON output, and follows the same
5
+ * `{ stdout, stderr, exitCode }` contract as `runThemesCommand` so the
6
+ * top-level CLI dispatcher can wire it identically.
7
+ *
8
+ * Color handling: we deliberately do NOT auto-detect TTY here. Tests assert
9
+ * that `--no-color` strips ANSI escapes, and the default rendering already
10
+ * uses no escape codes (plain text only) — keeping the formatter color-free
11
+ * in the default path means a TTY-less test environment never sees stray
12
+ * sequences, and a future enhancement can layer color in via `createColors`
13
+ * without changing the test contract.
14
+ *
15
+ * Session auto-discovery (issue #114 follow-up): when `--session-id` is
16
+ * omitted, derive the Claude Code project slug from `cwd` (`/foo/bar` →
17
+ * `-foo-bar`) and read the newest `.jsonl` in `<homeDir>/.claude/projects/
18
+ * <slug>/`. If that dir is missing or empty, fall back to the globally
19
+ * newest transcript across all project subdirs and emit a stderr notice so
20
+ * users know a non-cwd session was picked. A bare uuid passed via
21
+ * `--session-id` is treated the same way: prefer cwd-slug, fall back to
22
+ * global search by filename.
23
+ */
24
+ import { promises as fs } from 'node:fs';
25
+ import { homedir } from 'node:os';
26
+ import { join, relative, isAbsolute } from 'node:path';
27
+ import { aggregateStats } from '../parsers/transcript-stats.js';
28
+ import { formatTokens, formatDuration, formatCost, formatBurnRate } from '../utils/format.js';
29
+ import { stripAnsi } from '../render/colors.js';
30
+ /**
31
+ * Parse argv for `lumira stats [...flags]`. argv is the full process.argv;
32
+ * the 'stats' command starts at argv[2], flags from argv[3].
33
+ *
34
+ * Per https://no-color.org, the NO_COLOR env var (any non-empty value)
35
+ * disables color. An empty string is *not* a trigger — the variable being
36
+ * unset and the variable being empty must behave identically, otherwise
37
+ * shell environments that pre-export NO_COLOR='' would silently disable
38
+ * color everywhere.
39
+ *
40
+ * Unknown flags are ignored to stay forward-compatible: a future minor that
41
+ * adds a new flag shouldn't crash older binaries piped against new shells.
42
+ */
43
+ export function parseStatsArgs(argv) {
44
+ let sessionId;
45
+ let noColor = false;
46
+ let json = false;
47
+ for (let i = 3; i < argv.length; i++) {
48
+ const arg = argv[i];
49
+ if (arg === '--session-id' && i + 1 < argv.length) {
50
+ const next = argv[i + 1];
51
+ // Guard: if the "value" begins with `-`, the user almost certainly
52
+ // forgot to pass a value (e.g. `--session-id --json`). Consuming it
53
+ // would silently swallow the next flag — so skip without advancing
54
+ // and let the loop reprocess `--json` on its own.
55
+ if (next.startsWith('-'))
56
+ continue;
57
+ sessionId = next;
58
+ i += 1;
59
+ continue;
60
+ }
61
+ if (arg === '--no-color') {
62
+ noColor = true;
63
+ continue;
64
+ }
65
+ if (arg === '--json') {
66
+ json = true;
67
+ continue;
68
+ }
69
+ // Unknown flag — ignored.
70
+ }
71
+ const envNoColor = process.env['NO_COLOR'];
72
+ if (typeof envNoColor === 'string' && envNoColor.length > 0)
73
+ noColor = true;
74
+ return { sessionId, noColor, json };
75
+ }
76
+ /** True when the session has no temporal extent and no activity at all. */
77
+ function isEmptySession(stats) {
78
+ const noTokens = stats.inputTokens === 0 && stats.outputTokens === 0
79
+ && stats.cacheReadTokens === 0 && stats.cacheCreationTokens === 0;
80
+ const noTools = Object.keys(stats.toolFrequency).length === 0;
81
+ const noTime = stats.sessionStart === null || stats.durationMs === 0;
82
+ return noTokens && noTools && noTime;
83
+ }
84
+ /** Cache hit % = cache_read / (cache_read + input). Returns null when undefined. */
85
+ function cacheHitPercent(stats) {
86
+ const denom = stats.cacheReadTokens + stats.inputTokens;
87
+ if (denom <= 0)
88
+ return null;
89
+ return Math.round((stats.cacheReadTokens / denom) * 100);
90
+ }
91
+ function totalTokens(stats) {
92
+ return stats.inputTokens + stats.outputTokens
93
+ + stats.cacheReadTokens + stats.cacheCreationTokens;
94
+ }
95
+ /**
96
+ * Render a SessionStats either as human-readable text or as pretty JSON.
97
+ *
98
+ * In `json` mode we emit `JSON.stringify(stats, null, 2)` — the 2-space
99
+ * indent makes the output diff-friendly without being absurdly verbose, and
100
+ * matches the convention used elsewhere in the tree for CLI JSON output.
101
+ *
102
+ * In human mode we always render the `Session:` and `Tools:` lines so the
103
+ * user gets a consistent skeleton regardless of which renderer produced the
104
+ * transcript. Cost and burn-rate lines are gated on `hasCostData` so Qwen
105
+ * (and other no-usage backends) never surface a misleading "$0.00".
106
+ */
107
+ export function formatStatsOutput(stats, opts) {
108
+ if (opts.json)
109
+ return JSON.stringify(stats, null, 2);
110
+ if (isEmptySession(stats)) {
111
+ const text = 'Session: empty session — no activity recorded.';
112
+ return opts.noColor ? stripAnsi(text) : text;
113
+ }
114
+ const lines = [];
115
+ // Session: <duration> [— <cost>, <tokens> tokens, <cache>% cache]
116
+ const segments = [`Session: ${formatDuration(stats.durationMs) || '0s'}`];
117
+ if (stats.hasCostData) {
118
+ const cost = formatCost(stats.costUsd);
119
+ if (cost)
120
+ segments.push(cost);
121
+ const tt = totalTokens(stats);
122
+ if (tt > 0)
123
+ segments.push(`${formatTokens(tt)} tokens`);
124
+ const hit = cacheHitPercent(stats);
125
+ if (hit !== null)
126
+ segments.push(`${hit}% cache`);
127
+ }
128
+ lines.push(segments.join(' — '));
129
+ // Tools: ToolName×N ToolName×N ...
130
+ const toolEntries = Object.entries(stats.toolFrequency)
131
+ .sort(([, a], [, b]) => b - a)
132
+ .map(([name, count]) => `${name}×${count}`);
133
+ lines.push(`Tools: ${toolEntries.length > 0 ? toolEntries.join(' ') : '(none)'}`);
134
+ // Burn: $X.XX/h — only meaningful when we have cost AND a long-enough session.
135
+ if (stats.hasCostData) {
136
+ const burn = formatBurnRate(stats.costUsd, stats.durationMs);
137
+ if (burn)
138
+ lines.push(`Burn: ${burn}`);
139
+ }
140
+ const out = lines.join('\n');
141
+ return opts.noColor ? stripAnsi(out) : out;
142
+ }
143
+ function err(message, exitCode = 1) {
144
+ return { stdout: '', stderr: message.endsWith('\n') ? message : `${message}\n`, exitCode };
145
+ }
146
+ function ok(stdout, stderr = '') {
147
+ return { stdout, stderr, exitCode: 0 };
148
+ }
149
+ /**
150
+ * Convert a cwd into the Claude Code project slug. Claude Code names project
151
+ * directories by replacing every `/` in the absolute cwd with `-`, including
152
+ * the leading slash — `/home/me/proj` becomes `-home-me-proj`.
153
+ *
154
+ * Trailing slashes are stripped first so `/foo/bar/` and `/foo/bar` produce
155
+ * the same slug `-foo-bar`. Without this normalization a stray trailing slash
156
+ * (e.g. from a shell prompt's PWD with a trailing /) would yield `-foo-bar-`
157
+ * and silently miss the real project dir.
158
+ */
159
+ function cwdToSlug(cwd) {
160
+ return cwd.replace(/\/+$/, '').replace(/\//g, '-');
161
+ }
162
+ /**
163
+ * Return the newest `.jsonl` file in `dir` by mtime, or `null` if the dir
164
+ * doesn't exist, can't be read, or contains no `.jsonl` files. Symlinks and
165
+ * non-regular files are tolerated — `fs.stat` follows symlinks, and a stat
166
+ * failure on a single entry just drops it from the candidate set.
167
+ */
168
+ async function newestJsonl(dir) {
169
+ let entries;
170
+ try {
171
+ entries = await fs.readdir(dir);
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ let bestPath = null;
177
+ let bestMtime = -Infinity;
178
+ for (const name of entries) {
179
+ if (!name.endsWith('.jsonl'))
180
+ continue;
181
+ const full = join(dir, name);
182
+ try {
183
+ const st = await fs.stat(full);
184
+ if (!st.isFile())
185
+ continue;
186
+ if (st.mtimeMs > bestMtime) {
187
+ bestMtime = st.mtimeMs;
188
+ bestPath = full;
189
+ }
190
+ }
191
+ catch {
192
+ // Unstable entry (deleted between readdir and stat, permission denied,
193
+ // broken symlink). Skip it — discovery should never crash on one bad file.
194
+ }
195
+ }
196
+ return bestPath;
197
+ }
198
+ /**
199
+ * Walk every project subdir under `projectsRoot` and return the globally
200
+ * newest `.jsonl` by mtime. Used as the fallback when the cwd-slug dir
201
+ * doesn't exist (e.g. user runs `lumira stats` from a directory that isn't
202
+ * a known Claude Code project).
203
+ */
204
+ async function globalNewestJsonl(projectsRoot) {
205
+ let dirs;
206
+ try {
207
+ dirs = await fs.readdir(projectsRoot);
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ let bestPath = null;
213
+ let bestMtime = -Infinity;
214
+ for (const subdir of dirs) {
215
+ const candidate = await newestJsonl(join(projectsRoot, subdir));
216
+ if (!candidate)
217
+ continue;
218
+ try {
219
+ const st = await fs.stat(candidate);
220
+ if (st.mtimeMs > bestMtime) {
221
+ bestMtime = st.mtimeMs;
222
+ bestPath = candidate;
223
+ }
224
+ }
225
+ catch {
226
+ // Race: file vanished between `newestJsonl` and this stat. Skip.
227
+ }
228
+ }
229
+ return bestPath;
230
+ }
231
+ /**
232
+ * Locate a transcript when the user didn't pass an explicit path. Prefers
233
+ * the cwd-slug project dir; falls back to the globally newest transcript.
234
+ */
235
+ async function discoverTranscript(cwd, homeDir) {
236
+ const projectsRoot = join(homeDir, '.claude', 'projects');
237
+ const cwdSlugDir = join(projectsRoot, cwdToSlug(cwd));
238
+ const local = await newestJsonl(cwdSlugDir);
239
+ if (local)
240
+ return local;
241
+ return await globalNewestJsonl(projectsRoot);
242
+ }
243
+ /**
244
+ * Resolve a bare session uuid to a JSONL path. Prefer the cwd-slug dir so
245
+ * a uuid that exists in multiple projects (rare but possible if the user
246
+ * imports an old session) maps to the locally-scoped one. Falls back to the
247
+ * first match found by scanning every project subdir.
248
+ */
249
+ async function resolveSessionId(uuid, cwd, homeDir) {
250
+ const projectsRoot = join(homeDir, '.claude', 'projects');
251
+ // Accept either bare `<uuid>` or `<uuid>.jsonl` — strip the extension if
252
+ // present so we don't produce `<uuid>.jsonl.jsonl`.
253
+ const bare = uuid.endsWith('.jsonl') ? uuid.slice(0, -'.jsonl'.length) : uuid;
254
+ const filename = `${bare}.jsonl`;
255
+ // 1. Try cwd-slug dir.
256
+ const localPath = join(projectsRoot, cwdToSlug(cwd), filename);
257
+ try {
258
+ const st = await fs.stat(localPath);
259
+ if (st.isFile())
260
+ return localPath;
261
+ }
262
+ catch { /* miss — fall through to global scan */ }
263
+ // 2. Walk all project subdirs.
264
+ let dirs;
265
+ try {
266
+ dirs = await fs.readdir(projectsRoot);
267
+ }
268
+ catch {
269
+ return null;
270
+ }
271
+ for (const sub of dirs) {
272
+ const cand = join(projectsRoot, sub, filename);
273
+ try {
274
+ const st = await fs.stat(cand);
275
+ if (st.isFile())
276
+ return cand;
277
+ }
278
+ catch { /* not in this project — keep walking */ }
279
+ }
280
+ return null;
281
+ }
282
+ /**
283
+ * Cheap detector for "looks like a path, not a uuid". The presence of a `/`
284
+ * is the only reliable signal — a bare filename (even one ending in `.jsonl`,
285
+ * such as `<uuid>.jsonl`) should still go through uuid resolution so the
286
+ * cwd-slug lookup applies. Treating `.jsonl` as path-ish would route
287
+ * `--session-id <uuid>.jsonl` to the "use as-is" branch, producing a
288
+ * `Transcript file not found` error instead of resolving via the project dir.
289
+ */
290
+ function looksLikePath(value) {
291
+ return value.includes('/');
292
+ }
293
+ /**
294
+ * Execute `lumira stats [...]`. Resolution order for the transcript:
295
+ * 1. `--session-id <path>` (contains `/` or ends in `.jsonl`) — used as-is.
296
+ * 2. `--session-id <uuid>` — looked up under cwd-slug, then globally.
297
+ * 3. No flag — auto-discover newest in cwd-slug dir, then globally.
298
+ *
299
+ * The parser's own allow-list check (LUMIRA_ALLOWED_ROOTS) remains the
300
+ * security boundary — anything we hand it must pass `isUnderAllowedRoot`.
301
+ * Discovered paths live under `~/.claude/projects/` (covered by the home
302
+ * root) or under tmpdir in tests (also covered).
303
+ *
304
+ * `cols` is accepted for parity with `runThemesCommand` but the human
305
+ * renderer here doesn't wrap or align to terminal width — keep it in the
306
+ * signature so a future widened renderer (sparklines, multi-column tool
307
+ * frequency) can opt in without breaking the dispatcher contract.
308
+ */
309
+ export async function runStatsCommand(argv,
310
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
311
+ _cols, opts = {}) {
312
+ const args = parseStatsArgs(argv);
313
+ const cwd = opts.cwd ?? process.cwd();
314
+ const homeDir = opts.homeDir ?? homedir();
315
+ const projectsRoot = join(homeDir, '.claude', 'projects');
316
+ let transcriptPath;
317
+ let usedDiscovery = false;
318
+ if (args.sessionId) {
319
+ if (looksLikePath(args.sessionId)) {
320
+ // Explicit path — hand straight to the parser.
321
+ transcriptPath = args.sessionId;
322
+ }
323
+ else {
324
+ // Bare uuid — resolve via cwd-slug then global scan.
325
+ const resolved = await resolveSessionId(args.sessionId, cwd, homeDir);
326
+ if (!resolved) {
327
+ return err(`lumira stats: session id "${args.sessionId}" not found under ${projectsRoot}\n`);
328
+ }
329
+ transcriptPath = resolved;
330
+ }
331
+ }
332
+ else {
333
+ const discovered = await discoverTranscript(cwd, homeDir);
334
+ if (!discovered) {
335
+ return err(`lumira stats: no transcripts found under ${projectsRoot}\n\n`
336
+ + 'Pass --session-id <path-or-uuid> explicitly, or run `lumira stats` from a directory '
337
+ + 'where Claude Code has recorded a session.\n');
338
+ }
339
+ transcriptPath = discovered;
340
+ usedDiscovery = true;
341
+ }
342
+ let stats;
343
+ try {
344
+ stats = await aggregateStats(transcriptPath);
345
+ }
346
+ catch (e) {
347
+ const message = e instanceof Error ? e.message : String(e);
348
+ return err(`lumira stats: ${message}`);
349
+ }
350
+ // Fallback notice — emitted to stderr only when discovery picked a
351
+ // transcript outside the cwd-slug project dir. Keeping it on stderr means
352
+ // `lumira stats --json | jq` stays parseable even when the fallback fired.
353
+ //
354
+ // Use `path.relative` instead of `startsWith` because slug names can be
355
+ // prefix-collisions of each other: `cwd=/foo` (slug `-foo`) vs a transcript
356
+ // under `<projects>/-foo-bar/x.jsonl` — `startsWith('-foo')` matches the
357
+ // sibling project and would silently SUPPRESS the cross-project notice.
358
+ // `relative(cwdSlugDir, transcriptPath)` returns `..` or an absolute path
359
+ // when the target is outside the dir, which we reject correctly.
360
+ let stderr = '';
361
+ if (usedDiscovery) {
362
+ const cwdSlugDir = join(projectsRoot, cwdToSlug(cwd));
363
+ const rel = relative(cwdSlugDir, transcriptPath);
364
+ const inCwdSlug = rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
365
+ if (!inCwdSlug) {
366
+ stderr = `lumira stats: no transcripts for cwd; reading most recent session from ${transcriptPath}\n`;
367
+ }
368
+ }
369
+ return ok(formatStatsOutput(stats, { noColor: args.noColor, json: args.json }), stderr);
370
+ }
371
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/commands/stats.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { render } from './render/index.js';
16
16
  import { resolveIcons } from './render/icons.js';
17
17
  import { install, uninstall } from './installer.js';
18
18
  import { runThemesCommand } from './commands/themes.js';
19
+ import { runStatsCommand } from './commands/stats.js';
19
20
  import { EMPTY_TRANSCRIPT } from './types.js';
20
21
  import { normalize } from './normalize.js';
21
22
  const defaultDeps = {
@@ -82,6 +83,19 @@ if (isDirectRun()) {
82
83
  if (r.exitCode !== 0)
83
84
  process.exit(r.exitCode);
84
85
  }
86
+ else if (cmd === 'stats') {
87
+ runStatsCommand(process.argv, process.stdout.columns).then(r => {
88
+ if (r.stdout)
89
+ process.stdout.write(r.stdout);
90
+ if (r.stderr)
91
+ process.stderr.write(r.stderr);
92
+ if (r.exitCode !== 0)
93
+ process.exit(r.exitCode);
94
+ }).catch(e => {
95
+ process.stderr.write(`Stats error: ${e.message}\n`);
96
+ process.exit(1);
97
+ });
98
+ }
85
99
  else {
86
100
  main().then(o => process.stdout.write(o)).catch(e => { if (!(e instanceof StdinParseError))
87
101
  process.stderr.write(`Statusline error: ${e.message}\n`); });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * SessionStats — aggregate view of a Claude Code transcript for the stats CLI.
3
+ *
4
+ * `hasCostData` reflects whether *any* assistant turn carried a `usage` block
5
+ * (i.e. the platform is emitting token accounting), not whether the dollar
6
+ * total is non-zero. Qwen and other non-Anthropic backends never emit usage,
7
+ * so `hasCostData=false` lets renderers suppress cost-related output without
8
+ * surfacing a misleading "$0.00".
9
+ */
10
+ export interface SessionStats {
11
+ sessionStart: number | null;
12
+ sessionEnd: number | null;
13
+ durationMs: number;
14
+ inputTokens: number;
15
+ outputTokens: number;
16
+ cacheReadTokens: number;
17
+ cacheCreationTokens: number;
18
+ costUsd: number;
19
+ hasCostData: boolean;
20
+ toolFrequency: Record<string, number>;
21
+ agentCount: number;
22
+ errorCount: number;
23
+ }
24
+ /**
25
+ * Stream-aggregate a Claude Code transcript JSONL into a SessionStats summary.
26
+ *
27
+ * Single-pass design: tokens, cost, tool frequency, agent count, and error
28
+ * count are all collected in one readline sweep. We deliberately do NOT call
29
+ * `parseTranscript` — that parser returns derived TranscriptData (with zombie
30
+ * detection, todo merging, subagents-dir override, etc.) but does not expose
31
+ * the raw token totals we need here. Re-streaming once is cheaper than
32
+ * parsing twice, and keeps the single-pass model clean.
33
+ *
34
+ * Hardening:
35
+ * - rejects if the path resolves outside LUMIRA_ALLOWED_ROOTS
36
+ * - rejects with a clear message if the file does not exist
37
+ * - caps reads at MAX_LINES (50_000) to bound runtime on runaway JSONL
38
+ * - skips malformed JSON lines silently; missing/non-numeric fields are
39
+ * treated as zero (no NaN propagation)
40
+ */
41
+ export declare function aggregateStats(transcriptPath: string): Promise<SessionStats>;
@@ -0,0 +1,141 @@
1
+ import { promises as fs, createReadStream } from 'node:fs';
2
+ import { createInterface } from 'node:readline';
3
+ import { realpathSafe, isUnderAllowedRoot, LUMIRA_ALLOWED_ROOTS } from '../utils/path.js';
4
+ import { MAX_LINES } from './transcript.js';
5
+ function emptyStats() {
6
+ return {
7
+ sessionStart: null,
8
+ sessionEnd: null,
9
+ durationMs: 0,
10
+ inputTokens: 0,
11
+ outputTokens: 0,
12
+ cacheReadTokens: 0,
13
+ cacheCreationTokens: 0,
14
+ costUsd: 0,
15
+ hasCostData: false,
16
+ toolFrequency: {},
17
+ agentCount: 0,
18
+ errorCount: 0,
19
+ };
20
+ }
21
+ function safeNumber(v) {
22
+ return typeof v === 'number' && Number.isFinite(v) ? v : 0;
23
+ }
24
+ /**
25
+ * Stream-aggregate a Claude Code transcript JSONL into a SessionStats summary.
26
+ *
27
+ * Single-pass design: tokens, cost, tool frequency, agent count, and error
28
+ * count are all collected in one readline sweep. We deliberately do NOT call
29
+ * `parseTranscript` — that parser returns derived TranscriptData (with zombie
30
+ * detection, todo merging, subagents-dir override, etc.) but does not expose
31
+ * the raw token totals we need here. Re-streaming once is cheaper than
32
+ * parsing twice, and keeps the single-pass model clean.
33
+ *
34
+ * Hardening:
35
+ * - rejects if the path resolves outside LUMIRA_ALLOWED_ROOTS
36
+ * - rejects with a clear message if the file does not exist
37
+ * - caps reads at MAX_LINES (50_000) to bound runtime on runaway JSONL
38
+ * - skips malformed JSON lines silently; missing/non-numeric fields are
39
+ * treated as zero (no NaN propagation)
40
+ */
41
+ export async function aggregateStats(transcriptPath) {
42
+ // Canonicalise via realpathSafe before the allow-list check so that
43
+ // symlinks outside LUMIRA_ALLOWED_ROOTS can't be smuggled in via a path
44
+ // that *looks* safe at the string level.
45
+ const resolved = realpathSafe(transcriptPath);
46
+ if (!isUnderAllowedRoot(resolved, LUMIRA_ALLOWED_ROOTS)) {
47
+ throw new Error(`Path outside allowed roots: ${transcriptPath}`);
48
+ }
49
+ try {
50
+ await fs.stat(resolved);
51
+ }
52
+ catch {
53
+ throw new Error(`Transcript file not found: ${transcriptPath}`);
54
+ }
55
+ const stats = emptyStats();
56
+ let fileStream = null;
57
+ try {
58
+ fileStream = createReadStream(resolved);
59
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
60
+ let lineCount = 0;
61
+ for await (const line of rl) {
62
+ if (!line.trim())
63
+ continue;
64
+ if (++lineCount > MAX_LINES)
65
+ break;
66
+ let entry;
67
+ try {
68
+ entry = JSON.parse(line);
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ // Timestamp tracking — first valid ISO → sessionStart, last → sessionEnd.
74
+ const ts = entry.timestamp;
75
+ if (typeof ts === 'string') {
76
+ const ms = Date.parse(ts);
77
+ if (Number.isFinite(ms)) {
78
+ if (stats.sessionStart === null)
79
+ stats.sessionStart = ms;
80
+ stats.sessionEnd = ms;
81
+ }
82
+ }
83
+ const message = (entry.message ?? null);
84
+ // Usage block (assistant turns only). The mere presence of a usage
85
+ // payload flips hasCostData=true even if all counts are zero — see
86
+ // the "zero-cost-with-usage" test for why this matters.
87
+ if (entry.type === 'assistant' && message && typeof message === 'object') {
88
+ const usage = message.usage;
89
+ if (usage && typeof usage === 'object') {
90
+ stats.hasCostData = true;
91
+ stats.inputTokens += safeNumber(usage.input_tokens);
92
+ stats.outputTokens += safeNumber(usage.output_tokens);
93
+ stats.cacheReadTokens += safeNumber(usage.cache_read_input_tokens);
94
+ stats.cacheCreationTokens += safeNumber(usage.cache_creation_input_tokens);
95
+ }
96
+ }
97
+ // Cost lives at the top level of the entry in real transcripts
98
+ // (`entry.total_cost_usd`), but defensively accept it on `message`
99
+ // too — older recorders or future renames shouldn't silently zero out
100
+ // the dollar column.
101
+ //
102
+ // Precedence: top is authoritative, message is fallback ONLY when top
103
+ // is absent. We branch on field presence (`entry.total_cost_usd !==
104
+ // undefined`), not truthiness — Anthropic emits `total_cost_usd: 0` for
105
+ // fully-cached turns, and a `topCost || msgCost` short-circuit would
106
+ // incorrectly fall through to the message field in that case.
107
+ const topCost = safeNumber(entry.total_cost_usd);
108
+ const msgCost = message ? safeNumber(message.total_cost_usd) : 0;
109
+ const costContribution = entry.total_cost_usd !== undefined ? topCost : msgCost;
110
+ stats.costUsd += costContribution;
111
+ // Tool / agent / error extraction from the message.content array.
112
+ const content = message?.content;
113
+ if (!Array.isArray(content))
114
+ continue;
115
+ for (const block of content) {
116
+ if (!block || typeof block !== 'object')
117
+ continue;
118
+ const b = block;
119
+ if (b.type === 'tool_use' && typeof b.name === 'string') {
120
+ const name = b.name;
121
+ stats.toolFrequency[name] = (stats.toolFrequency[name] ?? 0) + 1;
122
+ if (name === 'Agent' || name === 'Task')
123
+ stats.agentCount += 1;
124
+ }
125
+ if (entry.type === 'user'
126
+ && b.type === 'tool_result'
127
+ && b.is_error === true) {
128
+ stats.errorCount += 1;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ finally {
134
+ fileStream?.destroy();
135
+ }
136
+ if (stats.sessionStart !== null && stats.sessionEnd !== null) {
137
+ stats.durationMs = Math.max(0, stats.sessionEnd - stats.sessionStart);
138
+ }
139
+ return stats;
140
+ }
141
+ //# sourceMappingURL=transcript-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-stats.js","sourceRoot":"","sources":["../../src/parsers/transcript-stats.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumira",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Real-time statusline HUD for Claude Code and Qwen Code — context bar, burn rate, themes, powerline, zero deps.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",