@thisisayande/tokentracker 0.1.5
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/README.md +99 -0
- package/bin/tokentracker.js +143 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# TokenTracker
|
|
2
|
+
|
|
3
|
+
TokenTracker is a cross-platform desktop application that monitors AI provider quotas, rate limits, and spend statistics in real-time.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|:---:|:---:|
|
|
7
|
+
| <img src="/public/logos/image.png" width="290"> | <img src="/public/logos/image%20copy.png" width="290"> |
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multi-Provider Support** — Monitor Anthropic, OpenAI, OpenRouter, Groq, Ollama, and more
|
|
12
|
+
- **Real-Time Quota Tracking** — Live usage and rate limit data with configurable polling
|
|
13
|
+
- **Cost Analytics** — Per-provider and aggregate spend breakdowns with model-level detail
|
|
14
|
+
- **Secure Credential Storage** — API keys and browser cookie import
|
|
15
|
+
- **Browser Profile Import** — Pull authentication cookies from Chrome, Firefox, and Edge profiles
|
|
16
|
+
- **Dark / Light Themes** — macOS-inspired glassmorphic UI with smooth transitions
|
|
17
|
+
- **System Tray** — Runs quietly in the background; click to show/hide
|
|
18
|
+
- **Cross-Platform** — Linux, Windows, and macOS via Tauri 2
|
|
19
|
+
|
|
20
|
+
## Tech Stack
|
|
21
|
+
|
|
22
|
+
- **Frontend** — Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS 4
|
|
23
|
+
- **Backend** — Rust (Axum HTTP API server running as a bundled subprocess)
|
|
24
|
+
- **Desktop Runtime** — Tauri 2 (system tray, window management, process lifecycle)
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Frontend (Tauri/Next.js)
|
|
30
|
+
→ HTTP (localhost) → Rust Backend (Axum API, port 46727)
|
|
31
|
+
→ useProviders hook manages all state
|
|
32
|
+
→ mapProviderUsage / mapProviderCost transform raw JSON → typed objects
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Key Files
|
|
36
|
+
|
|
37
|
+
| Path | Purpose |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `backend/src/main.rs` | Rust HTTP server entry point (Axum router) |
|
|
40
|
+
| `backend/src/server/` | Request handlers for all API endpoints |
|
|
41
|
+
| `src-tauri/src/lib.rs` | Tauri app lifecycle, tray icon, window management |
|
|
42
|
+
| `src-tauri/src/backend.rs` | Backend process spawning, health check, lifecycle |
|
|
43
|
+
| `src/lib/apiClient.ts` | Frontend HTTP client (fetches port dynamically from Tauri) |
|
|
44
|
+
| `src/hooks/useProviders.ts` | Central hook owning all provider/cost/settings state |
|
|
45
|
+
| `src/lib/dataMapping.ts` | Raw JSON → typed object transformers + provider descriptors |
|
|
46
|
+
|
|
47
|
+
## Getting Started
|
|
48
|
+
|
|
49
|
+
### Prerequisites
|
|
50
|
+
|
|
51
|
+
- [Rust](https://rustup.rs/) (latest stable)
|
|
52
|
+
- [Node.js](https://nodejs.org/) 20+
|
|
53
|
+
|
|
54
|
+
### Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Install frontend dependencies
|
|
58
|
+
npm install
|
|
59
|
+
|
|
60
|
+
# Run the full Tauri app (frontend + bundled backend, hot-reload)
|
|
61
|
+
npm run tauri:dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The backend HTTP server starts automatically. The frontend connects via a port dynamically reported by Tauri (default `46727`).
|
|
65
|
+
|
|
66
|
+
### Production Build
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run tauri:build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Outputs native installers for your platform (AppImage, `.deb`, `.msi`, etc.).
|
|
73
|
+
|
|
74
|
+
### Running Tests
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Terminal 1 — start the dev server
|
|
78
|
+
npm run dev
|
|
79
|
+
|
|
80
|
+
# Terminal 2 — run smoke tests
|
|
81
|
+
npm run test:smoke
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Environment Variables
|
|
85
|
+
|
|
86
|
+
| Variable | Default | Description |
|
|
87
|
+
|----------|---------|-------------|
|
|
88
|
+
| `TOKEN_TRACKER_BACKEND_PORT` | `46727` | Port for the bundled Rust backend HTTP server |
|
|
89
|
+
|
|
90
|
+
## Local Caching
|
|
91
|
+
|
|
92
|
+
Usage and cost data is cached locally for offline reading and fast startup:
|
|
93
|
+
|
|
94
|
+
- **Linux** — `~/.config/CodexBar/cache.json`
|
|
95
|
+
- **Windows** — `%USERPROFILE%\.config\CodexBar\cache.json`
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import https from "https";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
import os from "os";
|
|
8
|
+
|
|
9
|
+
const REPO = "ayan-de/Token-Tracker";
|
|
10
|
+
const OWNER = "ayan-de";
|
|
11
|
+
const APP_NAME = "TokenTracker";
|
|
12
|
+
|
|
13
|
+
function getAssetUrl(version, filename) {
|
|
14
|
+
return `https://github.com/${OWNER}/${REPO}/releases/download/v${version}/${filename}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getLatestVersion() {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const req = https.get(
|
|
20
|
+
`https://api.github.com/repos/${REPO}/releases/latest`,
|
|
21
|
+
{ headers: { "User-Agent": "tokentracker-npm" } },
|
|
22
|
+
(res) => {
|
|
23
|
+
let data = "";
|
|
24
|
+
res.on("data", (chunk) => (data += chunk));
|
|
25
|
+
res.on("end", () => {
|
|
26
|
+
try {
|
|
27
|
+
const json = JSON.parse(data);
|
|
28
|
+
resolve(json.tag_name.replace("v", ""));
|
|
29
|
+
} catch {
|
|
30
|
+
reject(new Error("Failed to parse GitHub API response"));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
req.on("error", reject);
|
|
36
|
+
req.setTimeout(10000, () => {
|
|
37
|
+
req.destroy();
|
|
38
|
+
reject(new Error("Timeout reaching GitHub API"));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function downloadFile(url, dest) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const file = fs.createWriteStream(dest);
|
|
46
|
+
const protocol = url.startsWith("https") ? https : http;
|
|
47
|
+
const req = protocol.get(url, { headers: { "User-Agent": "tokentracker-npm" } }, (res) => {
|
|
48
|
+
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
49
|
+
file.close();
|
|
50
|
+
downloadFile(res.headers.location, dest).then(resolve).catch(reject);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (res.statusCode !== 200) {
|
|
54
|
+
file.close();
|
|
55
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
res.pipe(file);
|
|
59
|
+
file.on("finish", () => file.close(resolve));
|
|
60
|
+
});
|
|
61
|
+
req.on("error", (err) => {
|
|
62
|
+
file.close();
|
|
63
|
+
reject(err);
|
|
64
|
+
});
|
|
65
|
+
req.setTimeout(60000, () => {
|
|
66
|
+
req.destroy();
|
|
67
|
+
reject(new Error("Download timeout"));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function ensureInstalled(version) {
|
|
73
|
+
const platform = os.platform();
|
|
74
|
+
if (platform !== "linux") {
|
|
75
|
+
console.error("TokenTracker npm install only supports Linux currently.");
|
|
76
|
+
console.error("Download from: https://github.com/ayan-de/Token-Tracker/releases");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const arch = os.arch() === "x64" ? "amd64" : os.arch();
|
|
81
|
+
const filename = `TokenTracker_${version}_${arch}.deb`;
|
|
82
|
+
const url = getAssetUrl(version, filename);
|
|
83
|
+
const tmpDir = path.join(os.tmpdir(), "tokentracker-install");
|
|
84
|
+
const debPath = path.join(tmpDir, filename);
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(debPath)) {
|
|
89
|
+
console.log(`Downloading TokenTracker v${version}...`);
|
|
90
|
+
await downloadFile(url, debPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return debPath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function installAndRun() {
|
|
97
|
+
try {
|
|
98
|
+
const version = process.env.TOKEN_TRACKER_VERSION || await getLatestVersion();
|
|
99
|
+
const debPath = await ensureInstalled(version);
|
|
100
|
+
|
|
101
|
+
console.log(`Installing TokenTracker v${version}...`);
|
|
102
|
+
const dpkg = spawn("sudo", ["dpkg", "-i", debPath], { stdio: "inherit" });
|
|
103
|
+
dpkg.on("close", (code) => {
|
|
104
|
+
if (code === 0) {
|
|
105
|
+
console.log("TokenTracker installed successfully!");
|
|
106
|
+
console.log('Run "tokentracker" to start.');
|
|
107
|
+
} else {
|
|
108
|
+
console.error("Installation failed. Try running with sudo.");
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("Error:", err.message);
|
|
113
|
+
console.error("\nDownload manually: https://github.com/ayan-de/Token-Tracker/releases");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const args = process.argv.slice(2);
|
|
119
|
+
|
|
120
|
+
if (args.includes("--install") || args.includes("-i")) {
|
|
121
|
+
installAndRun();
|
|
122
|
+
} else {
|
|
123
|
+
// Direct execution path
|
|
124
|
+
const tokentrackerBin = "/usr/lib/TokenTracker/_up_/target/release/backend";
|
|
125
|
+
const tauriBin = "/usr/bin/tokentracker";
|
|
126
|
+
|
|
127
|
+
let binPath = null;
|
|
128
|
+
if (fs.existsSync(tauriBin)) binPath = tauriBin;
|
|
129
|
+
else if (fs.existsSync(tokentrackerBin)) binPath = tokentrackerBin;
|
|
130
|
+
|
|
131
|
+
if (!binPath) {
|
|
132
|
+
console.log("TokenTracker not found. Installing...");
|
|
133
|
+
installAndRun();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Spawn the actual app
|
|
138
|
+
const child = spawn(binPath, [], {
|
|
139
|
+
stdio: "inherit",
|
|
140
|
+
env: { ...process.env },
|
|
141
|
+
});
|
|
142
|
+
child.on("close", (code) => process.exit(code));
|
|
143
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thisisayande/tokentracker",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "AI Provider Quota Monitor - track usage, rate limits, and spend across 40+ LLM providers",
|
|
7
|
+
"bin": {
|
|
8
|
+
"tokentracker": "./bin/tokentracker.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "next dev",
|
|
12
|
+
"build": "next build",
|
|
13
|
+
"tauri": "tauri",
|
|
14
|
+
"tauri:dev": "tauri dev",
|
|
15
|
+
"tauri:build": "next build && tauri build",
|
|
16
|
+
"test:smoke": "playwright test",
|
|
17
|
+
"test:smoke:ui": "playwright test --ui",
|
|
18
|
+
"postinstall": "node scripts/postinstall.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin/",
|
|
22
|
+
"dist/"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org/"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@playwright/test": "^1.61.1",
|
|
30
|
+
"@tailwindcss/postcss": "^4.3.1",
|
|
31
|
+
"@tauri-apps/cli": "^2",
|
|
32
|
+
"@types/node": "^26.0.0",
|
|
33
|
+
"@types/react": "^19.2.17",
|
|
34
|
+
"autoprefixer": "^10.5.0",
|
|
35
|
+
"postcss": "^8.5.15",
|
|
36
|
+
"tailwindcss": "^4.3.1",
|
|
37
|
+
"typescript": "^6.0.3"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@tauri-apps/api": "^2.11.1",
|
|
41
|
+
"lucide-react": "^1.21.0",
|
|
42
|
+
"next": "^16.2.9",
|
|
43
|
+
"react": "^19.2.7",
|
|
44
|
+
"react-dom": "^19.2.7"
|
|
45
|
+
}
|
|
46
|
+
}
|