getaimeter 0.9.0 → 0.10.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/package.json +1 -1
- package/watcher.js +86 -0
package/package.json
CHANGED
package/watcher.js
CHANGED
|
@@ -35,6 +35,9 @@ function logError(...args) {
|
|
|
35
35
|
// Cache detected sources per file to avoid re-reading headers
|
|
36
36
|
const _sourceCache = new Map();
|
|
37
37
|
|
|
38
|
+
// Cache conversation metadata per file: { conversationId, projectPath }
|
|
39
|
+
const _convMetaCache = new Map();
|
|
40
|
+
|
|
38
41
|
// Track cumulative token counts per file for Codex CLI (which reports cumulative, not delta)
|
|
39
42
|
const _codexCumulative = {};
|
|
40
43
|
|
|
@@ -140,6 +143,76 @@ function detectSource(filePath) {
|
|
|
140
143
|
return source;
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Conversation metadata extraction
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extract conversation ID and project path from a file.
|
|
152
|
+
* - conversationId: file basename without extension (unique per session)
|
|
153
|
+
* - projectPath: cwd from the JSONL header (Claude Code stores this in init/system messages)
|
|
154
|
+
*
|
|
155
|
+
* For subagent files, the conversation ID is inherited from the parent session.
|
|
156
|
+
*/
|
|
157
|
+
function getConversationMeta(filePath) {
|
|
158
|
+
if (_convMetaCache.has(filePath)) return _convMetaCache.get(filePath);
|
|
159
|
+
|
|
160
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
161
|
+
|
|
162
|
+
// Conversation ID = file basename without extension
|
|
163
|
+
let conversationId = path.basename(filePath, '.jsonl');
|
|
164
|
+
|
|
165
|
+
// For subagent files, use the parent session UUID as conversation ID
|
|
166
|
+
const subagentMatch = normalized.match(/\/([^/]+)\/subagents\//);
|
|
167
|
+
if (subagentMatch) {
|
|
168
|
+
conversationId = subagentMatch[1]; // parent session UUID
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Extract project path (cwd) from the first few lines of the file
|
|
172
|
+
let projectPath = null;
|
|
173
|
+
try {
|
|
174
|
+
const fd = fs.openSync(filePath, 'r');
|
|
175
|
+
const buf = Buffer.alloc(Math.min(8192, fs.fstatSync(fd).size));
|
|
176
|
+
fs.readSync(fd, buf, 0, buf.length, 0);
|
|
177
|
+
fs.closeSync(fd);
|
|
178
|
+
const header = buf.toString('utf8');
|
|
179
|
+
|
|
180
|
+
for (const line of header.split('\n').slice(0, 10)) {
|
|
181
|
+
if (!line.trim()) continue;
|
|
182
|
+
try {
|
|
183
|
+
const obj = JSON.parse(line.trim());
|
|
184
|
+
// Claude Code: type=system or init messages have cwd
|
|
185
|
+
if (obj.cwd) {
|
|
186
|
+
projectPath = obj.cwd;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
// Some formats nest it in message or data
|
|
190
|
+
if (obj.message?.cwd) {
|
|
191
|
+
projectPath = obj.message.cwd;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
// Codex: session_meta may have cwd
|
|
195
|
+
if (obj.type === 'session_meta' && obj.payload?.cwd) {
|
|
196
|
+
projectPath = obj.payload.cwd;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
} catch {}
|
|
202
|
+
|
|
203
|
+
// Shorten project path to just the last directory name for privacy/brevity
|
|
204
|
+
if (projectPath) {
|
|
205
|
+
projectPath = projectPath.replace(/\\/g, '/').replace(/\/$/, '');
|
|
206
|
+
// Keep last 2 path segments: "User/project" or just "project"
|
|
207
|
+
const parts = projectPath.split('/');
|
|
208
|
+
projectPath = parts.length > 1 ? parts.slice(-2).join('/') : parts[parts.length - 1];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const meta = { conversationId, projectPath };
|
|
212
|
+
_convMetaCache.set(filePath, meta);
|
|
213
|
+
return meta;
|
|
214
|
+
}
|
|
215
|
+
|
|
143
216
|
// ---------------------------------------------------------------------------
|
|
144
217
|
// JSONL parsing — extract usage from new bytes in a transcript file
|
|
145
218
|
// ---------------------------------------------------------------------------
|
|
@@ -166,6 +239,7 @@ function extractNewUsage(filePath) {
|
|
|
166
239
|
if (lastOffset > 0 && lines.length > 0) lines.shift();
|
|
167
240
|
|
|
168
241
|
const usageEvents = [];
|
|
242
|
+
const convMeta = getConversationMeta(filePath);
|
|
169
243
|
let lineOffset = lastOffset;
|
|
170
244
|
let pendingThinkingChars = 0; // Track thinking chars from streaming progress messages
|
|
171
245
|
|
|
@@ -252,6 +326,8 @@ function extractNewUsage(filePath) {
|
|
|
252
326
|
thinkingTokens: deltaReasoning,
|
|
253
327
|
cacheReadTokens: cachedTokens,
|
|
254
328
|
cacheWriteTokens: 0,
|
|
329
|
+
conversationId: convMeta.conversationId,
|
|
330
|
+
projectPath: convMeta.projectPath,
|
|
255
331
|
});
|
|
256
332
|
continue;
|
|
257
333
|
}
|
|
@@ -272,6 +348,8 @@ function extractNewUsage(filePath) {
|
|
|
272
348
|
thinkingTokens: obj.reasoning_tokens || 0,
|
|
273
349
|
cacheReadTokens: 0,
|
|
274
350
|
cacheWriteTokens: 0,
|
|
351
|
+
conversationId: convMeta.conversationId,
|
|
352
|
+
projectPath: convMeta.projectPath,
|
|
275
353
|
});
|
|
276
354
|
continue;
|
|
277
355
|
}
|
|
@@ -303,6 +381,8 @@ function extractNewUsage(filePath) {
|
|
|
303
381
|
thinkingTokens: 0,
|
|
304
382
|
cacheReadTokens: u.cacheReadTokens || 0,
|
|
305
383
|
cacheWriteTokens: u.cacheWriteTokens || 0,
|
|
384
|
+
conversationId: convMeta.conversationId,
|
|
385
|
+
projectPath: convMeta.projectPath,
|
|
306
386
|
});
|
|
307
387
|
}
|
|
308
388
|
continue;
|
|
@@ -326,6 +406,8 @@ function extractNewUsage(filePath) {
|
|
|
326
406
|
thinkingTokens: um.thoughtsTokenCount || 0,
|
|
327
407
|
cacheReadTokens: um.cachedContentTokenCount || 0,
|
|
328
408
|
cacheWriteTokens: 0,
|
|
409
|
+
conversationId: convMeta.conversationId,
|
|
410
|
+
projectPath: convMeta.projectPath,
|
|
329
411
|
});
|
|
330
412
|
continue;
|
|
331
413
|
}
|
|
@@ -384,6 +466,8 @@ function extractNewUsage(filePath) {
|
|
|
384
466
|
thinkingTokens: estimatedThinkingTokens,
|
|
385
467
|
cacheReadTokens: u.cache_read_input_tokens || 0,
|
|
386
468
|
cacheWriteTokens: u.cache_creation_input_tokens || 0,
|
|
469
|
+
conversationId: convMeta.conversationId,
|
|
470
|
+
projectPath: convMeta.projectPath,
|
|
387
471
|
});
|
|
388
472
|
}
|
|
389
473
|
|
|
@@ -575,6 +659,8 @@ function extractCursorUsage(dbPath) {
|
|
|
575
659
|
thinkingTokens: 0,
|
|
576
660
|
cacheReadTokens: 0,
|
|
577
661
|
cacheWriteTokens: 0,
|
|
662
|
+
conversationId: conv.composerId,
|
|
663
|
+
projectPath: null,
|
|
578
664
|
});
|
|
579
665
|
} catch {}
|
|
580
666
|
}
|