getaimeter 0.1.1 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/watcher.js +33 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getaimeter",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Track your Claude AI usage across CLI, VS Code, and Desktop App. One command to start.",
5
5
  "bin": {
6
6
  "aimeter": "cli.js"
package/watcher.js CHANGED
@@ -72,9 +72,11 @@ function extractNewUsage(filePath) {
72
72
  if (lastOffset > 0 && lines.length > 0) lines.shift();
73
73
 
74
74
  const usageEvents = [];
75
+ let lineOffset = lastOffset;
75
76
 
76
77
  for (const line of lines) {
77
78
  const trimmed = line.trim();
79
+ lineOffset += Buffer.byteLength(line + '\n', 'utf8');
78
80
  if (!trimmed) continue;
79
81
 
80
82
  let obj;
@@ -85,9 +87,9 @@ function extractNewUsage(filePath) {
85
87
  const u = obj.message.usage;
86
88
  const model = obj.message.model || 'unknown';
87
89
 
88
- // Build dedup hash
90
+ // Build dedup hash — include line offset for uniqueness
89
91
  const hash = crypto.createHash('md5')
90
- .update(`${filePath}:${model}:${u.input_tokens || 0}:${u.output_tokens || 0}:${currentSize}`)
92
+ .update(`${filePath}:${lineOffset}:${model}:${u.input_tokens || 0}:${u.output_tokens || 0}`)
91
93
  .digest('hex');
92
94
 
93
95
  if (isDuplicate(hash)) continue;
@@ -141,12 +143,15 @@ function handleFileChange(filePath) {
141
143
  // Only care about .jsonl files
142
144
  if (!filePath.endsWith('.jsonl')) return;
143
145
 
144
- // Debounce: wait 500ms after last change before processing
145
- const existing = _debounceTimers.get(filePath);
146
+ // Normalize path for consistent debounce key (Windows fires with mixed separators/casing)
147
+ const normalizedKey = path.resolve(filePath).toLowerCase();
148
+
149
+ // Debounce: wait 1000ms after last change before processing
150
+ const existing = _debounceTimers.get(normalizedKey);
146
151
  if (existing) clearTimeout(existing);
147
152
 
148
- _debounceTimers.set(filePath, setTimeout(async () => {
149
- _debounceTimers.delete(filePath);
153
+ _debounceTimers.set(normalizedKey, setTimeout(async () => {
154
+ _debounceTimers.delete(normalizedKey);
150
155
  try {
151
156
  const events = extractNewUsage(filePath);
152
157
  if (events.length > 0) {
@@ -235,7 +240,7 @@ function startWatching() {
235
240
  }
236
241
  saveState();
237
242
 
238
- // Set up fs.watch on each path
243
+ // Set up fs.watch on each path (works well on macOS/Linux)
239
244
  const watchers = [];
240
245
  for (const watchPath of watchPaths) {
241
246
  try {
@@ -250,18 +255,38 @@ function startWatching() {
250
255
  });
251
256
 
252
257
  watchers.push(w);
253
- log('Watching:', watchPath);
258
+ log('Watching (fs.watch):', watchPath);
254
259
  } catch (err) {
255
260
  logError(`Could not watch ${watchPath}:`, err.message);
256
261
  }
257
262
  }
258
263
 
264
+ // Polling fallback — fs.watch is unreliable on Windows for deeply nested dirs.
265
+ // Every 5 seconds, scan all known JSONL files for size changes.
266
+ const POLL_INTERVAL = 5_000;
267
+ const pollInterval = setInterval(() => {
268
+ for (const watchPath of watchPaths) {
269
+ const files = findJsonlFiles(watchPath);
270
+ for (const file of files) {
271
+ try {
272
+ const currentSize = fs.statSync(file).size;
273
+ const lastOffset = getOffset(file);
274
+ if (currentSize > lastOffset) {
275
+ handleFileChange(file);
276
+ }
277
+ } catch {}
278
+ }
279
+ }
280
+ }, POLL_INTERVAL);
281
+ log(`Polling every ${POLL_INTERVAL / 1000}s as fallback`);
282
+
259
283
  // Periodic state save
260
284
  const saveInterval = setInterval(() => saveState(), 30_000);
261
285
 
262
286
  // Return cleanup
263
287
  return () => {
264
288
  clearInterval(saveInterval);
289
+ clearInterval(pollInterval);
265
290
  for (const w of watchers) w.close();
266
291
  for (const t of _debounceTimers.values()) clearTimeout(t);
267
292
  saveState();