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 +0 -0
- package/package.json +6 -4
- package/tray-launcher.vbs +2 -0
- package/tray.js +38 -0
- package/tray.ps1 +79 -0
- package/watcher.js +13 -12
package/icon.ico
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getaimeter",
|
|
3
|
-
"version": "0.
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const parentSource = detectSource(parentFile);
|
|
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
|
-
}
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// Read first 10KB of the file to find entrypoint or IDE markers
|