getaimeter 0.2.2 → 0.3.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/icon.ico ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getaimeter",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
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"
@@ -29,6 +29,10 @@
29
29
  "config.js",
30
30
  "state.js",
31
31
  "service.js",
32
+ "tray.js",
33
+ "tray.ps1",
34
+ "tray-launcher.vbs",
35
+ "icon.ico",
32
36
  "update-check.js",
33
37
  "README.md"
34
38
  ],
@@ -39,7 +43,5 @@
39
43
  "homepage": "https://getaimeter.com",
40
44
  "author": "Alejandro Ceja",
41
45
  "preferGlobal": true,
42
- "dependencies": {
43
- "systray2": "^2.1.4"
44
- }
46
+ "dependencies": {}
45
47
  }
@@ -0,0 +1,2 @@
1
+ Set objShell = CreateObject("WScript.Shell")
2
+ objShell.Run "powershell -NoProfile -ExecutionPolicy Bypass -STA -File """ & Replace(WScript.ScriptFullName, "tray-launcher.vbs", "tray.ps1") & """ -IconPath """ & Replace(WScript.ScriptFullName, "tray-launcher.vbs", "icon.ico") & """ -MaskedKey """ & WScript.Arguments(0) & """ -LogPath """ & WScript.Arguments(1) & """ -ParentPid " & WScript.Arguments(2), 0, False
package/tray.js ADDED
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { execFile } = require('child_process');
5
+ const { getApiKey, AIMETER_DIR } = require('./config');
6
+
7
+ let trayPid = null;
8
+
9
+ async function startTray(onStop) {
10
+ if (process.platform !== 'win32') return null;
11
+
12
+ const vbsPath = path.join(__dirname, 'tray-launcher.vbs');
13
+ const apiKey = getApiKey();
14
+ const maskedKey = apiKey ? `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}` : 'not set';
15
+ const logPath = path.join(AIMETER_DIR, 'watcher.log');
16
+
17
+ return new Promise((resolve) => {
18
+ // Use cscript to launch VBS which launches PowerShell hidden with -STA
19
+ execFile('cscript', [
20
+ '//Nologo',
21
+ vbsPath,
22
+ maskedKey,
23
+ logPath,
24
+ String(process.pid),
25
+ ], { windowsHide: true }, (err) => {
26
+ if (err) resolve(null);
27
+ });
28
+
29
+ // VBS returns immediately, give PS time to start
30
+ setTimeout(() => resolve(true), 3000);
31
+ });
32
+ }
33
+
34
+ function stopTray() {
35
+ // The tray PS1 watches parent PID and self-terminates
36
+ }
37
+
38
+ module.exports = { startTray, stopTray };
package/tray.ps1 ADDED
@@ -0,0 +1,79 @@
1
+ param(
2
+ [string]$IconPath,
3
+ [string]$MaskedKey,
4
+ [string]$LogPath,
5
+ [int]$ParentPid
6
+ )
7
+
8
+ Add-Type -AssemblyName System.Windows.Forms
9
+ Add-Type -AssemblyName System.Drawing
10
+
11
+ # Create tray icon
12
+ $tray = New-Object System.Windows.Forms.NotifyIcon
13
+
14
+ if ($IconPath -and (Test-Path $IconPath)) {
15
+ try {
16
+ $tray.Icon = New-Object System.Drawing.Icon($IconPath)
17
+ } catch {
18
+ $tray.Icon = [System.Drawing.SystemIcons]::Application
19
+ }
20
+ } else {
21
+ $tray.Icon = [System.Drawing.SystemIcons]::Application
22
+ }
23
+
24
+ $tray.Text = "AIMeter - Tracking AI usage"
25
+ $tray.Visible = $true
26
+
27
+ # Context menu
28
+ $menu = New-Object System.Windows.Forms.ContextMenuStrip
29
+
30
+ $header = $menu.Items.Add("AIMeter Watcher")
31
+ $header.Enabled = $false
32
+ $header.Font = New-Object System.Drawing.Font($header.Font, [System.Drawing.FontStyle]::Bold)
33
+
34
+ $menu.Items.Add("-")
35
+
36
+ $keyItem = $menu.Items.Add("Key: $MaskedKey")
37
+ $keyItem.Enabled = $false
38
+
39
+ $menu.Items.Add("-")
40
+
41
+ $dashboard = $menu.Items.Add("Open Dashboard")
42
+ $dashboard.Add_Click({ Start-Process "https://getaimeter.com/dashboard" })
43
+
44
+ $logs = $menu.Items.Add("View Logs")
45
+ $logs.Add_Click({ Start-Process notepad $LogPath })
46
+
47
+ $menu.Items.Add("-")
48
+
49
+ $stop = $menu.Items.Add("Stop Watcher")
50
+ $stop.Add_Click({
51
+ $tray.Visible = $false
52
+ $tray.Dispose()
53
+ if ($ParentPid -gt 0) {
54
+ try { Stop-Process -Id $ParentPid -Force -ErrorAction SilentlyContinue } catch {}
55
+ }
56
+ [System.Windows.Forms.Application]::Exit()
57
+ })
58
+
59
+ $tray.ContextMenuStrip = $menu
60
+
61
+ # Timer to check if parent process is still alive
62
+ $timer = New-Object System.Windows.Forms.Timer
63
+ $timer.Interval = 5000
64
+ $timer.Add_Tick({
65
+ if ($ParentPid -gt 0) {
66
+ try {
67
+ $p = Get-Process -Id $ParentPid -ErrorAction Stop
68
+ } catch {
69
+ # Parent died, clean up
70
+ $tray.Visible = $false
71
+ $tray.Dispose()
72
+ [System.Windows.Forms.Application]::Exit()
73
+ }
74
+ }
75
+ })
76
+ $timer.Start()
77
+
78
+ # Run message loop
79
+ [System.Windows.Forms.Application]::Run()
package/watcher.js CHANGED
@@ -43,22 +43,23 @@ function detectSource(filePath) {
43
43
  return 'desktop_app';
44
44
  }
45
45
 
46
- // For subagent files, inherit the parent session's source
46
+ // For subagent files (e.g. .../ceeb9217.../subagents/agent-xxx.jsonl),
47
+ // inherit the parent session's source. Parent JSONL is at the grandparent dir level.
47
48
  if (normalized.includes('/subagents/')) {
48
- const parentDir = normalized.replace(/\/[^/]+\/subagents\/.*$/, '');
49
- // Find the parent JSONL (same dir, .jsonl file)
50
- try {
51
- const parentDirNative = parentDir.replace(/\//g, path.sep);
52
- const entries = fs.readdirSync(parentDirNative);
53
- for (const entry of entries) {
54
- if (entry.endsWith('.jsonl')) {
55
- const parentFile = path.join(parentDirNative, entry);
56
- const parentSource = detectSource(parentFile); // recursive, will cache
49
+ // Extract session UUID dir: .../projects/PROJECT/SESSION_UUID/subagents/...
50
+ const match = normalized.match(/(.+)\/([^/]+)\/subagents\//);
51
+ if (match) {
52
+ const projectDir = match[1]; // .../projects/PROJECT
53
+ const sessionUuid = match[2]; // SESSION_UUID
54
+ const parentFile = path.join(projectDir.replace(/\//g, path.sep), sessionUuid + '.jsonl');
55
+ try {
56
+ if (fs.existsSync(parentFile)) {
57
+ const parentSource = detectSource(parentFile);
57
58
  _sourceCache.set(filePath, parentSource);
58
59
  return parentSource;
59
60
  }
60
- }
61
- } catch {}
61
+ } catch {}
62
+ }
62
63
  }
63
64
 
64
65
  // Read first 10KB of the file to find entrypoint or IDE markers