agentlytics 0.0.4 → 0.0.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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/share-image.js +151 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlytics",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravity, OpenCode",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "index.js",
11
11
  "cache.js",
12
12
  "server.js",
13
+ "share-image.js",
13
14
  "editors/",
14
15
  "ui/src/",
15
16
  "ui/index.html",
package/share-image.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Generates a shareable SVG stats card from cached data.
3
+ */
4
+
5
+ function fmt(n) {
6
+ if (n == null) return '0';
7
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
8
+ if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
9
+ return n.toLocaleString();
10
+ }
11
+
12
+ const EDITOR_COLORS = {
13
+ 'cursor': '#f59e0b',
14
+ 'windsurf': '#06b6d4',
15
+ 'windsurf-next': '#22d3ee',
16
+ 'antigravity': '#a78bfa',
17
+ 'claude-code': '#f97316',
18
+ 'claude': '#f97316',
19
+ 'vscode': '#3b82f6',
20
+ 'vscode-insiders': '#60a5fa',
21
+ 'zed': '#10b981',
22
+ 'opencode': '#ec4899',
23
+ 'gemini-cli': '#4285f4',
24
+ 'copilot-cli': '#8957e5',
25
+ };
26
+
27
+ const EDITOR_LABELS = {
28
+ 'cursor': 'Cursor',
29
+ 'windsurf': 'Windsurf',
30
+ 'windsurf-next': 'WS Next',
31
+ 'antigravity': 'Antigravity',
32
+ 'claude-code': 'Claude Code',
33
+ 'claude': 'Claude Code',
34
+ 'vscode': 'VS Code',
35
+ 'vscode-insiders': 'VS Code Ins.',
36
+ 'zed': 'Zed',
37
+ 'opencode': 'OpenCode',
38
+ 'gemini-cli': 'Gemini CLI',
39
+ 'copilot-cli': 'Copilot CLI',
40
+ };
41
+
42
+ function generateShareSvg(overview, stats) {
43
+ const W = 800, H = 440;
44
+ const F = "Menlo, Monaco, Cascadia Code, Courier New, monospace";
45
+ const editors = overview.editors || [];
46
+ const tk = stats.tokens || {};
47
+ const streaks = stats.streaks || {};
48
+ const topModels = (stats.topModels || []).slice(0, 5);
49
+
50
+ // Editor bar chart
51
+ const maxEditorCount = Math.max(...editors.map(e => e.count), 1);
52
+ const editorBars = editors.slice(0, 8).map((e, i) => {
53
+ const barW = Math.max((e.count / maxEditorCount) * 180, 4);
54
+ const color = EDITOR_COLORS[e.id] || '#6b7280';
55
+ const label = (EDITOR_LABELS[e.id] || e.id).padEnd(12);
56
+ const y = 170 + i * 22;
57
+ return `
58
+ <text x="30" y="${y + 12}" fill="#586e75" font-size="10" font-family="${F}">${esc(label)}</text>
59
+ <rect x="140" y="${y + 1}" width="${barW}" height="14" rx="2" fill="${color}" opacity="0.8"/>
60
+ <text x="${146 + barW}" y="${y + 12}" fill="#839496" font-size="9" font-family="${F}">${e.count}</text>
61
+ `;
62
+ }).join('');
63
+
64
+ // Activity sparkline from hourly data
65
+ const hourly = stats.hourly || new Array(24).fill(0);
66
+ const maxH = Math.max(...hourly, 1);
67
+ const sparkW = 180, sparkH = 40;
68
+ const sparkPoints = hourly.map((v, i) => {
69
+ const x = 590 + (i / 23) * sparkW;
70
+ const y = 180 + sparkH - (v / maxH) * sparkH;
71
+ return `${x},${y}`;
72
+ }).join(' ');
73
+
74
+ // Top models list
75
+ const modelsList = topModels.map((m, i) => {
76
+ const y = 274 + i * 16;
77
+ const name = m.name.length > 24 ? m.name.substring(0, 24) : m.name;
78
+ return `<text x="590" y="${y}" fill="#586e75" font-size="9" font-family="${F}">${esc(name)} <tspan fill="#475569">${m.count}</tspan></text>`;
79
+ }).join('');
80
+
81
+ const dateStr = new Date().toISOString().split('T')[0];
82
+
83
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
84
+ <!-- Background -->
85
+ <rect width="${W}" height="${H}" rx="12" fill="#002b36"/>
86
+ <rect x="0.5" y="0.5" width="${W - 1}" height="${H - 1}" rx="12" fill="none" stroke="#073642" stroke-width="1"/>
87
+
88
+ <!-- Terminal title bar -->
89
+ <rect x="0" y="0" width="${W}" height="32" rx="12" fill="#073642"/>
90
+ <rect x="0" y="16" width="${W}" height="16" fill="#073642"/>
91
+ <circle cx="18" cy="16" r="5" fill="#dc322f" opacity="0.8"/>
92
+ <circle cx="36" cy="16" r="5" fill="#b58900" opacity="0.8"/>
93
+ <circle cx="54" cy="16" r="5" fill="#859900" opacity="0.8"/>
94
+ <text x="${W / 2}" y="20" fill="#586e75" font-size="11" font-family="${F}" text-anchor="middle">agentlytics</text>
95
+
96
+ <!-- Prompt line -->
97
+ <text x="24" y="58" fill="#859900" font-size="12" font-family="${F}">$</text>
98
+ <text x="40" y="58" fill="#93a1a1" font-size="12" font-family="${F}">npx agentlytics</text>
99
+
100
+ <!-- Divider -->
101
+ <line x1="24" y1="68" x2="${W - 24}" y2="68" stroke="#073642" stroke-width="1"/>
102
+
103
+ <!-- KPI row -->
104
+ <rect x="24" y="78" width="175" height="58" rx="6" fill="#073642"/>
105
+ <text x="36" y="96" fill="#586e75" font-size="9" font-family="${F}">sessions</text>
106
+ <text x="36" y="122" fill="#93a1a1" font-size="22" font-weight="bold" font-family="${F}">${fmt(overview.totalChats)}</text>
107
+
108
+ <rect x="210" y="78" width="175" height="58" rx="6" fill="#073642"/>
109
+ <text x="222" y="96" fill="#586e75" font-size="9" font-family="${F}">tokens</text>
110
+ <text x="222" y="122" fill="#93a1a1" font-size="22" font-weight="bold" font-family="${F}">${fmt((tk.input || 0) + (tk.output || 0))}</text>
111
+
112
+ <rect x="396" y="78" width="175" height="58" rx="6" fill="#073642"/>
113
+ <text x="408" y="96" fill="#586e75" font-size="9" font-family="${F}">active_days</text>
114
+ <text x="408" y="122" fill="#93a1a1" font-size="22" font-weight="bold" font-family="${F}">${streaks.totalDays || 0}</text>
115
+
116
+ <rect x="582" y="78" width="194" height="58" rx="6" fill="#073642"/>
117
+ <text x="594" y="96" fill="#586e75" font-size="9" font-family="${F}">streak <tspan fill="#475569">longest:${streaks.longest || 0}</tspan></text>
118
+ <text x="594" y="122" fill="#93a1a1" font-size="22" font-weight="bold" font-family="${F}">${streaks.current || 0} <tspan font-size="11" fill="#586e75">day${(streaks.current || 0) !== 1 ? 's' : ''}</tspan></text>
119
+
120
+ <!-- Editors section -->
121
+ <text x="24" y="160" fill="#859900" font-size="10" font-family="${F}"># editors</text>
122
+ ${editorBars}
123
+
124
+ <!-- Right column: Peak Hours -->
125
+ <text x="590" y="160" fill="#859900" font-size="10" font-family="${F}"># peak_hours</text>
126
+ <polyline points="${sparkPoints}" fill="none" stroke="#268bd2" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>
127
+ <text x="590" y="${180 + sparkH + 14}" fill="#475569" font-size="8" font-family="${F}">00:00</text>
128
+ <text x="${590 + sparkW - 28}" y="${180 + sparkH + 14}" fill="#475569" font-size="8" font-family="${F}">23:00</text>
129
+
130
+ <!-- Top Models -->
131
+ <text x="590" y="258" fill="#859900" font-size="10" font-family="${F}"># models</text>
132
+ ${modelsList}
133
+
134
+ <!-- Token breakdown -->
135
+ <line x1="24" y1="${H - 62}" x2="${W - 24}" y2="${H - 62}" stroke="#073642" stroke-width="1"/>
136
+ <text x="24" y="${H - 44}" fill="#586e75" font-size="9" font-family="${F}">in:${fmt(tk.input)} out:${fmt(tk.output)} cache:${fmt(tk.cacheRead)} tools:${fmt(stats.totalToolCalls || 0)} editors:${editors.length}</text>
137
+
138
+ <!-- Footer -->
139
+ <line x1="24" y1="${H - 28}" x2="${W - 24}" y2="${H - 28}" stroke="#073642" stroke-width="1"/>
140
+ <text x="24" y="${H - 10}" fill="#475569" font-size="9" font-family="${F}">github.com/f/agentlytics</text>
141
+ <text x="${W - 24}" y="${H - 10}" fill="#475569" font-size="9" font-family="${F}" text-anchor="end">${esc(dateStr)}</text>
142
+ </svg>`;
143
+
144
+ return svg;
145
+ }
146
+
147
+ function esc(str) {
148
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
149
+ }
150
+
151
+ module.exports = { generateShareSvg };