agentlytics 0.0.3 → 0.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlytics",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
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": {
package/server.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const express = require('express');
2
2
  const path = require('path');
3
3
  const cache = require('./cache');
4
+ const { generateShareSvg } = require('./share-image');
4
5
 
5
6
  const app = express();
6
7
  app.use(express.json());
@@ -148,6 +149,19 @@ app.get('/api/schema', (req, res) => {
148
149
  }
149
150
  });
150
151
 
152
+ app.get('/api/share-image', (req, res) => {
153
+ try {
154
+ const overview = cache.getCachedOverview();
155
+ const stats = cache.getCachedDashboardStats();
156
+ const svg = generateShareSvg(overview, stats);
157
+ res.setHeader('Content-Type', 'image/svg+xml');
158
+ res.send(svg);
159
+ } catch (err) {
160
+ console.error('Share image error:', err);
161
+ res.status(500).json({ error: err.message, stack: err.stack });
162
+ }
163
+ });
164
+
151
165
  app.get('/api/refetch', async (req, res) => {
152
166
  res.writeHead(200, {
153
167
  'Content-Type': 'text/event-stream',
package/ui/src/lib/api.js CHANGED
@@ -83,6 +83,11 @@ export async function fetchSchema() {
83
83
  return res.json();
84
84
  }
85
85
 
86
+ export async function fetchShareImage() {
87
+ const res = await fetch(`${BASE}/api/share-image`);
88
+ return res.text();
89
+ }
90
+
86
91
  export async function fetchToolCalls(name, opts = {}) {
87
92
  const q = new URLSearchParams({ name });
88
93
  if (opts.limit) q.set('limit', opts.limit);
@@ -1,11 +1,11 @@
1
1
  import { useState, useEffect } from 'react'
2
2
  import { useNavigate } from 'react-router-dom'
3
- import { ArrowRight, X, Flame, Zap, MessageSquare, Wrench } from 'lucide-react'
3
+ import { ArrowRight, X, Flame, Zap, MessageSquare, Wrench, Share2 } from 'lucide-react'
4
4
  import { Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement, PointElement, LineElement, Filler } from 'chart.js'
5
5
  import { Doughnut, Bar, Line } from 'react-chartjs-2'
6
6
  import KpiCard from '../components/KpiCard'
7
7
  import ActivityHeatmap from '../components/ActivityHeatmap'
8
- import { fetchDailyActivity, fetchOverview as fetchOverviewApi, fetchDashboardStats } from '../lib/api'
8
+ import { fetchDailyActivity, fetchOverview as fetchOverviewApi, fetchDashboardStats, fetchShareImage } from '../lib/api'
9
9
  import { editorColor, editorLabel, formatNumber } from '../lib/constants'
10
10
  import { useTheme } from '../lib/theme'
11
11
 
@@ -30,6 +30,7 @@ export default function Dashboard({ overview }) {
30
30
  const [stats, setStats] = useState(null)
31
31
  const [selectedEditor, setSelectedEditor] = useState(null)
32
32
  const { dark } = useTheme()
33
+ const [sharing, setSharing] = useState(false)
33
34
  const txtColor = dark ? '#888' : '#555'
34
35
  const txtDim = dark ? '#555' : '#999'
35
36
  const gridColor = dark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)'
@@ -157,8 +158,70 @@ export default function Dashboard({ overview }) {
157
158
  return s + v * midpoints[i]
158
159
  }, 0) / tk.sessions).toFixed(1) : '—') : '—'
159
160
 
161
+ const handleShare = async () => {
162
+ setSharing(true)
163
+ try {
164
+ const svg = await fetchShareImage()
165
+ if (!svg || svg.startsWith('{')) throw new Error('Failed to fetch image')
166
+
167
+ // Try PNG conversion via canvas, fallback to SVG download
168
+ let downloaded = false
169
+ try {
170
+ const canvas = document.createElement('canvas')
171
+ canvas.width = 1600
172
+ canvas.height = 880
173
+ const ctx = canvas.getContext('2d')
174
+ const img = new Image()
175
+ const svgB64 = btoa(unescape(encodeURIComponent(svg)))
176
+ const dataUrl = `data:image/svg+xml;base64,${svgB64}`
177
+ await new Promise((resolve, reject) => {
178
+ img.onload = resolve
179
+ img.onerror = reject
180
+ img.src = dataUrl
181
+ })
182
+ ctx.drawImage(img, 0, 0, 1600, 880)
183
+ const pngUrl = canvas.toDataURL('image/png')
184
+ const a = document.createElement('a')
185
+ a.href = pngUrl
186
+ a.download = 'agentlytics.png'
187
+ a.click()
188
+ downloaded = true
189
+ } catch {
190
+ // Fallback: download SVG directly
191
+ const blob = new Blob([svg], { type: 'image/svg+xml' })
192
+ const a = document.createElement('a')
193
+ a.href = URL.createObjectURL(blob)
194
+ a.download = 'agentlytics.svg'
195
+ a.click()
196
+ URL.revokeObjectURL(a.href)
197
+ downloaded = true
198
+ }
199
+
200
+ if (downloaded) {
201
+ const text = encodeURIComponent("Here's my agentic coding stats using github.com/f/agentlytics")
202
+ window.open(`https://x.com/intent/post?text=${text}`, '_blank')
203
+ }
204
+ } catch (e) {
205
+ console.error('Share failed:', e)
206
+ }
207
+ setSharing(false)
208
+ }
209
+
160
210
  return (
161
211
  <div className="fade-in space-y-3">
212
+ {/* Share button */}
213
+ <div className="flex justify-end">
214
+ <button
215
+ onClick={handleShare}
216
+ disabled={sharing}
217
+ className="flex items-center gap-1.5 px-3 py-1 text-[11px] rounded-md transition hover:opacity-80"
218
+ style={{ background: '#6366f1', color: '#fff', opacity: sharing ? 0.5 : 1 }}
219
+ >
220
+ <Share2 size={12} />
221
+ {sharing ? 'Generating...' : 'Share Stats'}
222
+ </button>
223
+ </div>
224
+
162
225
  {/* Editor breakdown - top */}
163
226
  <div className="card p-3">
164
227
  <SectionTitle>editors</SectionTitle>