getaimeter 0.1.2 → 0.1.4

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 +32 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getaimeter",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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
@@ -138,6 +138,7 @@ async function reportEvents(events) {
138
138
  // ---------------------------------------------------------------------------
139
139
 
140
140
  const _debounceTimers = new Map();
141
+ const _processing = new Set();
141
142
 
142
143
  function handleFileChange(filePath) {
143
144
  // Only care about .jsonl files
@@ -146,12 +147,17 @@ function handleFileChange(filePath) {
146
147
  // Normalize path for consistent debounce key (Windows fires with mixed separators/casing)
147
148
  const normalizedKey = path.resolve(filePath).toLowerCase();
148
149
 
149
- // Debounce: wait 1000ms after last change before processing
150
+ // Skip if already being processed
151
+ if (_processing.has(normalizedKey)) return;
152
+
153
+ // Debounce: wait 2000ms after last change before processing
150
154
  const existing = _debounceTimers.get(normalizedKey);
151
155
  if (existing) clearTimeout(existing);
152
156
 
153
157
  _debounceTimers.set(normalizedKey, setTimeout(async () => {
154
158
  _debounceTimers.delete(normalizedKey);
159
+ if (_processing.has(normalizedKey)) return;
160
+ _processing.add(normalizedKey);
155
161
  try {
156
162
  const events = extractNewUsage(filePath);
157
163
  if (events.length > 0) {
@@ -160,8 +166,10 @@ function handleFileChange(filePath) {
160
166
  }
161
167
  } catch (err) {
162
168
  logError(`Processing ${filePath}:`, err.message);
169
+ } finally {
170
+ _processing.delete(normalizedKey);
163
171
  }
164
- }, 500));
172
+ }, 2000));
165
173
  }
166
174
 
167
175
  /**
@@ -240,7 +248,7 @@ function startWatching() {
240
248
  }
241
249
  saveState();
242
250
 
243
- // Set up fs.watch on each path
251
+ // Set up fs.watch on each path (works well on macOS/Linux)
244
252
  const watchers = [];
245
253
  for (const watchPath of watchPaths) {
246
254
  try {
@@ -255,18 +263,38 @@ function startWatching() {
255
263
  });
256
264
 
257
265
  watchers.push(w);
258
- log('Watching:', watchPath);
266
+ log('Watching (fs.watch):', watchPath);
259
267
  } catch (err) {
260
268
  logError(`Could not watch ${watchPath}:`, err.message);
261
269
  }
262
270
  }
263
271
 
272
+ // Polling fallback — fs.watch is unreliable on Windows for deeply nested dirs.
273
+ // Every 5 seconds, scan all known JSONL files for size changes.
274
+ const POLL_INTERVAL = 5_000;
275
+ const pollInterval = setInterval(() => {
276
+ for (const watchPath of watchPaths) {
277
+ const files = findJsonlFiles(watchPath);
278
+ for (const file of files) {
279
+ try {
280
+ const currentSize = fs.statSync(file).size;
281
+ const lastOffset = getOffset(file);
282
+ if (currentSize > lastOffset) {
283
+ handleFileChange(file);
284
+ }
285
+ } catch {}
286
+ }
287
+ }
288
+ }, POLL_INTERVAL);
289
+ log(`Polling every ${POLL_INTERVAL / 1000}s as fallback`);
290
+
264
291
  // Periodic state save
265
292
  const saveInterval = setInterval(() => saveState(), 30_000);
266
293
 
267
294
  // Return cleanup
268
295
  return () => {
269
296
  clearInterval(saveInterval);
297
+ clearInterval(pollInterval);
270
298
  for (const w of watchers) w.close();
271
299
  for (const t of _debounceTimers.values()) clearTimeout(t);
272
300
  saveState();