ai-burnrate-tool 1.0.2 → 1.0.3
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/core/parser.js +132 -4
- package/dist/public/assets/index-CkatfMIb.js +197 -0
- package/dist/public/assets/index-DhHuxkVD.css +1 -0
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/dist/public/assets/index-B9NR2QIc.css +0 -1
- package/dist/public/assets/index-_wmJph4q.js +0 -197
package/dist/core/parser.js
CHANGED
|
@@ -1,7 +1,85 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { calculateTurnCost, loadPricing } from './pricing.js';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
function loadAntigravityTitles(geminiDir) {
|
|
8
|
+
const titles = new Map();
|
|
9
|
+
let DatabaseSync;
|
|
10
|
+
try {
|
|
11
|
+
({ DatabaseSync } = require('node:sqlite'));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return titles;
|
|
15
|
+
}
|
|
16
|
+
const dbCandidates = [
|
|
17
|
+
path.join(geminiDir, 'antigravity-cli', 'conversation_summaries.db'),
|
|
18
|
+
path.join(geminiDir, 'antigravity', 'conversation_summaries.db'),
|
|
19
|
+
path.join(geminiDir, 'conversation_summaries.db'),
|
|
20
|
+
];
|
|
21
|
+
for (const dbPath of dbCandidates) {
|
|
22
|
+
if (!fs.existsSync(dbPath))
|
|
23
|
+
continue;
|
|
24
|
+
let db = null;
|
|
25
|
+
try {
|
|
26
|
+
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
27
|
+
const rows = db
|
|
28
|
+
.prepare("SELECT conversation_id, title FROM conversation_summaries WHERE title != ''")
|
|
29
|
+
.all();
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
if (row.conversation_id && row.title && !titles.has(row.conversation_id)) {
|
|
32
|
+
titles.set(row.conversation_id, row.title.trim());
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Ignore locked or incompatible SQLite DB
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
try {
|
|
41
|
+
db?.close?.();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// ignore close error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return titles;
|
|
49
|
+
}
|
|
50
|
+
function cleanChatTitle(raw, maxLen = 72) {
|
|
51
|
+
const userReqMatch = raw.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/i);
|
|
52
|
+
const base = userReqMatch ? userReqMatch[1] : raw;
|
|
53
|
+
const cleaned = base
|
|
54
|
+
.replace(/<local-command-[^>]*>[\s\S]*?<\/local-command-[^>]*>/gi, ' ')
|
|
55
|
+
.replace(/<command-[^>]*>[\s\S]*?<\/command-[^>]*>/gi, ' ')
|
|
56
|
+
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, ' ')
|
|
57
|
+
.replace(/<[^>]+>/g, ' ')
|
|
58
|
+
.replace(/^#+\s*/g, '')
|
|
59
|
+
.replace(/\s+/g, ' ')
|
|
60
|
+
.trim();
|
|
61
|
+
if (!cleaned)
|
|
62
|
+
return '';
|
|
63
|
+
return cleaned.length > maxLen ? `${cleaned.slice(0, maxLen - 1).trimEnd()}…` : cleaned;
|
|
64
|
+
}
|
|
65
|
+
function extractClaudeUserText(msgContent) {
|
|
66
|
+
if (typeof msgContent === 'string')
|
|
67
|
+
return msgContent;
|
|
68
|
+
if (Array.isArray(msgContent)) {
|
|
69
|
+
return msgContent
|
|
70
|
+
.filter((part) => part && part.type === 'text' && typeof part.text === 'string')
|
|
71
|
+
.map((part) => part.text)
|
|
72
|
+
.join(' ');
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
function formatSlugTitle(slug) {
|
|
77
|
+
return slug
|
|
78
|
+
.split(/[-_]+/)
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
81
|
+
.join(' ');
|
|
82
|
+
}
|
|
5
83
|
const fileCache = new Map();
|
|
6
84
|
export function getWatchRoots() {
|
|
7
85
|
const home = os.homedir();
|
|
@@ -126,7 +204,7 @@ function extractAntigravitySessionId(filePath) {
|
|
|
126
204
|
return match[1];
|
|
127
205
|
return path.basename(filePath, '.jsonl');
|
|
128
206
|
}
|
|
129
|
-
function parseAntigravityFile(filePath, stat, pricing) {
|
|
207
|
+
function parseAntigravityFile(filePath, stat, pricing, antigravityTitles) {
|
|
130
208
|
let content;
|
|
131
209
|
try {
|
|
132
210
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -139,6 +217,7 @@ function parseAntigravityFile(filePath, stat, pricing) {
|
|
|
139
217
|
let currentModel = 'Gemini 4 Argon';
|
|
140
218
|
let workspace = null;
|
|
141
219
|
let lastUserPromptChars = 0;
|
|
220
|
+
let firstUserTitle = '';
|
|
142
221
|
const turnHistory = [];
|
|
143
222
|
let startedAt = '';
|
|
144
223
|
let lastActiveAt = '';
|
|
@@ -164,6 +243,11 @@ function parseAntigravityFile(filePath, stat, pricing) {
|
|
|
164
243
|
lastActiveAt = timestamp;
|
|
165
244
|
if (obj.type === 'USER_INPUT' && typeof obj.content === 'string') {
|
|
166
245
|
lastUserPromptChars = obj.content.length;
|
|
246
|
+
if (!firstUserTitle) {
|
|
247
|
+
const candidateTitle = cleanChatTitle(obj.content);
|
|
248
|
+
if (candidateTitle)
|
|
249
|
+
firstUserTitle = candidateTitle;
|
|
250
|
+
}
|
|
167
251
|
const modelMatch = obj.content.match(/Model Selection`?\s+from\s+[^\r\n]+?\s+to\s+([^\r\n]+?)\.(?:\s|$)/i);
|
|
168
252
|
if (modelMatch && modelMatch[1]) {
|
|
169
253
|
currentModel = modelMatch[1].trim();
|
|
@@ -245,8 +329,10 @@ function parseAntigravityFile(filePath, stat, pricing) {
|
|
|
245
329
|
}
|
|
246
330
|
if (turnHistory.length === 0)
|
|
247
331
|
return null;
|
|
332
|
+
const sessionTitle = antigravityTitles?.get(sessionId) || firstUserTitle || sessionId;
|
|
248
333
|
return summarizeSession({
|
|
249
334
|
sessionId,
|
|
335
|
+
sessionTitle,
|
|
250
336
|
source: 'antigravity',
|
|
251
337
|
workspace: toRelativeHomePath(workspace || 'Global / CLI'),
|
|
252
338
|
filePath: toRelativeHomePath(filePath),
|
|
@@ -269,6 +355,11 @@ function parseClaudeFile(filePath, stat, pricing) {
|
|
|
269
355
|
let startedAt = '';
|
|
270
356
|
let lastActiveAt = '';
|
|
271
357
|
let lastUserPromptChars = 0;
|
|
358
|
+
let customTitle = '';
|
|
359
|
+
let summaryTitle = '';
|
|
360
|
+
let slugTitle = '';
|
|
361
|
+
let firstUserTitle = '';
|
|
362
|
+
let fallbackSlashTitle = '';
|
|
272
363
|
const turnMap = new Map();
|
|
273
364
|
let fallbackCounter = 0;
|
|
274
365
|
for (const rawLine of lines) {
|
|
@@ -288,6 +379,15 @@ function parseClaudeFile(filePath, stat, pricing) {
|
|
|
288
379
|
else if (typeof obj.session_id === 'string' && obj.session_id) {
|
|
289
380
|
sessionId = obj.session_id;
|
|
290
381
|
}
|
|
382
|
+
if (typeof obj.customTitle === 'string' && obj.customTitle.trim()) {
|
|
383
|
+
customTitle = cleanChatTitle(obj.customTitle);
|
|
384
|
+
}
|
|
385
|
+
if (obj.type === 'summary' && typeof obj.summary === 'string' && obj.summary.trim()) {
|
|
386
|
+
summaryTitle = cleanChatTitle(obj.summary);
|
|
387
|
+
}
|
|
388
|
+
if (!slugTitle && typeof obj.slug === 'string' && obj.slug.trim()) {
|
|
389
|
+
slugTitle = obj.slug.trim();
|
|
390
|
+
}
|
|
291
391
|
if (typeof obj.cwd === 'string' && obj.cwd.trim()) {
|
|
292
392
|
workspace = obj.cwd.trim();
|
|
293
393
|
}
|
|
@@ -306,6 +406,19 @@ function parseClaudeFile(filePath, stat, pricing) {
|
|
|
306
406
|
const msgContent = obj.message?.content ?? obj.content;
|
|
307
407
|
const text = typeof msgContent === 'string' ? msgContent : JSON.stringify(msgContent ?? '');
|
|
308
408
|
lastUserPromptChars = text.length;
|
|
409
|
+
if (!firstUserTitle) {
|
|
410
|
+
const userText = extractClaudeUserText(msgContent);
|
|
411
|
+
const candidate = cleanChatTitle(userText);
|
|
412
|
+
if (candidate && candidate !== '[Request interrupted by user]') {
|
|
413
|
+
if (candidate.startsWith('/')) {
|
|
414
|
+
if (!fallbackSlashTitle)
|
|
415
|
+
fallbackSlashTitle = candidate;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
firstUserTitle = candidate;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
309
422
|
continue;
|
|
310
423
|
}
|
|
311
424
|
const isAssistant = obj.type === 'assistant' || obj.message?.role === 'assistant';
|
|
@@ -375,8 +488,15 @@ function parseClaudeFile(filePath, stat, pricing) {
|
|
|
375
488
|
if (turnHistory.length === 0)
|
|
376
489
|
return null;
|
|
377
490
|
const resolvedWs = workspace || decodeClaudeProjectFolder(filePath);
|
|
491
|
+
const sessionTitle = customTitle ||
|
|
492
|
+
summaryTitle ||
|
|
493
|
+
firstUserTitle ||
|
|
494
|
+
(slugTitle ? formatSlugTitle(slugTitle) : '') ||
|
|
495
|
+
fallbackSlashTitle ||
|
|
496
|
+
sessionId;
|
|
378
497
|
return summarizeSession({
|
|
379
498
|
sessionId,
|
|
499
|
+
sessionTitle,
|
|
380
500
|
source: 'claude',
|
|
381
501
|
workspace: toRelativeHomePath(resolvedWs),
|
|
382
502
|
filePath: toRelativeHomePath(filePath),
|
|
@@ -423,6 +543,7 @@ function summarizeSession(params) {
|
|
|
423
543
|
}
|
|
424
544
|
return {
|
|
425
545
|
sessionId: params.sessionId,
|
|
546
|
+
sessionTitle: params.sessionTitle,
|
|
426
547
|
source: params.source,
|
|
427
548
|
workspace: params.workspace,
|
|
428
549
|
workspaceName: formatWorkspaceName(params.workspace),
|
|
@@ -443,7 +564,7 @@ function summarizeSession(params) {
|
|
|
443
564
|
turnHistory: params.turnHistory,
|
|
444
565
|
};
|
|
445
566
|
}
|
|
446
|
-
export function parseSingleFile(filePath, source, pricing = loadPricing()) {
|
|
567
|
+
export function parseSingleFile(filePath, source, pricing = loadPricing(), antigravityTitles) {
|
|
447
568
|
if (shouldSkipJsonlFile(filePath, source))
|
|
448
569
|
return null;
|
|
449
570
|
let stat;
|
|
@@ -454,12 +575,18 @@ export function parseSingleFile(filePath, source, pricing = loadPricing()) {
|
|
|
454
575
|
fileCache.delete(filePath);
|
|
455
576
|
return null;
|
|
456
577
|
}
|
|
578
|
+
const resolvedTitles = source === 'antigravity' ? antigravityTitles || loadAntigravityTitles(getWatchRoots().geminiDir) : undefined;
|
|
457
579
|
const cached = fileCache.get(filePath);
|
|
458
580
|
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
581
|
+
if (cached.session && resolvedTitles) {
|
|
582
|
+
const dbTitle = resolvedTitles.get(cached.session.sessionId);
|
|
583
|
+
if (dbTitle)
|
|
584
|
+
cached.session.sessionTitle = dbTitle;
|
|
585
|
+
}
|
|
459
586
|
return cached.session;
|
|
460
587
|
}
|
|
461
588
|
const session = source === 'antigravity'
|
|
462
|
-
? parseAntigravityFile(filePath, stat, pricing)
|
|
589
|
+
? parseAntigravityFile(filePath, stat, pricing, resolvedTitles)
|
|
463
590
|
: parseClaudeFile(filePath, stat, pricing);
|
|
464
591
|
fileCache.set(filePath, {
|
|
465
592
|
mtimeMs: stat.mtimeMs,
|
|
@@ -474,11 +601,12 @@ export function scanAllLogs(forceClearCache = false) {
|
|
|
474
601
|
}
|
|
475
602
|
const pricing = loadPricing(forceClearCache);
|
|
476
603
|
const { geminiDir, claudeDir } = getWatchRoots();
|
|
604
|
+
const antigravityTitles = loadAntigravityTitles(geminiDir);
|
|
477
605
|
const geminiFiles = collectJsonlFiles(geminiDir, 'antigravity');
|
|
478
606
|
const claudeFiles = collectJsonlFiles(claudeDir, 'claude');
|
|
479
607
|
const sessions = [];
|
|
480
608
|
for (const file of geminiFiles) {
|
|
481
|
-
const session = parseSingleFile(file, 'antigravity', pricing);
|
|
609
|
+
const session = parseSingleFile(file, 'antigravity', pricing, antigravityTitles);
|
|
482
610
|
if (session)
|
|
483
611
|
sessions.push(session);
|
|
484
612
|
}
|