agentlytics 0.1.12 → 0.1.14

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 CHANGED
@@ -6,12 +6,12 @@
6
6
 
7
7
  <p align="center">
8
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>
9
+ <sub>One command to turn scattered AI conversations from <b>15 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">
13
13
  <a href="https://www.npmjs.com/package/agentlytics"><img src="https://img.shields.io/npm/v/agentlytics?color=6366f1&label=npm" alt="npm"></a>
14
- <a href="#supported-editors"><img src="https://img.shields.io/badge/editors-14-818cf8" alt="editors"></a>
14
+ <a href="#supported-editors"><img src="https://img.shields.io/badge/editors-15-818cf8" alt="editors"></a>
15
15
  <a href="#license"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a>
16
16
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A520.19%20%7C%20%E2%89%A522.12-brightgreen" alt="node"></a>
17
17
  </p>
@@ -83,7 +83,7 @@ npx agentlytics --collect
83
83
 
84
84
  | Editor | Msgs | Tools | Models | Tokens |
85
85
  |--------|:----:|:-----:|:------:|:------:|
86
- | **Cursor** | ✅ | ✅ | ⚠️ | ⚠️ |
86
+ | **Cursor** | ✅ | ✅ | | |
87
87
  | **Windsurf** | ✅ | ✅ | ✅ | ✅ |
88
88
  | **Windsurf Next** | ✅ | ✅ | ✅ | ✅ |
89
89
  | **Antigravity** | ✅ | ✅ | ✅ | ✅ |
@@ -97,6 +97,7 @@ npx agentlytics --collect
97
97
  | **Copilot CLI** | ✅ | ✅ | ✅ | ✅ |
98
98
  | **Cursor Agent** | ✅ | ❌ | ❌ | ❌ |
99
99
  | **Command Code** | ✅ | ✅ | ❌ | ❌ |
100
+ | **Goose** | ✅ | ✅ | ✅ | ❌ |
100
101
 
101
102
  > Windsurf, Windsurf Next, and Antigravity must be running during scan.
102
103
 
package/editors/claude.js CHANGED
@@ -200,4 +200,6 @@ function extractAssistantContent(content) {
200
200
  return { text: parts.join('\n') || '', toolCalls };
201
201
  }
202
202
 
203
- module.exports = { name, getChats, getMessages };
203
+ const labels = { 'claude-code': 'Claude Code' };
204
+
205
+ module.exports = { name, labels, getChats, getMessages };
package/editors/codex.js CHANGED
@@ -435,8 +435,11 @@ function safeParseJson(value) {
435
435
  }
436
436
  }
437
437
 
438
+ const labels = { 'codex': 'Codex' };
439
+
438
440
  module.exports = {
439
441
  name,
442
+ labels,
440
443
  getChats,
441
444
  getMessages,
442
445
  };
@@ -156,4 +156,6 @@ function extractAssistantContent(content) {
156
156
  return { text: parts.join('\n') || '', toolCalls };
157
157
  }
158
158
 
159
- module.exports = { name, getChats, getMessages };
159
+ const labels = { 'commandcode': 'Command Code' };
160
+
161
+ module.exports = { name, labels, getChats, getMessages };
@@ -171,4 +171,6 @@ function safeParse(str) {
171
171
  try { return JSON.parse(str); } catch { return {}; }
172
172
  }
173
173
 
174
- module.exports = { name, getChats, getMessages };
174
+ const labels = { 'copilot-cli': 'Copilot CLI' };
175
+
176
+ module.exports = { name, labels, getChats, getMessages };
@@ -192,4 +192,6 @@ function getMessages(chat) {
192
192
  return result;
193
193
  }
194
194
 
195
- module.exports = { name, getChats, getMessages };
195
+ const labels = { 'cursor-agent': 'Cursor Agent' };
196
+
197
+ module.exports = { name, labels, getChats, getMessages };
package/editors/cursor.js CHANGED
@@ -341,4 +341,6 @@ function getMessages(chat) {
341
341
  return msgs;
342
342
  }
343
343
 
344
- module.exports = { name, getChats, getMessages };
344
+ const labels = { 'cursor': 'Cursor' };
345
+
346
+ module.exports = { name, labels, getChats, getMessages };
package/editors/gemini.js CHANGED
@@ -171,4 +171,6 @@ function getMessages(chat) {
171
171
  return result;
172
172
  }
173
173
 
174
- module.exports = { name, getChats, getMessages };
174
+ const labels = { 'gemini-cli': 'Gemini CLI' };
175
+
176
+ module.exports = { name, labels, getChats, getMessages };
@@ -0,0 +1,287 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const { execSync } = require('child_process');
5
+
6
+ const GOOSE_DIR = path.join(os.homedir(), '.local', 'share', 'goose', 'sessions');
7
+ const DB_PATH = path.join(GOOSE_DIR, 'sessions.db');
8
+ const CONFIG_PATH = path.join(os.homedir(), '.config', 'goose', 'config.yaml');
9
+
10
+ // ============================================================
11
+ // Query SQLite via CLI
12
+ // ============================================================
13
+
14
+ function queryDb(sql) {
15
+ if (!fs.existsSync(DB_PATH)) return [];
16
+ try {
17
+ const raw = execSync(
18
+ `sqlite3 -json ${JSON.stringify(DB_PATH)} ${JSON.stringify(sql)}`,
19
+ { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
20
+ );
21
+ return JSON.parse(raw);
22
+ } catch { return []; }
23
+ }
24
+
25
+ // ============================================================
26
+ // Adapter interface
27
+ // ============================================================
28
+
29
+ const name = 'goose';
30
+
31
+ let _configModel = undefined; // lazy-loaded
32
+
33
+ function getConfigModel() {
34
+ if (_configModel !== undefined) return _configModel;
35
+ _configModel = null;
36
+ try {
37
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
38
+ const modelMatch = raw.match(/^GOOSE_MODEL:\s*(.+)$/m);
39
+ if (modelMatch) _configModel = modelMatch[1].trim();
40
+ } catch {}
41
+ return _configModel;
42
+ }
43
+
44
+ function getChats() {
45
+ const chats = [];
46
+
47
+ const configModel = getConfigModel();
48
+
49
+ // --- SQLite sessions (v1.10.0+) ---
50
+ const dbSessions = queryDb(
51
+ `SELECT id, name, description, working_dir, created_at, updated_at,
52
+ total_tokens, input_tokens, output_tokens, provider_name, model_config_json,
53
+ (SELECT count(*) FROM messages m WHERE m.session_id = s.id) as msg_count
54
+ FROM sessions s ORDER BY updated_at DESC`
55
+ );
56
+
57
+ const dbSessionIds = new Set();
58
+ for (const row of dbSessions) {
59
+ dbSessionIds.add(row.id);
60
+ chats.push({
61
+ source: 'goose',
62
+ composerId: row.id,
63
+ name: cleanTitle(row.name || row.description),
64
+ createdAt: parseTimestamp(row.created_at),
65
+ lastUpdatedAt: parseTimestamp(row.updated_at),
66
+ mode: 'goose',
67
+ folder: row.working_dir || null,
68
+ encrypted: false,
69
+ bubbleCount: row.msg_count || 0,
70
+ _storage: 'db',
71
+ _inputTokens: row.input_tokens,
72
+ _outputTokens: row.output_tokens,
73
+ _model: extractSessionModel(row) || configModel,
74
+ });
75
+ }
76
+
77
+ // --- Legacy JSONL files ---
78
+ if (fs.existsSync(GOOSE_DIR)) {
79
+ let files;
80
+ try { files = fs.readdirSync(GOOSE_DIR).filter(f => f.endsWith('.jsonl')); } catch { files = []; }
81
+
82
+ for (const file of files) {
83
+ const sessionId = file.replace('.jsonl', '');
84
+ if (dbSessionIds.has(sessionId)) continue; // already in DB
85
+
86
+ const fullPath = path.join(GOOSE_DIR, file);
87
+ try {
88
+ const stat = fs.statSync(fullPath);
89
+ const meta = peekJsonlMeta(fullPath);
90
+ chats.push({
91
+ source: 'goose',
92
+ composerId: sessionId,
93
+ name: meta.firstPrompt ? cleanTitle(meta.firstPrompt) : null,
94
+ createdAt: meta.timestamp || stat.birthtime.getTime(),
95
+ lastUpdatedAt: stat.mtime.getTime(),
96
+ mode: 'goose',
97
+ folder: meta.workingDir || null,
98
+ encrypted: false,
99
+ _storage: 'jsonl',
100
+ _fullPath: fullPath,
101
+ _model: configModel,
102
+ });
103
+ } catch { /* skip */ }
104
+ }
105
+ }
106
+
107
+ return chats;
108
+ }
109
+
110
+ function parseTimestamp(ts) {
111
+ if (!ts) return null;
112
+ if (typeof ts === 'number') return ts;
113
+ const d = new Date(ts);
114
+ return isNaN(d.getTime()) ? null : d.getTime();
115
+ }
116
+
117
+ function cleanTitle(title) {
118
+ if (!title) return null;
119
+ return title.substring(0, 120).trim() || null;
120
+ }
121
+
122
+ function peekJsonlMeta(filePath) {
123
+ const meta = { firstPrompt: null, workingDir: null, timestamp: null };
124
+ try {
125
+ const buf = fs.readFileSync(filePath, 'utf-8');
126
+ for (const line of buf.split('\n')) {
127
+ if (!line) continue;
128
+ const obj = JSON.parse(line);
129
+
130
+ if (!meta.timestamp && obj.created) {
131
+ meta.timestamp = parseTimestamp(obj.created);
132
+ }
133
+
134
+ if (!meta.workingDir && obj.working_dir) {
135
+ meta.workingDir = obj.working_dir;
136
+ }
137
+
138
+ // First user text message
139
+ if (!meta.firstPrompt && obj.role === 'user' && obj.content) {
140
+ let parts;
141
+ try { parts = typeof obj.content === 'string' ? JSON.parse(obj.content) : obj.content; } catch { continue; }
142
+ if (Array.isArray(parts)) {
143
+ const text = parts.filter(p => p.type === 'text').map(p => p.text).join(' ');
144
+ if (text) meta.firstPrompt = text.substring(0, 200);
145
+ }
146
+ }
147
+
148
+ if (meta.firstPrompt && meta.workingDir) break;
149
+ }
150
+ } catch {}
151
+ return meta;
152
+ }
153
+
154
+ function getMessages(chat) {
155
+ if (chat._storage === 'db') return getMessagesFromDb(chat);
156
+ if (chat._storage === 'jsonl') return getMessagesFromJsonl(chat);
157
+ // Try DB first, then JSONL
158
+ const dbMessages = getMessagesFromDb(chat);
159
+ if (dbMessages.length) return dbMessages;
160
+ return getMessagesFromJsonl(chat);
161
+ }
162
+
163
+ function getMessagesFromDb(chat) {
164
+ const rows = queryDb(
165
+ `SELECT role, content_json, created_timestamp FROM messages
166
+ WHERE session_id = '${chat.composerId}' ORDER BY created_timestamp ASC`
167
+ );
168
+
169
+ const result = [];
170
+ for (const row of rows) {
171
+ let parts;
172
+ try { parts = JSON.parse(row.content_json); } catch { continue; }
173
+ if (!Array.isArray(parts)) continue;
174
+
175
+ const role = row.role;
176
+ const contentParts = [];
177
+ const toolCalls = [];
178
+
179
+ for (const part of parts) {
180
+ if (part.type === 'text' && part.text) {
181
+ contentParts.push(part.text);
182
+ } else if (part.type === 'toolRequest' && part.toolCall) {
183
+ const tc = part.toolCall.value || {};
184
+ const toolName = tc.name || 'tool';
185
+ let argKeys = '';
186
+ try { argKeys = Object.keys(tc.arguments || {}).join(', '); } catch {}
187
+ contentParts.push(`[tool-call: ${toolName}(${argKeys})]`);
188
+ toolCalls.push({ name: toolName, args: tc.arguments || {} });
189
+ } else if (part.type === 'toolResponse' && part.toolResult) {
190
+ const tr = part.toolResult;
191
+ let preview = '';
192
+ if (tr.value && Array.isArray(tr.value)) {
193
+ preview = tr.value
194
+ .filter(v => v.type === 'text')
195
+ .map(v => v.text)
196
+ .join('\n')
197
+ .substring(0, 500);
198
+ }
199
+ contentParts.push(`[tool-result: ${tr.status || 'done'}] ${preview}`);
200
+ }
201
+ }
202
+
203
+ const content = contentParts.join('\n');
204
+ if (!content) continue;
205
+
206
+ const mappedRole = role === 'user' ? 'user' : role === 'assistant' ? 'assistant' : role;
207
+ const msg = { role: mappedRole, content };
208
+ if (mappedRole === 'assistant' && chat._model) msg._model = chat._model;
209
+ if (toolCalls.length) msg._toolCalls = toolCalls;
210
+ result.push(msg);
211
+ }
212
+
213
+ return result;
214
+ }
215
+
216
+ function getMessagesFromJsonl(chat) {
217
+ const filePath = chat._fullPath || path.join(GOOSE_DIR, chat.composerId + '.jsonl');
218
+ if (!fs.existsSync(filePath)) return [];
219
+
220
+ const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
221
+ const result = [];
222
+
223
+ for (const line of lines) {
224
+ let obj;
225
+ try { obj = JSON.parse(line); } catch { continue; }
226
+
227
+ if (!obj.role) continue;
228
+
229
+ let parts;
230
+ try {
231
+ parts = typeof obj.content === 'string' ? JSON.parse(obj.content) : obj.content;
232
+ } catch {
233
+ // content might be plain text
234
+ if (typeof obj.content === 'string' && obj.content) {
235
+ result.push({ role: obj.role, content: obj.content });
236
+ }
237
+ continue;
238
+ }
239
+
240
+ if (!Array.isArray(parts)) continue;
241
+
242
+ const contentParts = [];
243
+ for (const part of parts) {
244
+ if (part.type === 'text' && part.text) {
245
+ contentParts.push(part.text);
246
+ } else if (part.type === 'toolRequest') {
247
+ const tc = part.toolCall?.value || {};
248
+ contentParts.push(`[tool-call: ${tc.name || 'tool'}(${Object.keys(tc.arguments || {}).join(', ')})]`);
249
+ } else if (part.type === 'toolResponse') {
250
+ const tr = part.toolResult || {};
251
+ let preview = '';
252
+ if (Array.isArray(tr.value)) {
253
+ preview = tr.value.filter(v => v.type === 'text').map(v => v.text).join('\n').substring(0, 500);
254
+ }
255
+ contentParts.push(`[tool-result] ${preview}`);
256
+ }
257
+ }
258
+
259
+ const content = contentParts.join('\n');
260
+ if (content) {
261
+ const msg = { role: obj.role, content };
262
+ if (obj.role === 'assistant' && chat._model) msg._model = chat._model;
263
+ result.push(msg);
264
+ }
265
+ }
266
+
267
+ return result;
268
+ }
269
+
270
+ function extractSessionModel(row) {
271
+ if (row.model_config_json) {
272
+ try {
273
+ const cfg = JSON.parse(row.model_config_json);
274
+ if (cfg.model) return cfg.model;
275
+ if (cfg.model_id) return cfg.model_id;
276
+ } catch {}
277
+ }
278
+ return null;
279
+ }
280
+
281
+ function resetCache() {
282
+ _configModel = undefined;
283
+ }
284
+
285
+ const labels = { 'goose': 'Goose' };
286
+
287
+ module.exports = { name, labels, getChats, getMessages, resetCache };
package/editors/index.js CHANGED
@@ -9,8 +9,15 @@ const gemini = require('./gemini');
9
9
  const copilot = require('./copilot');
10
10
  const cursorAgent = require('./cursor-agent');
11
11
  const commandcode = require('./commandcode');
12
+ const goose = require('./goose');
12
13
 
13
- const editors = [cursor, windsurf, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode];
14
+ const editors = [cursor, windsurf, claude, vscode, zed, opencode, codex, gemini, copilot, cursorAgent, commandcode, goose];
15
+
16
+ // Build a unified source → display-label map from all editor modules
17
+ const editorLabels = {};
18
+ for (const editor of editors) {
19
+ if (editor.labels) Object.assign(editorLabels, editor.labels);
20
+ }
14
21
 
15
22
  /**
16
23
  * Get all chats from all editor adapters, sorted by most recent first.
@@ -52,4 +59,4 @@ function resetCaches() {
52
59
  }
53
60
  }
54
61
 
55
- module.exports = { getAllChats, getMessages, editors, resetCaches };
62
+ module.exports = { getAllChats, getMessages, editors, editorLabels, resetCaches };
@@ -100,4 +100,6 @@ function getMessages(chat) {
100
100
  return result;
101
101
  }
102
102
 
103
- module.exports = { name, getChats, getMessages };
103
+ const labels = { 'opencode': 'OpenCode' };
104
+
105
+ module.exports = { name, labels, getChats, getMessages };
package/editors/vscode.js CHANGED
@@ -315,4 +315,6 @@ function getMessages(chat) {
315
315
  return messages;
316
316
  }
317
317
 
318
- module.exports = { name, getChats, getMessages };
318
+ const labels = { 'vscode': 'VS Code', 'vscode-insiders': 'VS Code Insiders' };
319
+
320
+ module.exports = { name, labels, getChats, getMessages };
@@ -24,19 +24,30 @@ function findLanguageServers() {
24
24
  _lsCache = [];
25
25
  try {
26
26
  const ps = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
27
+ // Also grab env vars for processes that use WINDSURF_CSRF_TOKEN instead of --csrf_token
28
+ const psEnv = execSync('ps eww -A', { encoding: 'utf-8', maxBuffer: 2 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }).split('\n');
29
+ const envCsrfByPid = {};
30
+ for (const envLine of psEnv) {
31
+ const envCsrf = envLine.match(/WINDSURF_CSRF_TOKEN=(\S+)/);
32
+ if (envCsrf) {
33
+ const envPid = envLine.match(/^\s*(\d+)/);
34
+ if (envPid) envCsrfByPid[envPid[1]] = envCsrf[1];
35
+ }
36
+ }
37
+
27
38
  for (const line of ps.split('\n')) {
28
- if (!line.includes('language_server_macos') || !line.includes('--csrf_token')) continue;
39
+ if (!line.includes('language_server_macos')) continue;
29
40
  const csrfMatch = line.match(/--csrf_token\s+(\S+)/);
30
41
  const ideMatch = line.match(/--ide_name\s+(\S+)/);
31
42
  const appDirMatch = line.match(/--app_data_dir\s+(\S+)/);
32
- if (!csrfMatch) continue;
33
- const csrf = csrfMatch[1];
34
- const ide = ideMatch ? ideMatch[1] : 'windsurf';
35
- const appDataDir = appDirMatch ? appDirMatch[1] : null;
36
- // Find port by checking listening sockets for this process
37
43
  const pidMatch = line.match(/^\S+\s+(\d+)/);
38
44
  if (!pidMatch) continue;
39
45
  const pid = pidMatch[1];
46
+ const csrf = csrfMatch ? csrfMatch[1] : envCsrfByPid[pid] || null;
47
+ if (!csrf) continue;
48
+ const ide = ideMatch ? ideMatch[1] : 'windsurf';
49
+ const appDataDir = appDirMatch ? appDirMatch[1] : null;
50
+ // Find port by checking listening sockets for this process
40
51
  try {
41
52
  const lsof = execSync(`lsof -i TCP -P -n -a -p ${pid} 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
42
53
  for (const l of lsof.split('\n')) {
@@ -336,4 +347,6 @@ function getMessages(chat) {
336
347
 
337
348
  function resetCache() { _lsCache = null; }
338
349
 
339
- module.exports = { name, sources, getChats, getMessages, resetCache };
350
+ const labels = { 'windsurf': 'Windsurf', 'windsurf-next': 'Windsurf Next', 'antigravity': 'Antigravity' };
351
+
352
+ module.exports = { name, sources, labels, getChats, getMessages, resetCache };
package/editors/zed.js CHANGED
@@ -135,4 +135,6 @@ function extractContent(content) {
135
135
  return { text: parts.join('\n') || '', toolCalls };
136
136
  }
137
137
 
138
- module.exports = { name, getChats, getMessages };
138
+ const labels = { 'zed': 'Zed' };
139
+
140
+ module.exports = { name, labels, getChats, getMessages };
package/index.js CHANGED
@@ -188,7 +188,7 @@ const WINDSURF_VARIANTS = [
188
188
  try {
189
189
  const ps = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
190
190
  for (const line of ps.split('\n')) {
191
- if (!line.includes('language_server_macos') || !line.includes('--csrf_token')) continue;
191
+ if (!line.includes('language_server_macos')) continue;
192
192
  const ideMatch = line.match(/--ide_name\s+(\S+)/);
193
193
  const appDirMatch = line.match(/--app_data_dir\s+(\S+)/);
194
194
  if (ideMatch) runningIdes.push(ideMatch[1]);
@@ -215,23 +215,7 @@ const WINDSURF_VARIANTS = [
215
215
  cache.initDb();
216
216
 
217
217
  // ── Detect editors & collect sessions ───────────────────────
218
- const { editors: editorModules } = require('./editors');
219
- const EDITOR_DISPLAY = [
220
- ['cursor', 'Cursor'],
221
- ['windsurf', 'Windsurf'],
222
- ['windsurf-next', 'Windsurf Next'],
223
- ['antigravity', 'Antigravity'],
224
- ['claude-code', 'Claude Code'],
225
- ['vscode', 'VS Code'],
226
- ['vscode-insiders', 'VS Code Insiders'],
227
- ['zed', 'Zed'],
228
- ['opencode', 'OpenCode'],
229
- ['codex', 'Codex'],
230
- ['gemini-cli', 'Gemini CLI'],
231
- ['copilot-cli', 'Copilot CLI'],
232
- ['cursor-agent', 'Cursor Agent'],
233
- ['commandcode', 'Command Code'],
234
- ];
218
+ const { editors: editorModules, editorLabels } = require('./editors');
235
219
 
236
220
  console.log(chalk.dim(' Looking for AI coding agents...'));
237
221
  const allChats = [];
@@ -247,8 +231,11 @@ allChats.sort((a, b) => (b.lastUpdatedAt || b.createdAt || 0) - (a.lastUpdatedAt
247
231
  const bySource = {};
248
232
  for (const chat of allChats) bySource[chat.source] = (bySource[chat.source] || 0) + 1;
249
233
 
250
- for (const [src, label] of EDITOR_DISPLAY) {
251
- const count = bySource[src] || 0;
234
+ const displayList = Object.entries(editorLabels)
235
+ .map(([src, label]) => [src, label, bySource[src] || 0])
236
+ .sort((a, b) => b[2] - a[2]);
237
+
238
+ for (const [src, label, count] of displayList) {
252
239
  if (count > 0) {
253
240
  console.log(` ${chalk.green('✓')} ${chalk.bold(label.padEnd(18))} ${chalk.dim(`${count} session${count === 1 ? '' : 's'}`)}`);
254
241
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlytics",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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": {
@@ -11,6 +11,7 @@ const PATHS = {
11
11
  vscode: 'M17.583.063a1.5 1.5 0 0 0-1.032.392 1.5 1.5 0 0 0-.001 0L7.04 9.708 2.81 6.442a1 1 0 0 0-1.32.098L.178 7.853a1 1 0 0 0 0 1.414l3.612 3.29-3.612 3.29a1 1 0 0 0 0 1.414L1.49 18.574a1 1 0 0 0 1.32.098L7.04 15.408l9.51 9.253a1.5 1.5 0 0 0 1.033.392A1.5 1.5 0 0 0 19.08 23.5V1.5A1.5 1.5 0 0 0 17.583.063M17.08 5.442l-5.5 7.115 5.5 7.115Z',
12
12
  antigravity: 'M21.751 22.607c1.34 1.005 3.35.335 1.508-1.508C17.73 15.74 18.904 1 12.037 1 5.17 1 6.342 15.74.815 21.1c-2.01 2.009.167 2.511 1.507 1.506 5.192-3.517 4.857-9.714 9.715-9.714 4.857 0 4.522 6.197 9.714 9.715z',
13
13
  command: 'M6,2A4,4 0 0,1 10,6V8H14V6A4,4 0 0,1 18,2A4,4 0 0,1 22,6A4,4 0 0,1 18,10H16V14H18A4,4 0 0,1 22,18A4,4 0 0,1 18,22A4,4 0 0,1 14,18V16H10V18A4,4 0 0,1 6,22A4,4 0 0,1 2,18A4,4 0 0,1 6,14H8V10H6A4,4 0 0,1 2,6A4,4 0 0,1 6,2M16,18A2,2 0 0,0 18,20A2,2 0 0,0 20,18A2,2 0 0,0 18,16H16V18M14,10H10V14H14V10M6,16A2,2 0 0,0 4,18A2,2 0 0,0 6,20A2,2 0 0,0 8,18V16H6M8,6A2,2 0 0,0 6,4A2,2 0 0,0 4,6A2,2 0 0,0 6,8H8V6M18,8A2,2 0 0,0 20,6A2,2 0 0,0 18,4A2,2 0 0,0 16,6V8H18Z',
14
+ goose: 'M22.112 23.596C23.018 23.399 23.979 22.864 23.979 22.864L22.297 21.479C21.466 20.795 20.759 19.973 20.206 19.05C19.441 17.774 18.385 16.697 17.125 15.908L16.509 15.55C16.298 15.403 16.151 15.175 16.13 14.917C16.117 14.751 16.157 14.602 16.25 14.471C16.57 14.019 18.227 12.053 18.531 11.802C18.922 11.479 19.357 11.21 19.762 10.902L19.934 10.77C19.936 10.768 19.938 10.767 19.94 10.765C20.07 10.663 20.192 10.554 20.29 10.425C20.641 10.018 20.726 9.658 20.747 9.499C20.7 9.346 20.558 9.003 20.163 8.608C20.411 8.623 20.71 8.819 20.982 9.05C21.165 8.758 21.356 8.45 21.547 8.141C21.674 7.934 21.485 7.78 21.48 7.775L21.479 7.775C21.479 7.775 21.479 7.774 21.479 7.774C21.474 7.769 21.319 7.579 21.114 7.707C20.673 7.979 20.234 8.252 19.842 8.5C19.842 8.5 19.379 8.49 18.829 8.964C18.7 9.062 18.591 9.184 18.489 9.314L18.484 9.32C18.439 9.377 18.396 9.434 18.352 9.492C18.044 9.897 17.775 10.332 17.452 10.723C17.201 11.027 15.235 12.684 14.783 13.004C14.652 13.097 14.504 13.137 14.337 13.124C14.08 13.103 13.851 12.956 13.704 12.745L13.346 12.129C12.557 10.868 11.48 9.813 10.204 9.048C9.281 8.495 8.459 7.787 7.775 6.957L6.39 5.275C6.39 5.275 5.854 6.236 5.658 7.141C5.931 7.474 6.644 8.298 7.473 8.928C6.581 8.509 5.922 8.184 5.402 7.913C5.322 8.506 5.353 9.403 5.436 10.098C5.999 10.344 6.957 10.724 7.933 10.926C7.152 11.108 6.296 11.141 5.635 11.128C5.751 11.558 5.914 11.997 6.132 12.438C6.224 12.642 6.326 12.842 6.435 13.037C6.785 13.133 8.159 13.333 8.89 13.169C8.163 13.428 6.942 13.865 6.942 13.865C7.88 15.031 8.916 15.98 8.916 15.98C10.492 15.133 10.852 15.017 12.034 14.244C10.119 15.802 9.622 16.438 9.085 17.091L8.71 17.617C8.516 17.89 8.347 18.18 8.206 18.484C7.734 19.5 7.065 21.666 7.065 21.666C6.946 22.043 7.222 22.32 7.589 22.189C7.589 22.189 9.754 21.521 10.77 21.048C11.074 20.907 11.364 20.738 11.637 20.544L12.163 20.169C12.339 20.024 12.514 19.882 12.707 19.714C12.707 19.714 14.03 21.282 15.39 22.313C15.39 22.313 15.826 21.092 16.086 20.364C15.921 21.096 16.121 22.469 16.218 22.819C16.412 22.929 16.612 23.03 16.816 23.123C17.258 23.341 17.696 23.503 18.126 23.619C18.113 22.958 18.146 22.102 18.329 21.321C18.531 22.297 18.91 23.256 19.157 23.819C19.851 23.902 20.748 23.933 21.341 23.853C21.07 23.333 20.746 22.673 20.326 21.781C20.956 22.611 21.781 23.324 22.113 23.597L22.112 23.596Z',
14
15
  // Terminal icon for generic editors
15
16
  terminal: 'M4 17.27V19h16v-1.73ZM4 5v1.73l7.07 4.55L4 15.82v1.73l10-6.46Z',
16
17
  }
@@ -32,6 +33,7 @@ const EDITOR_ICONS = {
32
33
  'opencode': 'terminal',
33
34
  'codex': 'terminal',
34
35
  'commandcode': 'command',
36
+ 'goose': 'goose',
35
37
  }
36
38
 
37
39
  export default function EditorIcon({ source, size = 16, className = '' }) {
@@ -104,7 +104,7 @@ export default function ShareModal({ open, onClose }) {
104
104
 
105
105
  const handleShareTwitter = async () => {
106
106
  await handleDownloadPng()
107
- const text = encodeURIComponent("Here's my agentic coding stats using github.com/f/agentlytics")
107
+ const text = encodeURIComponent("Here's my agentic coding stats using agentlytics.io\n\nrun `npx agentlytics` to get yours ✨\n\ngithub.com/f/agentlytics")
108
108
  window.open(`https://x.com/intent/post?text=${text}`, '_blank')
109
109
  }
110
110
 
@@ -14,6 +14,7 @@ export const EDITOR_COLORS = {
14
14
  'copilot-cli': '#8957e5',
15
15
  'cursor-agent': '#f59e0b',
16
16
  'commandcode': '#e11d48',
17
+ 'goose': '#333333',
17
18
  };
18
19
 
19
20
  export const EDITOR_LABELS = {
@@ -32,6 +33,7 @@ export const EDITOR_LABELS = {
32
33
  'copilot-cli': 'Copilot CLI',
33
34
  'cursor-agent': 'Cursor Agent',
34
35
  'commandcode': 'Command Code',
36
+ 'goose': 'Goose',
35
37
  };
36
38
 
37
39
  export function editorColor(src) {