agentlytics 0.1.9 → 0.1.11
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 +59 -32
- package/cache.js +76 -14
- package/editors/base.js +0 -116
- package/editors/codex.js +0 -11
- package/editors/index.js +1 -9
- package/editors/opencode.js +1 -1
- package/editors/windsurf.js +3 -3
- package/editors/zed.js +3 -3
- package/index.js +3 -1
- package/package.json +1 -3
- package/pricing.json +805 -70
- package/relay-client.js +0 -4
- package/server.js +10 -1
- package/share-image.js +288 -75
- package/ui/src/App.jsx +1 -2
- package/ui/src/components/LiveFeed.jsx +0 -10
- package/ui/src/components/MessageRenderer.jsx +0 -19
- package/ui/src/components/ShareModal.jsx +273 -0
- package/ui/src/lib/api.js +11 -7
- package/ui/src/pages/Dashboard.jsx +7 -54
- package/ui/src/pages/DeepAnalysis.jsx +2 -2
- package/ui/src/pages/ProjectDetail.jsx +0 -1
- package/ui/src/pages/Projects.jsx +1 -1
- package/ui/src/pages/RelayDashboard.jsx +2 -2
- package/ui/src/pages/RelayUserDetail.jsx +1 -3
- package/ui/src/components/EditorBreakdown.jsx +0 -22
- package/ui/src/components/ModelBreakdown.jsx +0 -23
- package/ui/src/pages/ChatDetail.jsx +0 -107
- package/ui/src/pages/RelaySessionDetail.jsx +0 -32
package/README.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<h1 align="center">Agentlytics</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
9
|
-
<sub>
|
|
8
|
+
<strong>Your Cursor, Windsurf, Claude Code sessions — analyzed, unified, tracked.</strong><br>
|
|
9
|
+
<sub>One command to turn scattered AI conversations from <b>14 editors</b> into a unified analytics dashboard.<br>Sessions, costs, models, tools — finally in one place. 100% local.</sub>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -22,57 +22,84 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
## The Problem
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
You switch between Cursor, Windsurf, Claude Code, VS Code Copilot, and more — each with its own siloed conversation history.
|
|
28
|
+
|
|
29
|
+
- ✗ Sessions scattered across editors, no unified view
|
|
30
|
+
- ✗ No idea how much you're spending on AI tokens
|
|
31
|
+
- ✗ Can't compare which editor is more effective
|
|
32
|
+
- ✗ Can't search across all your AI conversations
|
|
33
|
+
- ✗ No way to share session context with your team
|
|
34
|
+
|
|
35
|
+
## The Solution
|
|
36
|
+
|
|
37
|
+
**One command. Full picture. All local.**
|
|
28
38
|
|
|
29
39
|
```bash
|
|
30
40
|
npx agentlytics
|
|
31
41
|
```
|
|
32
42
|
|
|
33
|
-
Opens at **http://localhost:4637**. Requires Node.js ≥ 20.19 or ≥ 22.12, macOS.
|
|
43
|
+
Opens at **http://localhost:4637**. Requires Node.js ≥ 20.19 or ≥ 22.12, macOS. No data ever leaves your machine.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
$ npx agentlytics
|
|
47
|
+
|
|
48
|
+
(● ●) [● ●] Agentlytics
|
|
49
|
+
{● ●} <● ●> Unified analytics for your AI coding agents
|
|
50
|
+
|
|
51
|
+
Looking for AI coding agents...
|
|
52
|
+
✓ Cursor 498 sessions
|
|
53
|
+
✓ Windsurf 20 sessions
|
|
54
|
+
✓ Windsurf Next 56 sessions
|
|
55
|
+
✓ Claude Code 6 sessions
|
|
56
|
+
✓ VS Code 23 sessions
|
|
57
|
+
✓ Zed 1 session
|
|
58
|
+
✓ Codex 3 sessions
|
|
59
|
+
✓ Gemini CLI 2 sessions
|
|
60
|
+
...and 6 more
|
|
61
|
+
|
|
62
|
+
(● ●) [● ●] {● ●} <● ●> ✓ 691 analyzed, 360 cached (27.1s)
|
|
63
|
+
✓ Dashboard ready at http://localhost:4637
|
|
64
|
+
```
|
|
34
65
|
|
|
35
|
-
To only build the cache
|
|
66
|
+
To only build the cache without starting the server:
|
|
36
67
|
|
|
37
68
|
```bash
|
|
38
69
|
npx agentlytics --collect
|
|
39
70
|
```
|
|
40
71
|
|
|
41
|
-
For local development, run `npm run dev` from the repo root. That starts both the backend on `http://localhost:4637` and the Vite frontend on `http://localhost:5173`.
|
|
42
|
-
|
|
43
72
|
## Features
|
|
44
73
|
|
|
45
74
|
- **Dashboard** — KPIs, activity heatmap, editor breakdown, coding streaks, token economy, peak hours, top models & tools
|
|
46
|
-
- **Sessions** — Search, filter, full
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **Relay** —
|
|
75
|
+
- **Sessions** — Search, filter, and read full conversations with syntax highlighting. Open any chat in a slide-over sidebar.
|
|
76
|
+
- **Costs** — Estimate your AI spend broken down by model, editor, project, and month. Spot your most expensive sessions.
|
|
77
|
+
- **Projects** — Per-project analytics: sessions, messages, tokens, models, editor breakdown, and drill-down detail views
|
|
78
|
+
- **Deep Analysis** — Tool frequency heatmaps, model distribution, token breakdown, and filterable drill-down analytics
|
|
79
|
+
- **Compare** — Side-by-side editor comparison with efficiency ratios, token usage, and session patterns
|
|
80
|
+
- **Relay** — Share AI session context across your team via MCP
|
|
52
81
|
|
|
53
82
|
## Supported Editors
|
|
54
83
|
|
|
55
|
-
| Editor |
|
|
56
|
-
|
|
57
|
-
| **Cursor** |
|
|
58
|
-
| **Windsurf** |
|
|
59
|
-
| **Windsurf Next** |
|
|
60
|
-
| **Antigravity** |
|
|
61
|
-
| **Claude Code** |
|
|
62
|
-
| **VS Code** |
|
|
63
|
-
| **VS Code Insiders** |
|
|
64
|
-
| **Zed** |
|
|
65
|
-
| **OpenCode** |
|
|
66
|
-
| **Codex** |
|
|
67
|
-
| **Gemini CLI** |
|
|
68
|
-
| **Copilot CLI** |
|
|
69
|
-
| **Cursor Agent** |
|
|
70
|
-
| **Command Code** |
|
|
84
|
+
| Editor | Msgs | Tools | Models | Tokens |
|
|
85
|
+
|--------|:----:|:-----:|:------:|:------:|
|
|
86
|
+
| **Cursor** | ✅ | ✅ | ⚠️ | ⚠️ |
|
|
87
|
+
| **Windsurf** | ✅ | ✅ | ✅ | ✅ |
|
|
88
|
+
| **Windsurf Next** | ✅ | ✅ | ✅ | ✅ |
|
|
89
|
+
| **Antigravity** | ✅ | ✅ | ✅ | ✅ |
|
|
90
|
+
| **Claude Code** | ✅ | ✅ | ✅ | ✅ |
|
|
91
|
+
| **VS Code** | ✅ | ✅ | ✅ | ✅ |
|
|
92
|
+
| **VS Code Insiders** | ✅ | ✅ | ✅ | ✅ |
|
|
93
|
+
| **Zed** | ✅ | ✅ | ✅ | ❌ |
|
|
94
|
+
| **OpenCode** | ✅ | ✅ | ✅ | ✅ |
|
|
95
|
+
| **Codex** | ✅ | ✅ | ✅ | ✅ |
|
|
96
|
+
| **Gemini CLI** | ✅ | ✅ | ✅ | ✅ |
|
|
97
|
+
| **Copilot CLI** | ✅ | ✅ | ✅ | ✅ |
|
|
98
|
+
| **Cursor Agent** | ✅ | ❌ | ❌ | ❌ |
|
|
99
|
+
| **Command Code** | ✅ | ✅ | ❌ | ❌ |
|
|
71
100
|
|
|
72
101
|
> Windsurf, Windsurf Next, and Antigravity must be running during scan.
|
|
73
102
|
|
|
74
|
-
Codex sessions are read from `${CODEX_HOME:-~/.codex}/sessions/**/*.jsonl`. Reasoning summaries may appear in transcripts when Codex records them in clear text, but encrypted reasoning content is not readable. Codex Desktop and CLI sessions are aggregated into one `codex` editor in analytics.
|
|
75
|
-
|
|
76
103
|
## Relay
|
|
77
104
|
|
|
78
105
|
Relay enables multi-user context sharing across a team. One person starts a relay server, others join and share selected project sessions. An MCP server is exposed so AI clients can query across everyone's coding history.
|
package/cache.js
CHANGED
|
@@ -2,13 +2,56 @@ const Database = require('better-sqlite3');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const { getAllChats, getMessages,
|
|
5
|
+
const { getAllChats, getMessages, resetCaches } = require('./editors');
|
|
6
6
|
const { calculateCost, getModelPricing, normalizeModelName } = require('./pricing');
|
|
7
7
|
|
|
8
8
|
const CACHE_DIR = path.join(os.homedir(), '.agentlytics');
|
|
9
9
|
const CACHE_DB = path.join(CACHE_DIR, 'cache.db');
|
|
10
10
|
const SCHEMA_VERSION = 5; // bump this when schema changes to auto-revalidate
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a folder path for consistent storage/lookup.
|
|
14
|
+
* - Strips file:// prefix
|
|
15
|
+
* - On Windows: resolves real disk casing via fs.realpathSync.native(),
|
|
16
|
+
* falls back to uppercase drive letter + lowercase rest, trims trailing backslash,
|
|
17
|
+
* and converts backslashes to forward slashes.
|
|
18
|
+
* - On macOS/Linux: resolves symlinks via fs.realpathSync().
|
|
19
|
+
*/
|
|
20
|
+
function normalizeFolder(folder) {
|
|
21
|
+
if (!folder) return folder;
|
|
22
|
+
// Strip file:// prefix
|
|
23
|
+
folder = folder.replace(/^file:\/\//, '');
|
|
24
|
+
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
try {
|
|
27
|
+
folder = path.resolve(folder);
|
|
28
|
+
try {
|
|
29
|
+
folder = fs.realpathSync.native(folder);
|
|
30
|
+
} catch {
|
|
31
|
+
// realpathSync.native failed — uppercase drive letter, lowercase rest
|
|
32
|
+
if (/^[a-zA-Z]:/.test(folder)) {
|
|
33
|
+
folder = folder[0].toUpperCase() + folder.slice(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Remove trailing backslash (but keep "C:\")
|
|
37
|
+
folder = folder.replace(/\\$/, '');
|
|
38
|
+
if (/^[A-Z]:$/.test(folder)) folder += '\\';
|
|
39
|
+
// Convert backslashes to forward slashes
|
|
40
|
+
folder = folder.replace(/\\/g, '/');
|
|
41
|
+
} catch {
|
|
42
|
+
// If all else fails, just return as-is with forward slashes
|
|
43
|
+
folder = folder.replace(/\\/g, '/');
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
try {
|
|
47
|
+
folder = fs.realpathSync(folder);
|
|
48
|
+
} catch {
|
|
49
|
+
// Path doesn't exist, return as-is
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return folder;
|
|
53
|
+
}
|
|
54
|
+
|
|
12
55
|
let db = null;
|
|
13
56
|
|
|
14
57
|
// ============================================================
|
|
@@ -113,6 +156,30 @@ function initDb() {
|
|
|
113
156
|
|
|
114
157
|
// Store schema version so future runs can detect mismatches
|
|
115
158
|
db.prepare('INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)').run('schema_version', SCHEMA_VERSION.toString());
|
|
159
|
+
|
|
160
|
+
// v2 migration: normalize folder paths on Windows
|
|
161
|
+
if (process.platform === 'win32') {
|
|
162
|
+
let normV = 0;
|
|
163
|
+
try {
|
|
164
|
+
const row = db.prepare("SELECT value FROM meta WHERE key = 'folder_norm_v'").get();
|
|
165
|
+
if (row) normV = parseInt(row.value) || 0;
|
|
166
|
+
} catch {}
|
|
167
|
+
if (normV < 2) {
|
|
168
|
+
const chatRows = db.prepare('SELECT id, folder FROM chats WHERE folder IS NOT NULL').all();
|
|
169
|
+
const updChat = db.prepare('UPDATE chats SET folder = ? WHERE id = ?');
|
|
170
|
+
for (const r of chatRows) {
|
|
171
|
+
const norm = normalizeFolder(r.folder);
|
|
172
|
+
if (norm !== r.folder) updChat.run(norm, r.id);
|
|
173
|
+
}
|
|
174
|
+
const tcRows = db.prepare('SELECT id, folder FROM tool_calls WHERE folder IS NOT NULL').all();
|
|
175
|
+
const updTc = db.prepare('UPDATE tool_calls SET folder = ? WHERE id = ?');
|
|
176
|
+
for (const r of tcRows) {
|
|
177
|
+
const norm = normalizeFolder(r.folder);
|
|
178
|
+
if (norm !== r.folder) updTc.run(norm, r.id);
|
|
179
|
+
}
|
|
180
|
+
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('folder_norm_v', '2')").run();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
116
183
|
}
|
|
117
184
|
|
|
118
185
|
// ============================================================
|
|
@@ -245,6 +312,9 @@ function scanAll(onProgress, opts = {}) {
|
|
|
245
312
|
}
|
|
246
313
|
});
|
|
247
314
|
|
|
315
|
+
// Normalize folder paths
|
|
316
|
+
for (const chat of chats) chat.folder = normalizeFolder(chat.folder);
|
|
317
|
+
|
|
248
318
|
// Insert all chats in a transaction
|
|
249
319
|
batchInsert(chats);
|
|
250
320
|
|
|
@@ -401,7 +471,7 @@ function getCachedOverview(opts = {}) {
|
|
|
401
471
|
GROUP BY folder ORDER BY count DESC LIMIT 20
|
|
402
472
|
`).all(...params);
|
|
403
473
|
const topProjects = projects.map(p => ({
|
|
404
|
-
name: p.folder.split(
|
|
474
|
+
name: p.folder.split(/[/\\]/).slice(-2).join('/'),
|
|
405
475
|
fullPath: p.folder,
|
|
406
476
|
count: p.count,
|
|
407
477
|
}));
|
|
@@ -644,7 +714,7 @@ function getCachedProjects(opts = {}) {
|
|
|
644
714
|
|
|
645
715
|
result.push({
|
|
646
716
|
folder: proj.folder,
|
|
647
|
-
name: proj.folder.split(
|
|
717
|
+
name: proj.folder.split(/[/\\]/).pop(),
|
|
648
718
|
totalSessions: proj.totalSessions,
|
|
649
719
|
editors: proj.editors,
|
|
650
720
|
firstSeen: proj.firstSeen,
|
|
@@ -694,16 +764,6 @@ function safeParseJson(s) {
|
|
|
694
764
|
try { return JSON.parse(s); } catch { return {}; }
|
|
695
765
|
}
|
|
696
766
|
|
|
697
|
-
function resetAndRescan(onProgress) {
|
|
698
|
-
if (db) db.close();
|
|
699
|
-
if (fs.existsSync(CACHE_DB)) fs.unlinkSync(CACHE_DB);
|
|
700
|
-
for (const suffix of ['-wal', '-shm']) {
|
|
701
|
-
if (fs.existsSync(CACHE_DB + suffix)) fs.unlinkSync(CACHE_DB + suffix);
|
|
702
|
-
}
|
|
703
|
-
initDb();
|
|
704
|
-
return scanAll(onProgress);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
767
|
/**
|
|
708
768
|
* Async version of scanAll that yields the event loop between iterations.
|
|
709
769
|
* Required for SSE streaming so progress events actually flush to the client.
|
|
@@ -720,6 +780,9 @@ async function scanAllAsync(onProgress) {
|
|
|
720
780
|
existing[row.id] = row.last_updated_at;
|
|
721
781
|
}
|
|
722
782
|
|
|
783
|
+
// Normalize folder paths
|
|
784
|
+
for (const chat of chats) chat.folder = normalizeFolder(chat.folder);
|
|
785
|
+
|
|
723
786
|
const ins = insertChat();
|
|
724
787
|
const batchInsert = db.transaction((chatBatch) => {
|
|
725
788
|
for (const chat of chatBatch) {
|
|
@@ -1316,7 +1379,6 @@ module.exports = {
|
|
|
1316
1379
|
getCachedChat,
|
|
1317
1380
|
getCachedProjects,
|
|
1318
1381
|
getCachedToolCalls,
|
|
1319
|
-
resetAndRescan,
|
|
1320
1382
|
resetAndRescanAsync,
|
|
1321
1383
|
getCachedDashboardStats,
|
|
1322
1384
|
getCostBreakdown,
|
package/editors/base.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const os = require('os');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
3
|
|
|
5
4
|
const HOME = os.homedir();
|
|
6
5
|
|
|
@@ -23,112 +22,6 @@ function getAppDataPath(appName) {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
// --- Formatting utilities shared across all editor adapters ---
|
|
27
|
-
|
|
28
|
-
function formatArgs(args, maxLen = 300) {
|
|
29
|
-
if (!args || typeof args !== 'object') return '';
|
|
30
|
-
const lines = [];
|
|
31
|
-
for (const [key, val] of Object.entries(args)) {
|
|
32
|
-
let display = typeof val === 'string' ? val : JSON.stringify(val);
|
|
33
|
-
if (display && display.length > maxLen) {
|
|
34
|
-
display = display.substring(0, maxLen) + chalk.dim(`… (${display.length} chars)`);
|
|
35
|
-
}
|
|
36
|
-
lines.push(` ${chalk.dim(key + ':')} ${display}`);
|
|
37
|
-
}
|
|
38
|
-
return lines.join('\n');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function formatToolCall(item) {
|
|
42
|
-
const name = item.toolName || 'unknown';
|
|
43
|
-
const id = item.toolCallId || '';
|
|
44
|
-
const decision = item.userDecision;
|
|
45
|
-
const decisionStr = decision === 'accepted' ? chalk.green(' ✓accepted')
|
|
46
|
-
: decision === 'rejected' ? chalk.red(' ✗rejected')
|
|
47
|
-
: decision ? chalk.yellow(` ${decision}`) : '';
|
|
48
|
-
let out = ` ${chalk.magenta('▶')} ${chalk.bold.magenta(name)}${decisionStr} ${chalk.dim(id)}`;
|
|
49
|
-
if (item.args && Object.keys(item.args).length > 0) {
|
|
50
|
-
out += '\n' + formatArgs(item.args);
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function formatToolResult(item) {
|
|
56
|
-
const name = item.toolName || 'unknown';
|
|
57
|
-
const result = typeof item.result === 'string' ? item.result : JSON.stringify(item.result || '');
|
|
58
|
-
const maxPreview = 500;
|
|
59
|
-
const preview = result.length > maxPreview
|
|
60
|
-
? result.substring(0, maxPreview) + chalk.dim(`… (${result.length} chars)`)
|
|
61
|
-
: result;
|
|
62
|
-
const status = result.startsWith('Rejected') ? chalk.red('✗ rejected') : chalk.green('✓ ok');
|
|
63
|
-
let out = ` ${chalk.yellow('◀')} ${chalk.bold.yellow(name)} ${status}`;
|
|
64
|
-
if (preview.trim()) {
|
|
65
|
-
out += '\n ' + chalk.dim(preview.replace(/\n/g, '\n '));
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function extractText(content, { richToolDisplay = false } = {}) {
|
|
71
|
-
if (typeof content === 'string') return content;
|
|
72
|
-
if (!Array.isArray(content)) return '';
|
|
73
|
-
return content
|
|
74
|
-
.map((item) => {
|
|
75
|
-
if (item.type === 'text') return item.text;
|
|
76
|
-
if (item.type === 'reasoning') return `[thinking] ${item.text}`;
|
|
77
|
-
if (item.type === 'tool-call') {
|
|
78
|
-
return richToolDisplay
|
|
79
|
-
? formatToolCall(item)
|
|
80
|
-
: `[tool-call: ${item.toolName || 'unknown'}(${Object.keys(item.args || {}).join(', ')})]`;
|
|
81
|
-
}
|
|
82
|
-
if (item.type === 'tool-result') {
|
|
83
|
-
return richToolDisplay
|
|
84
|
-
? formatToolResult(item)
|
|
85
|
-
: `[tool-result: ${item.toolName || 'unknown'}] ${(typeof item.result === 'string' ? item.result : '').substring(0, 200)}`;
|
|
86
|
-
}
|
|
87
|
-
return '';
|
|
88
|
-
})
|
|
89
|
-
.filter(Boolean)
|
|
90
|
-
.join('\n');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function roleColor(role) {
|
|
94
|
-
switch (role) {
|
|
95
|
-
case 'user': return chalk.green;
|
|
96
|
-
case 'assistant': return chalk.cyan;
|
|
97
|
-
case 'system': return chalk.gray;
|
|
98
|
-
case 'tool': return chalk.yellow;
|
|
99
|
-
default: return chalk.white;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function roleLabel(role) {
|
|
104
|
-
switch (role) {
|
|
105
|
-
case 'user': return '👤 User';
|
|
106
|
-
case 'assistant': return '🤖 Assistant';
|
|
107
|
-
case 'system': return '⚙️ System';
|
|
108
|
-
case 'tool': return '🔧 Tool';
|
|
109
|
-
default: return role;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function formatDate(ts) {
|
|
114
|
-
if (!ts) return 'unknown';
|
|
115
|
-
return new Date(ts).toLocaleString();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function truncate(str, max = 120) {
|
|
119
|
-
if (!str) return '';
|
|
120
|
-
const oneLine = str.replace(/\n/g, ' ').trim();
|
|
121
|
-
return oneLine.length > max ? oneLine.substring(0, max) + '…' : oneLine;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function shortenPath(p, maxLen = 40) {
|
|
125
|
-
if (!p) return '';
|
|
126
|
-
if (p.length <= maxLen) return p;
|
|
127
|
-
const parts = p.split('/');
|
|
128
|
-
if (parts.length <= 3) return p;
|
|
129
|
-
return '…/' + parts.slice(-2).join('/');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
25
|
/**
|
|
133
26
|
* Every editor adapter must implement:
|
|
134
27
|
*
|
|
@@ -141,13 +34,4 @@ function shortenPath(p, maxLen = 40) {
|
|
|
141
34
|
|
|
142
35
|
module.exports = {
|
|
143
36
|
getAppDataPath,
|
|
144
|
-
formatArgs,
|
|
145
|
-
formatToolCall,
|
|
146
|
-
formatToolResult,
|
|
147
|
-
extractText,
|
|
148
|
-
roleColor,
|
|
149
|
-
roleLabel,
|
|
150
|
-
formatDate,
|
|
151
|
-
truncate,
|
|
152
|
-
shortenPath,
|
|
153
37
|
};
|
package/editors/codex.js
CHANGED
|
@@ -439,15 +439,4 @@ module.exports = {
|
|
|
439
439
|
name,
|
|
440
440
|
getChats,
|
|
441
441
|
getMessages,
|
|
442
|
-
_test: {
|
|
443
|
-
getSessionsDir,
|
|
444
|
-
readChatMetadata,
|
|
445
|
-
parseSessionMessages,
|
|
446
|
-
normalizeRawUsage,
|
|
447
|
-
subtractRawUsage,
|
|
448
|
-
convertToDelta,
|
|
449
|
-
extractModel,
|
|
450
|
-
isBootstrapMessage,
|
|
451
|
-
previewToolOutput,
|
|
452
|
-
},
|
|
453
442
|
};
|
package/editors/index.js
CHANGED
|
@@ -46,18 +46,10 @@ function getMessages(chat) {
|
|
|
46
46
|
return resolvedEditor.getMessages(chat);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Find a chat by ID prefix across all editors.
|
|
51
|
-
*/
|
|
52
|
-
function findChat(idPrefix) {
|
|
53
|
-
const chats = getAllChats();
|
|
54
|
-
return chats.find((c) => c.composerId.startsWith(idPrefix));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
49
|
function resetCaches() {
|
|
58
50
|
for (const editor of editors) {
|
|
59
51
|
if (typeof editor.resetCache === 'function') editor.resetCache();
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
54
|
|
|
63
|
-
module.exports = { getAllChats, getMessages,
|
|
55
|
+
module.exports = { getAllChats, getMessages, editors, resetCaches };
|
package/editors/opencode.js
CHANGED
|
@@ -14,7 +14,7 @@ function queryDb(sql) {
|
|
|
14
14
|
try {
|
|
15
15
|
const raw = execSync(
|
|
16
16
|
`sqlite3 -json ${JSON.stringify(DB_PATH)} ${JSON.stringify(sql)}`,
|
|
17
|
-
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
|
|
17
|
+
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
18
18
|
);
|
|
19
19
|
return JSON.parse(raw);
|
|
20
20
|
} catch { return []; }
|
package/editors/windsurf.js
CHANGED
|
@@ -23,7 +23,7 @@ function findLanguageServers() {
|
|
|
23
23
|
if (_lsCache) return _lsCache;
|
|
24
24
|
_lsCache = [];
|
|
25
25
|
try {
|
|
26
|
-
const ps = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
|
|
26
|
+
const ps = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
27
27
|
for (const line of ps.split('\n')) {
|
|
28
28
|
if (!line.includes('language_server_macos') || !line.includes('--csrf_token')) continue;
|
|
29
29
|
const csrfMatch = line.match(/--csrf_token\s+(\S+)/);
|
|
@@ -38,7 +38,7 @@ function findLanguageServers() {
|
|
|
38
38
|
if (!pidMatch) continue;
|
|
39
39
|
const pid = pidMatch[1];
|
|
40
40
|
try {
|
|
41
|
-
const lsof = execSync(`lsof -i TCP -P -n -a -p ${pid} 2>/dev/null`, { encoding: 'utf-8' });
|
|
41
|
+
const lsof = execSync(`lsof -i TCP -P -n -a -p ${pid} 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
42
42
|
for (const l of lsof.split('\n')) {
|
|
43
43
|
const portMatch = l.match(/TCP\s+127\.0\.0\.1:(\d+)\s+\(LISTEN\)/);
|
|
44
44
|
if (portMatch) {
|
|
@@ -79,7 +79,7 @@ function callRpc(port, csrf, method, body, useHttps) {
|
|
|
79
79
|
`-H "x-codeium-csrf-token: ${csrf}" ` +
|
|
80
80
|
`-d ${JSON.stringify(data)} ` +
|
|
81
81
|
`--max-time 10`,
|
|
82
|
-
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }
|
|
82
|
+
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
83
83
|
);
|
|
84
84
|
return JSON.parse(result);
|
|
85
85
|
} catch { return null; }
|
package/editors/zed.js
CHANGED
|
@@ -15,7 +15,7 @@ function decompressZstd(buf) {
|
|
|
15
15
|
const tmpOut = tmpIn.replace('.zst', '.json');
|
|
16
16
|
try {
|
|
17
17
|
fs.writeFileSync(tmpIn, buf);
|
|
18
|
-
execSync(`zstd -d -f -q ${JSON.stringify(tmpIn)} -o ${JSON.stringify(tmpOut)}`, { stdio: 'pipe' });
|
|
18
|
+
execSync(`zstd -d -f -q ${JSON.stringify(tmpIn)} -o ${JSON.stringify(tmpOut)}`, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
19
19
|
const data = fs.readFileSync(tmpOut, 'utf-8');
|
|
20
20
|
return data;
|
|
21
21
|
} finally {
|
|
@@ -33,7 +33,7 @@ function queryDb(sql) {
|
|
|
33
33
|
try {
|
|
34
34
|
const raw = execSync(
|
|
35
35
|
`sqlite3 -json ${JSON.stringify(THREADS_DB)} ${JSON.stringify(sql)}`,
|
|
36
|
-
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
|
|
36
|
+
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
37
37
|
);
|
|
38
38
|
return JSON.parse(raw);
|
|
39
39
|
} catch { return []; }
|
|
@@ -44,7 +44,7 @@ function queryBlobHex(id) {
|
|
|
44
44
|
try {
|
|
45
45
|
const hex = execSync(
|
|
46
46
|
`sqlite3 ${JSON.stringify(THREADS_DB)} "SELECT hex(data) FROM threads WHERE id = '${id}'"`,
|
|
47
|
-
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }
|
|
47
|
+
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
48
48
|
).trim();
|
|
49
49
|
if (!hex) return null;
|
|
50
50
|
return Buffer.from(hex, 'hex');
|
package/index.js
CHANGED
|
@@ -174,7 +174,8 @@ if (noCache) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// ── Warn about installed-but-not-running Windsurf variants ─
|
|
177
|
+
// ── Warn about installed-but-not-running Windsurf variants (macOS only) ─
|
|
178
|
+
if (process.platform === 'darwin') {
|
|
178
179
|
const WINDSURF_VARIANTS = [
|
|
179
180
|
{ name: 'Windsurf', app: '/Applications/Windsurf.app', dataDir: path.join(HOME, '.codeium', 'windsurf'), ide: 'windsurf' },
|
|
180
181
|
{ name: 'Windsurf Next', app: '/Applications/Windsurf Next.app', dataDir: path.join(HOME, '.codeium', 'windsurf-next'), ide: 'windsurf-next' },
|
|
@@ -208,6 +209,7 @@ const WINDSURF_VARIANTS = [
|
|
|
208
209
|
console.log('');
|
|
209
210
|
}
|
|
210
211
|
})();
|
|
212
|
+
}
|
|
211
213
|
|
|
212
214
|
// Initialize cache DB
|
|
213
215
|
cache.initDb();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlytics",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravity, OpenCode, Command Code",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -54,9 +54,7 @@
|
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
55
55
|
"better-sqlite3": "^12.6.2",
|
|
56
56
|
"chalk": "^4.1.2",
|
|
57
|
-
"commander": "^14.0.3",
|
|
58
57
|
"express": "^4.22.1",
|
|
59
|
-
"inquirer": "^13.3.0",
|
|
60
58
|
"log-update": "^4.0.0",
|
|
61
59
|
"open": "^8.4.2"
|
|
62
60
|
}
|