getaimeter 0.7.9 → 0.8.1

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 (3) hide show
  1. package/package.json +1 -1
  2. package/tray.ps1 +9 -4
  3. package/watcher.js +56 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getaimeter",
3
- "version": "0.7.9",
3
+ "version": "0.8.1",
4
4
  "description": "Track AI coding costs across Claude, Cursor, Codex, and Gemini. Optimization recommendations that cut costs by 30%.",
5
5
  "bin": {
6
6
  "aimeter": "cli.js"
package/tray.ps1 CHANGED
@@ -55,10 +55,15 @@ $dashboard.Add_Click({ Start-Process "https://getaimeter.com/dashboard" })
55
55
  $logs = $menu.Items.Add("View Logs")
56
56
  $viewerScript = Join-Path (Split-Path $MyInvocation.MyCommand.Path) 'log-viewer.ps1'
57
57
  $logs.Add_Click({
58
- $vbsCmd = "CreateObject(""WScript.Shell"").Run ""powershell -NoProfile -ExecutionPolicy Bypass -STA -WindowStyle Hidden -File """"$viewerScript"""" -LogPath """"$LogPath"""""", 0, False"
59
- $tempVbs = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'aimeter-logviewer.vbs')
60
- [System.IO.File]::WriteAllText($tempVbs, $vbsCmd)
61
- Start-Process 'wscript' -ArgumentList @($tempVbs) -WindowStyle Hidden
58
+ # Launch log viewer directly via PowerShell avoids VBS quoting issues with paths containing spaces
59
+ Start-Process "powershell" -ArgumentList @(
60
+ "-NoProfile",
61
+ "-ExecutionPolicy", "Bypass",
62
+ "-STA",
63
+ "-WindowStyle", "Hidden",
64
+ "-File", $viewerScript,
65
+ "-LogPath", $LogPath
66
+ ) -WindowStyle Hidden
62
67
  })
63
68
 
64
69
  $menu.Items.Add("-")
package/watcher.js CHANGED
@@ -67,8 +67,28 @@ function detectSource(filePath) {
67
67
  }
68
68
 
69
69
  if (normalized.includes('.copilot/') || normalized.includes('/copilot/')) {
70
- _sourceCache.set(filePath, 'copilot_cli');
71
- return 'copilot_cli';
70
+ // Check session.start or vscode.metadata.json to detect CLI vs VS Code
71
+ let copilotSource = 'copilot_cli';
72
+ try {
73
+ // Check for vscode.metadata.json alongside events.jsonl
74
+ const dir = path.dirname(filePath);
75
+ if (fs.existsSync(path.join(dir, 'vscode.metadata.json'))) {
76
+ copilotSource = 'copilot_vscode';
77
+ } else {
78
+ // Read first line for session.start producer field
79
+ const fd = fs.openSync(filePath, 'r');
80
+ const buf = Buffer.alloc(Math.min(1024, fs.fstatSync(fd).size));
81
+ fs.readSync(fd, buf, 0, buf.length, 0);
82
+ fs.closeSync(fd);
83
+ const firstLine = buf.toString('utf8').split('\n')[0];
84
+ const meta = JSON.parse(firstLine);
85
+ if (meta.type === 'session.start' && meta.data?.producer === 'copilot-agent') {
86
+ copilotSource = 'copilot_vscode'; // VS Code agent sessions have "copilot-agent" producer
87
+ }
88
+ }
89
+ } catch {}
90
+ _sourceCache.set(filePath, copilotSource);
91
+ return copilotSource;
72
92
  }
73
93
  if (normalized.includes('.gemini/') || normalized.includes('/gemini/')) {
74
94
  _sourceCache.set(filePath, 'gemini_cli');
@@ -236,8 +256,7 @@ function extractNewUsage(filePath) {
236
256
  continue;
237
257
  }
238
258
 
239
- // ── Copilot CLI format ────────────────────────────────────────
240
- // Copilot events: { type: "token_usage", input_tokens, output_tokens, reasoning_tokens, model }
259
+ // ── Copilot format (old: token_usage, new: session.shutdown with modelMetrics) ──
241
260
  if (obj.type === 'token_usage' && (obj.input_tokens !== undefined || obj.output_tokens !== undefined)) {
242
261
  const copilotModel = obj.model || 'copilot';
243
262
  const hashKey = `${filePath}:${lineOffset}:copilot:${obj.input_tokens || 0}:${obj.output_tokens || 0}`;
@@ -247,7 +266,7 @@ function extractNewUsage(filePath) {
247
266
  usageEvents.push({
248
267
  provider: 'github',
249
268
  model: copilotModel,
250
- source: 'copilot_cli',
269
+ source: detectSource(filePath),
251
270
  inputTokens: obj.input_tokens || 0,
252
271
  outputTokens: obj.output_tokens || 0,
253
272
  thinkingTokens: obj.reasoning_tokens || 0,
@@ -257,6 +276,38 @@ function extractNewUsage(filePath) {
257
276
  continue;
258
277
  }
259
278
 
279
+ // GitHub Copilot agent: session.shutdown has per-model usage in data.modelMetrics
280
+ if (obj.type === 'session.shutdown' && obj.data?.modelMetrics) {
281
+ for (const [model, metrics] of Object.entries(obj.data.modelMetrics)) {
282
+ const u = metrics.usage || {};
283
+ const inputTokens = u.inputTokens || 0;
284
+ const outputTokens = u.outputTokens || 0;
285
+ if (inputTokens === 0 && outputTokens === 0) continue;
286
+
287
+ const hashKey = `${filePath}:copilot-shutdown:${model}:${inputTokens}:${outputTokens}`;
288
+ const hash = crypto.createHash('md5').update(hashKey).digest('hex');
289
+ if (isDuplicate(hash)) continue;
290
+
291
+ // Determine provider from model name
292
+ let provider = 'github';
293
+ if (model.includes('claude') || model.includes('anthropic')) provider = 'anthropic';
294
+ else if (model.includes('gpt') || model.includes('o3') || model.includes('o4')) provider = 'openai';
295
+ else if (model.includes('gemini')) provider = 'google';
296
+
297
+ usageEvents.push({
298
+ provider,
299
+ model,
300
+ source: detectSource(filePath),
301
+ inputTokens,
302
+ outputTokens,
303
+ thinkingTokens: 0,
304
+ cacheReadTokens: u.cacheReadTokens || 0,
305
+ cacheWriteTokens: u.cacheWriteTokens || 0,
306
+ });
307
+ }
308
+ continue;
309
+ }
310
+
260
311
  // ── Gemini CLI format ─────────────────────────────────────────
261
312
  // Gemini events may contain usageMetadata: { promptTokenCount, candidatesTokenCount, totalTokenCount }
262
313
  if (obj.usageMetadata && (obj.usageMetadata.promptTokenCount || obj.usageMetadata.candidatesTokenCount)) {