agentlytics 0.1.5 → 0.1.7

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/editors/vscode.js CHANGED
@@ -118,6 +118,7 @@ function parseSessionFile(filePath) {
118
118
  title: state.customTitle || null,
119
119
  requests: state.requests || [],
120
120
  format: 'jsonl',
121
+ selectedModel: state.inputState?.selectedModel?.metadata?.id || null,
121
122
  };
122
123
  } else if (ext === '.json') {
123
124
  let data;
@@ -128,6 +129,7 @@ function parseSessionFile(filePath) {
128
129
  title: data.customTitle || null,
129
130
  requests: data.requests || [],
130
131
  format: 'json',
132
+ selectedModel: data.inputState?.selectedModel?.metadata?.id || null,
131
133
  };
132
134
  }
133
135
  return null;
@@ -204,6 +206,7 @@ function collectSessions(dir, folder, source, chats) {
204
206
  encrypted: false,
205
207
  bubbleCount: meta.requestCount || 0,
206
208
  _filePath: filePath,
209
+ _modelPref: meta.selectedModel || null,
207
210
  });
208
211
  } catch { /* skip */ }
209
212
  }
@@ -221,6 +224,7 @@ function peekMeta(filePath) {
221
224
  createdAt: data.creationDate || data.lastMessageDate,
222
225
  requestCount: data.requests?.length || 0,
223
226
  firstUserText: firstText.substring(0, 120) || null,
227
+ selectedModel: data.inputState?.selectedModel?.metadata?.id || null,
224
228
  };
225
229
  } catch { return {}; }
226
230
  }
@@ -251,6 +255,7 @@ function peekMeta(filePath) {
251
255
  createdAt: state.creationDate,
252
256
  requestCount: null,
253
257
  firstUserText: null,
258
+ selectedModel: state.inputState?.selectedModel?.metadata?.id || null,
254
259
  };
255
260
  } catch { return {}; }
256
261
  }
@@ -301,6 +306,7 @@ function getMessages(chat) {
301
306
  const meta = req.result?.metadata;
302
307
  messages.push({
303
308
  role: 'assistant', content: responseText,
309
+ _model: parsed.selectedModel || null,
304
310
  _inputTokens: meta?.promptTokens, _outputTokens: meta?.outputTokens,
305
311
  _toolCalls,
306
312
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlytics",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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": {
package/server.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const express = require('express');
2
2
  const path = require('path');
3
+ const fs = require('fs');
4
+ const os = require('os');
3
5
  const cache = require('./cache');
4
6
  const { generateShareSvg } = require('./share-image');
5
7
 
@@ -7,6 +9,26 @@ const app = express();
7
9
  app.use(express.json());
8
10
  app.use(express.static(path.join(__dirname, 'public')));
9
11
 
12
+ // ============================================================
13
+ // Config: ~/.agentlytics/config.json
14
+ // ============================================================
15
+
16
+ const CONFIG_PATH = path.join(os.homedir(), '.agentlytics', 'config.json');
17
+
18
+ function readConfig() {
19
+ try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
20
+ }
21
+
22
+ function writeConfig(config) {
23
+ const dir = path.dirname(CONFIG_PATH);
24
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
25
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
26
+ }
27
+
28
+ function getHiddenFolders() {
29
+ return readConfig().hiddenProjects || [];
30
+ }
31
+
10
32
  // ============================================================
11
33
  // API endpoints — all reads from SQLite cache
12
34
  // ============================================================
@@ -25,7 +47,7 @@ app.get('/api/mode', (req, res) => {
25
47
 
26
48
  app.get('/api/overview', (req, res) => {
27
49
  try {
28
- const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query) };
50
+ const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: getHiddenFolders() };
29
51
  res.json(cache.getCachedOverview(opts));
30
52
  } catch (err) {
31
53
  res.status(500).json({ error: err.message });
@@ -34,7 +56,7 @@ app.get('/api/overview', (req, res) => {
34
56
 
35
57
  app.get('/api/daily-activity', (req, res) => {
36
58
  try {
37
- const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query) };
59
+ const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: getHiddenFolders() };
38
60
  res.json(cache.getCachedDailyActivity(opts));
39
61
  } catch (err) {
40
62
  res.status(500).json({ error: err.message });
@@ -50,6 +72,7 @@ app.get('/api/chats', (req, res) => {
50
72
  limit: req.query.limit ? parseInt(req.query.limit) : 200,
51
73
  offset: req.query.offset ? parseInt(req.query.offset) : 0,
52
74
  ...parseDateOpts(req.query),
75
+ hiddenFolders: getHiddenFolders(),
53
76
  };
54
77
  const total = cache.countCachedChats(opts);
55
78
  const rows = cache.getCachedChats(opts);
@@ -66,6 +89,7 @@ app.get('/api/chats', (req, res) => {
66
89
  encrypted: !!c.encrypted,
67
90
  bubbleCount: c.bubble_count,
68
91
  topModel: c.top_model || null,
92
+ cost: c.cost || 0,
69
93
  })),
70
94
  });
71
95
  } catch (err) {
@@ -130,7 +154,7 @@ app.get('/api/chats/:id/markdown', (req, res) => {
130
154
 
131
155
  app.get('/api/projects', (req, res) => {
132
156
  try {
133
- res.json(cache.getCachedProjects(parseDateOpts(req.query)));
157
+ res.json(cache.getCachedProjects({ ...parseDateOpts(req.query), hiddenFolders: getHiddenFolders() }));
134
158
  } catch (err) {
135
159
  res.status(500).json({ error: err.message });
136
160
  }
@@ -143,6 +167,7 @@ app.get('/api/deep-analytics', (req, res) => {
143
167
  folder: req.query.folder || null,
144
168
  limit: Math.min(parseInt(req.query.limit) || 500, 5000),
145
169
  ...parseDateOpts(req.query),
170
+ hiddenFolders: getHiddenFolders(),
146
171
  };
147
172
  res.json(cache.getCachedDeepAnalytics(opts));
148
173
  } catch (err) {
@@ -152,13 +177,41 @@ app.get('/api/deep-analytics', (req, res) => {
152
177
 
153
178
  app.get('/api/dashboard-stats', (req, res) => {
154
179
  try {
155
- const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query) };
180
+ const opts = { editor: req.query.editor || null, ...parseDateOpts(req.query), hiddenFolders: getHiddenFolders() };
156
181
  res.json(cache.getCachedDashboardStats(opts));
157
182
  } catch (err) {
158
183
  res.status(500).json({ error: err.message });
159
184
  }
160
185
  });
161
186
 
187
+ app.get('/api/cost-analytics', (req, res) => {
188
+ try {
189
+ const opts = {
190
+ editor: req.query.editor || null,
191
+ ...parseDateOpts(req.query),
192
+ hiddenFolders: getHiddenFolders(),
193
+ };
194
+ res.json(cache.getCostAnalytics(opts));
195
+ } catch (err) {
196
+ res.status(500).json({ error: err.message });
197
+ }
198
+ });
199
+
200
+ app.get('/api/costs', (req, res) => {
201
+ try {
202
+ const opts = {
203
+ editor: req.query.editor || null,
204
+ folder: req.query.folder || null,
205
+ chatId: req.query.chatId || null,
206
+ ...parseDateOpts(req.query),
207
+ hiddenFolders: getHiddenFolders(),
208
+ };
209
+ res.json(cache.getCostBreakdown(opts));
210
+ } catch (err) {
211
+ res.status(500).json({ error: err.message });
212
+ }
213
+ });
214
+
162
215
  app.get('/api/tool-calls', (req, res) => {
163
216
  try {
164
217
  const name = req.query.name;
@@ -239,6 +292,33 @@ app.get('/api/refetch', async (req, res) => {
239
292
  res.end();
240
293
  });
241
294
 
295
+ // ============================================================
296
+ // Config endpoints
297
+ // ============================================================
298
+
299
+ app.get('/api/config', (req, res) => {
300
+ res.json(readConfig());
301
+ });
302
+
303
+ app.put('/api/config', (req, res) => {
304
+ try {
305
+ const config = readConfig();
306
+ Object.assign(config, req.body);
307
+ writeConfig(config);
308
+ res.json(config);
309
+ } catch (err) {
310
+ res.status(500).json({ error: err.message });
311
+ }
312
+ });
313
+
314
+ app.get('/api/all-projects', (req, res) => {
315
+ try {
316
+ res.json(cache.getCachedProjects({ ...parseDateOpts(req.query), includeHidden: true }));
317
+ } catch (err) {
318
+ res.status(500).json({ error: err.message });
319
+ }
320
+ });
321
+
242
322
  // SPA fallback
243
323
  app.get('*', (req, res) => {
244
324
  res.sendFile(path.join(__dirname, 'public', 'index.html'));