agentlytics 0.2.11 → 0.2.13

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.
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Agentlytics — AI Agent Analytics</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
10
+ <script type="module" crossorigin src="/assets/index-SOQVJIDS.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DV6ONi_F.css">
12
+ </head>
13
+ <body>
14
+ <div id="root"></div>
15
+ </body>
16
+ </html>
package/relay-client.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const http = require('http');
3
+ const https = require('https');
3
4
  const readline = require('readline');
4
5
  const crypto = require('crypto');
5
6
 
@@ -9,8 +10,8 @@ const SYNC_INTERVAL_MS = 30000; // 30 seconds
9
10
 
10
11
  const EDITOR_LABELS = {
11
12
  'cursor': 'Cursor',
12
- 'windsurf': 'Windsurf',
13
- 'windsurf-next': 'Windsurf Next',
13
+ 'devin': 'Devin',
14
+ 'devin-next': 'Devin Next',
14
15
  'antigravity': 'Antigravity',
15
16
  'claude-code': 'Claude Code',
16
17
  'claude': 'Claude Code',
@@ -20,7 +21,7 @@ const EDITOR_LABELS = {
20
21
  'opencode': 'OpenCode',
21
22
  'codex': 'Codex CLI',
22
23
  'gemini-cli': 'Gemini CLI',
23
- 'copilot-cli': 'Copilot CLI',
24
+ 'copilot-cli': 'GitHub Copilot',
24
25
  'cursor-agent': 'Cursor (Background Agent)',
25
26
  'commandcode': 'CommandCode',
26
27
  };
@@ -177,7 +178,7 @@ function collectProjectData(selectedFolders) {
177
178
  /**
178
179
  * POST data to relay server.
179
180
  */
180
- function postToRelay(host, port, username, data, authToken) {
181
+ function postToRelay(scheme, host, port, username, data, authToken) {
181
182
  return new Promise((resolve, reject) => {
182
183
  const payload = JSON.stringify({
183
184
  username,
@@ -200,8 +201,8 @@ function postToRelay(host, port, username, data, authToken) {
200
201
  method: 'POST',
201
202
  headers,
202
203
  };
203
-
204
- const req = http.request(options, (res) => {
204
+ const client = scheme === 'https' ? https : http
205
+ const req = client.request(options, (res) => {
205
206
  let body = '';
206
207
  res.on('data', (chunk) => { body += chunk; });
207
208
  res.on('end', () => {
@@ -234,6 +235,7 @@ async function startJoinClient(relayAddress, username) {
234
235
 
235
236
  // Parse host:port
236
237
  const parts = relayAddress.replace(/^https?:\/\//, '').split(':');
238
+ const scheme = relayAddress.startsWith("https://") ? "https" : 'http'
237
239
  const host = parts[0] || 'localhost';
238
240
  const port = parseInt(parts[1]) || 4638;
239
241
 
@@ -245,7 +247,7 @@ async function startJoinClient(relayAddress, username) {
245
247
 
246
248
  // Test connection
247
249
  try {
248
- const testResult = await postToRelay(host, port, username, { projects: [], chats: [], messages: [], stats: [] }, authToken);
250
+ const testResult = await postToRelay(scheme, host, port, username, { projects: [], chats: [], messages: [], stats: [] }, authToken);
249
251
  if (!testResult.ok) {
250
252
  const msg = testResult.error || 'unknown error';
251
253
  if (msg === 'Unauthorized') {
@@ -278,7 +280,7 @@ async function startJoinClient(relayAddress, username) {
278
280
  cache.scanAll(() => {}, { resetCaches: true });
279
281
  const data = collectProjectData(selectedFolders);
280
282
  data.projects = selectedFolders;
281
- const result = await postToRelay(host, port, username, data, authToken);
283
+ const result = await postToRelay(scheme, host, port, username, data, authToken);
282
284
  if (result.ok) {
283
285
  const s = result.synced || {};
284
286
  process.stdout.write(chalk.dim(`\r ⟳ Synced: ${s.chats || 0} chats, ${s.messages || 0} messages — ${new Date().toLocaleTimeString()} `));
package/server.js CHANGED
@@ -532,7 +532,7 @@ app.get('/api/mcps', async (req, res) => {
532
532
 
533
533
  // 5. Match tool calls to MCP servers using actual queried tool names.
534
534
  // Editors prefix MCP tool names in various ways:
535
- // - Windsurf: mcp{N}_{toolName} (e.g. mcp1_query-docs)
535
+ // - Devin: mcp{N}_{toolName} (e.g. mcp1_query-docs)
536
536
  // - Cursor: mcp_{ServerName}_{toolName} (e.g. mcp_Figma_get_figma_data)
537
537
  // - VS Code: mcp_{sanitizedId}_{toolName} (e.g. mcp_io_github_byt_execute_sql)
538
538
  // - Others: {server}_{sep}_{toolName} (e.g. prompts_chat__search_prompts)
@@ -546,7 +546,7 @@ app.get('/api/mcps', async (req, res) => {
546
546
  const tcName = tc.name;
547
547
  let serverName = null;
548
548
 
549
- // Pattern 1: Windsurf — mcp{N}_{toolName}
549
+ // Pattern 1: Devin — mcp{N}_{toolName}
550
550
  const windsurfMatch = tcName.match(/^mcp(\d+)_(.+)$/);
551
551
  if (windsurfMatch) {
552
552
  const stripped = windsurfMatch[2];
@@ -727,6 +727,108 @@ app.get('/api/all-projects', (req, res) => {
727
727
  }
728
728
  });
729
729
 
730
+ // ============================================================
731
+ // GSD endpoints
732
+ // ============================================================
733
+
734
+ app.get('/api/gsd/projects', (req, res) => {
735
+ try {
736
+ res.json(cache.getCachedGSDProjects());
737
+ } catch (err) {
738
+ res.status(500).json({ error: err.message });
739
+ }
740
+ });
741
+
742
+ app.get('/api/gsd/phases', (req, res) => {
743
+ try {
744
+ const { folder } = req.query;
745
+ if (!folder) return res.status(400).json({ error: 'folder query param required' });
746
+ res.json(cache.getCachedGSDPhases(folder));
747
+ } catch (err) {
748
+ res.status(500).json({ error: err.message });
749
+ }
750
+ });
751
+
752
+ app.get('/api/gsd/plan', (req, res) => {
753
+ try {
754
+ const { folder, phase } = req.query;
755
+ if (!folder || !phase) return res.status(400).json({ error: 'folder and phase query params required' });
756
+ const gsd = require('./editors/gsd');
757
+ const detail = gsd.getGSDPlanDetail(folder, phase);
758
+ if (!detail) return res.status(404).json({ error: 'Plan not found' });
759
+ res.json(detail);
760
+ } catch (err) {
761
+ res.status(500).json({ error: err.message });
762
+ }
763
+ });
764
+
765
+ app.get('/api/gsd/overview', (req, res) => {
766
+ try {
767
+ res.json(cache.getCachedGSDOverview());
768
+ } catch (err) {
769
+ res.status(500).json({ error: err.message });
770
+ }
771
+ });
772
+
773
+ app.get('/api/gsd/config', (req, res) => {
774
+ try {
775
+ const { folder } = req.query;
776
+ if (!folder) return res.status(400).json({ error: 'folder query param required' });
777
+ const configPath = require('path').join(folder, '.planning', 'config.json');
778
+ if (!fs.existsSync(configPath)) return res.json(null);
779
+ res.json(JSON.parse(fs.readFileSync(configPath, 'utf-8')));
780
+ } catch (err) {
781
+ res.status(500).json({ error: err.message });
782
+ }
783
+ });
784
+
785
+ app.get('/api/gsd/phase-tokens', (req, res) => {
786
+ try {
787
+ const { folder } = req.query;
788
+ if (!folder) return res.status(400).json({ error: 'folder query param required' });
789
+ res.json(cache.getGSDPhaseTokens(folder));
790
+ } catch (err) {
791
+ res.status(500).json({ error: err.message });
792
+ }
793
+ });
794
+
795
+ // Generic .planning file reader
796
+ // type: 'state' (project-level STATE.md) | 'research' | 'verification' | 'summary' (phase-level, requires phase param)
797
+ app.get('/api/gsd/file', (req, res) => {
798
+ try {
799
+ const { folder, phase: phaseDir, type } = req.query;
800
+ if (!folder || !type) return res.status(400).json({ error: 'folder and type required' });
801
+
802
+ const planningDir = path.join(folder, '.planning');
803
+ let content = null;
804
+
805
+ if (type === 'state') {
806
+ const filePath = path.join(planningDir, 'STATE.md');
807
+ if (fs.existsSync(filePath)) content = fs.readFileSync(filePath, 'utf-8');
808
+ } else if (phaseDir) {
809
+ const phaseFullDir = path.join(planningDir, 'phases', phaseDir);
810
+ const pattern = type === 'research' ? /RESEARCH\.md$/i
811
+ : type === 'verification' ? /VERIFICATION\.md$/i
812
+ : type === 'summary' ? /SUMMARY\.md$/i
813
+ : null;
814
+ if (pattern && fs.existsSync(phaseFullDir)) {
815
+ const files = fs.readdirSync(phaseFullDir).filter(f => pattern.test(f)).sort();
816
+ if (files.length > 0) {
817
+ const sections = files.map(f => {
818
+ const c = fs.readFileSync(path.join(phaseFullDir, f), 'utf-8');
819
+ return files.length > 1 ? `## ${f}\n\n${c}` : c;
820
+ });
821
+ content = sections.join('\n\n---\n\n');
822
+ }
823
+ }
824
+ }
825
+
826
+ res.json({ content });
827
+ } catch (err) {
828
+ res.status(500).json({ error: err.message });
829
+ }
830
+ });
831
+
730
832
  // SPA fallback
731
833
  app.get('*', (req, res) => {
732
834
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
package/share-image.js CHANGED
@@ -20,26 +20,27 @@ function fmtCost(n) {
20
20
 
21
21
  const EDITOR_COLORS = {
22
22
  'cursor': '#f59e0b',
23
- 'windsurf': '#06b6d4',
24
- 'windsurf-next': '#22d3ee',
23
+ 'devin': '#2563eb',
24
+ 'devin-next': '#7c3aed',
25
25
  'antigravity': '#a78bfa',
26
26
  'claude-code': '#f97316',
27
27
  'claude': '#f97316',
28
28
  'vscode': '#3b82f6',
29
29
  'vscode-insiders': '#60a5fa',
30
30
  'zed': '#10b981',
31
- 'opencode': '#ec4899',
32
- 'codex': '#0f766e',
31
+ 'opencode': '#656363',
32
+ 'codex': '#3941FF',
33
33
  'gemini-cli': '#4285f4',
34
34
  'copilot-cli': '#8957e5',
35
35
  'cursor-agent': '#f59e0b',
36
36
  'commandcode': '#e11d48',
37
+ 'kiro': '#9046FF',
37
38
  };
38
39
 
39
40
  const EDITOR_LABELS = {
40
41
  'cursor': 'Cursor',
41
- 'windsurf': 'Windsurf',
42
- 'windsurf-next': 'WS Next',
42
+ 'devin': 'Devin',
43
+ 'devin-next': 'Devin Next',
43
44
  'antigravity': 'Antigravity',
44
45
  'claude-code': 'Claude Code',
45
46
  'claude': 'Claude Code',
@@ -49,9 +50,10 @@ const EDITOR_LABELS = {
49
50
  'opencode': 'OpenCode',
50
51
  'codex': 'Codex',
51
52
  'gemini-cli': 'Gemini CLI',
52
- 'copilot-cli': 'Copilot CLI',
53
+ 'copilot-cli': 'GitHub Copilot',
53
54
  'cursor-agent': 'Cursor Agent',
54
55
  'commandcode': 'Cmd Code',
56
+ 'kiro': 'Kiro',
55
57
  };
56
58
 
57
59
  /**
package/ui/src/App.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useRef, useCallback } from 'react'
2
2
  import { Routes, Route, NavLink, useLocation } from 'react-router-dom'
3
- import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, CreditCard, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Plug, Copy, Check, Settings as SettingsIcon, Package, ChevronDown } from 'lucide-react'
3
+ import { Activity, BarChart3, GitCompare, MessageSquare, FolderOpen, DollarSign, CreditCard, Sun, Moon, RefreshCw, AlertTriangle, Github, Terminal, Database, Users, Plug, Copy, Check, Settings as SettingsIcon, Package, ChevronDown, Target } from 'lucide-react'
4
4
  import { fetchOverview, refetchAgents, fetchMode, fetchRelayConfig, getAuthToken, setOnAuthFailure } from './lib/api'
5
5
  import { useTheme } from './lib/theme'
6
6
  import { useLive } from './hooks/useLive'
@@ -21,6 +21,7 @@ import Subscriptions from './pages/Subscriptions'
21
21
  import MCPs from './pages/MCPs'
22
22
  import RelayDashboard from './pages/RelayDashboard'
23
23
  import RelayUserDetail from './pages/RelayUserDetail'
24
+ import GSD from './pages/GSD'
24
25
 
25
26
  function NavDropdown({ icon: Icon, label, items }) {
26
27
  const [open, setOpen] = useState(false)
@@ -154,6 +155,7 @@ export default function App() {
154
155
  { to: '/compare', icon: GitCompare, label: 'Compare' },
155
156
  ]},
156
157
  { to: '/artifacts', icon: Package, label: 'Artifacts' },
158
+ { to: '/gsd', icon: Target, label: 'GSD' },
157
159
  { to: '/mcps', icon: Plug, label: 'MCPs' },
158
160
  { to: '/sql', icon: Database, label: 'SQL' },
159
161
  ]
@@ -257,7 +259,7 @@ export default function App() {
257
259
  {refetchState && (
258
260
  <div className="flex items-center gap-2 px-4 py-1.5 text-[12px]" style={{ background: 'rgba(234,179,8,0.08)', borderBottom: '1px solid rgba(234,179,8,0.15)', color: '#ca8a04' }}>
259
261
  <AlertTriangle size={12} />
260
- <span>Windsurf, Windsurf Next, and Antigravity require their app to be running during refetch — otherwise their sessions won't be detected.</span>
262
+ <span>Devin, Devin Next, and Antigravity require their app to be running during refetch — otherwise their sessions won't be detected.</span>
261
263
  </div>
262
264
  )}
263
265
 
@@ -284,6 +286,7 @@ export default function App() {
284
286
  <Route path="/artifacts" element={<Artifacts />} />
285
287
  <Route path="/mcps" element={<MCPs />} />
286
288
  <Route path="/sql" element={<SqlViewer />} />
289
+ <Route path="/gsd" element={<GSD />} />
287
290
  <Route path="/settings" element={<Settings />} />
288
291
  </Routes>
289
292
  )}
@@ -1,8 +1,9 @@
1
- import { useState, useEffect, useRef } from 'react'
1
+ import { useState, useEffect, useRef, useCallback } from 'react'
2
2
  import { X, Download, Send, Search } from 'lucide-react'
3
3
  import { fetchChat, BASE } from '../lib/api'
4
4
  import { editorColor, editorLabel, formatDateTime, formatNumber } from '../lib/constants'
5
5
  import MessageContent, { ROLE_CONFIG } from './MessageRenderer'
6
+ import TokenTimeline from './TokenTimeline'
6
7
 
7
8
  export default function ChatSidebar({ chatId, onClose, fetchFn, extraHeader, username }) {
8
9
  const [chat, setChat] = useState(null)
@@ -47,6 +48,17 @@ export default function ChatSidebar({ chatId, onClose, fetchFn, extraHeader, use
47
48
  setMsgFilter('')
48
49
  }, [chatId])
49
50
 
51
+ const handleScrollToMessage = useCallback((msgIndex) => {
52
+ if (!scrollRef.current) return
53
+ const el = scrollRef.current.querySelector(`[data-msg-index="${msgIndex}"]`)
54
+ if (el) {
55
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' })
56
+ el.style.outline = '1.5px solid var(--c-accent)'
57
+ el.style.outlineOffset = '2px'
58
+ setTimeout(() => { el.style.outline = 'none' }, 1500)
59
+ }
60
+ }, [])
61
+
50
62
  if (!chatId) return null
51
63
 
52
64
  return (
@@ -107,6 +119,13 @@ export default function ChatSidebar({ chatId, onClose, fetchFn, extraHeader, use
107
119
  {chat.stats.toolCalls?.length > 0 && <span>{chat.stats.toolCalls.length} tools</span>}
108
120
  {chat.stats.totalInputTokens > 0 && <span>{formatNumber(chat.stats.totalInputTokens)} in</span>}
109
121
  {chat.stats.totalOutputTokens > 0 && <span>{formatNumber(chat.stats.totalOutputTokens)} out</span>}
122
+ {chat.createdAt && chat.lastUpdatedAt && (() => {
123
+ const ms = new Date(chat.lastUpdatedAt).getTime() - new Date(chat.createdAt).getTime()
124
+ if (ms <= 0) return null
125
+ const mins = Math.floor(ms / 60000)
126
+ const label = mins < 60 ? `${mins}m` : `${Math.floor(mins / 60)}h ${mins % 60}m`
127
+ return <span>{label}</span>
128
+ })()}
110
129
  {chat.stats.models?.length > 0 && (
111
130
  <span className="ml-auto font-mono truncate" style={{ color: 'var(--c-accent)', opacity: 0.7 }}>
112
131
  {[...new Set(chat.stats.models)].join(', ')}
@@ -115,6 +134,16 @@ export default function ChatSidebar({ chatId, onClose, fetchFn, extraHeader, use
115
134
  </div>
116
135
  )}
117
136
 
137
+ {/* Token Timeline */}
138
+ {chat && chat.messages.length > 0 && (
139
+ <TokenTimeline
140
+ messages={chat.messages}
141
+ createdAt={chat.createdAt}
142
+ lastUpdatedAt={chat.lastUpdatedAt}
143
+ onScrollToMessage={handleScrollToMessage}
144
+ />
145
+ )}
146
+
118
147
  {/* Search bar */}
119
148
  {chat && chat.messages.length > 0 && (
120
149
  <div className="shrink-0 px-4 py-2" style={{ borderBottom: '1px solid var(--c-border)' }}>
@@ -148,7 +177,7 @@ export default function ChatSidebar({ chatId, onClose, fetchFn, extraHeader, use
148
177
  const cfg = ROLE_CONFIG[msg.role] || ROLE_CONFIG.system
149
178
  const Icon = cfg.icon
150
179
  return (
151
- <div key={i} className="rounded-r px-3 py-2" style={{ borderLeft: `2px solid ${cfg.borderColor}`, background: cfg.bg }}>
180
+ <div key={i} data-msg-index={i} className="rounded-r px-3 py-2" style={{ borderLeft: `2px solid ${cfg.borderColor}`, background: cfg.bg, transition: 'outline 0.3s' }}>
152
181
  <div className="flex items-center gap-1.5 text-[11px] mb-1" style={{ color: 'var(--c-text2)' }}>
153
182
  <Icon size={11} />
154
183
  <span className="font-medium">{msg.role === 'user' && (username || chat?.username) ? (username || chat.username) : cfg.label}</span>
@@ -1,18 +1,44 @@
1
+ import { useId } from 'react'
1
2
  import { editorColor } from '../lib/constants'
2
3
 
3
4
  const PATHS = {
4
5
  cursor: 'M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23',
5
- windsurf: 'M23.55 5.067c-1.2038-.002-2.1806.973-2.1806 2.1765v4.8676c0 .972-.8035 1.7594-1.7597 1.7594-.568 0-1.1352-.286-1.4718-.7659l-4.9713-7.1003c-.4125-.5896-1.0837-.941-1.8103-.941-1.1334 0-2.1533.9635-2.1533 2.153v4.8957c0 .972-.7969 1.7594-1.7596 1.7594-.57 0-1.1363-.286-1.4728-.7658L.4076 5.1598C.2822 4.9798 0 5.0688 0 5.2882v4.2452c0 .2147.0656.4228.1884.599l5.4748 7.8183c.3234.462.8006.8052 1.3509.9298 1.3771.313 2.6446-.747 2.6446-2.0977v-4.893c0-.972.7875-1.7593 1.7596-1.7593h.003a1.798 1.798 0 0 1 1.4718.7658l4.9723 7.0994c.4135.5905 1.05.941 1.8093.941 1.1587 0 2.1515-.9645 2.1515-2.153v-4.8948c0-.972.7875-1.7594 1.7596-1.7594h.194a.22.22 0 0 0 .2204-.2202v-4.622a.22.22 0 0 0-.2203-.2203Z',
6
+ devin: {
7
+ viewBox: '0 0 35.25 40.38',
8
+ transform: 'translate(-14.777 -137.73)',
9
+ d: 'm39.167 155.54c0.84846-0.48774 1.8974-0.48774 2.7459 0l2.1929 1.2661c0.34838 0.19148 0.68547 0.18106 1.0225-5e-4l4.3834-2.5323c0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36328-0.1924-0.69894-0.50671-0.87811l-4.3834-2.5323c-0.31439-0.18031-0.70131-0.18031-1.0157 0l-4.3834 2.5323c-0.26369 0.1778-0.51306 0.45907-0.51306 0.88708v2.5323c0 0.97659-0.52447 1.8879-1.3729 2.3757-0.84846 0.48768-1.8974 0.48768-2.7459 0l-2.1929-1.2657c-0.46985-0.27058-0.95152-0.0497-1.0194-7e-3l-4.3834 2.5323c-0.31444 0.1803-0.5067 0.51501-0.5067 0.87811v5.0634c0 0.36327 0.19222 0.69892 0.5067 0.87809 1.4694 0.84487 2.9332 1.7016 4.4049 2.5413 0.29351 0.15057 0.6327 0.18995 1.0015-8e-3l2.1929-1.2661c0.84841-0.48768 1.8974-0.48768 2.7447 0 0.84487 0.48769 1.3729 1.4002 1.3729 2.3768v2.5323c0.0019 0.39738 0.22253 0.64724 0.51351 0.88532l4.3834 2.5323c0.31175 0.1833 0.7022 0.18306 1.0134 0l4.3834-2.5323c0.31439-0.18029 0.50666-0.51495 0.50666-0.87809v-5.0633c0-0.36328-0.1924-0.69894-0.50666-0.87815l-4.3834-2.5323c-0.34578-0.17938-0.66742-0.14455-1.0114 7e-3l-2.1929 1.2661c-0.84491 0.48773-1.8974 0.48773-2.7447 0-0.84491-0.48769-1.373-1.4002-1.373-2.3768 0-0.97659 0.52451-1.8879 1.373-2.3768l-0.0036-0.0134z M15.285 154.27l4.3834 2.5323c0.3113 0.18324 0.7023 0.18311 1.0134 0 1.4659-0.85041 2.9382-1.691 4.4013-2.5457 0.35014-0.20864 0.49266-0.58437 0.49266-0.87232v-2.5323c0-0.97664 0.52448-1.888 1.3729-2.3757 0.84844-0.48769 1.8974-0.48769 2.7459 0l2.1929 1.2661c0.13685 0.079 0.52674 0.25225 1.0015 8e-3 1.4704-0.84233 2.9372-1.6934 4.4049-2.5413 0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36329-0.1924-0.69894-0.50671-0.87811l-4.3834-2.5323c-0.31448-0.1803-0.7013-0.1803-1.0158 0-1.4659 0.85041-2.9382 1.691-4.4013 2.5457-0.38564 0.23443-0.49275 0.62763-0.49275 0.87367v2.5323c0 0.97659-0.52449 1.8879-1.3729 2.3768-0.84489 0.48773-1.8974 0.48773-2.7459 0l-2.1929-1.2661c-0.34408-0.18796-0.6721-0.14674-1.0194-7e-3l-4.3834 2.5323c-0.31448 0.1803-0.50669 0.51501-0.50669 0.8781v5.0634c0 0.36328 0.19222 0.69893 0.50669 0.8781v4e-3z M37.287 168.61l-4.3834-2.5323s-0.01522-5e-3 -0.02152-9e-3c-0.3311-0.16938-0.66408-0.16515-0.99911 0.0125l-2.1929 1.2661c-0.84488 0.48769-1.8974 0.48769-2.7423 0-0.84844-0.48774-1.3729-1.4002-1.3729-2.3768v-2.5323c0-0.15429-0.04232-0.59032-0.49139-0.87327-0.0054-4e-3 -0.01171-9e-3 -0.01792-0.0134l-4.3834-2.5323c-0.31448-0.18029-0.7013-0.18029-1.0158 0l-4.3834 2.5323c-0.31448 0.18029-0.50669 0.515-0.50669 0.8781v5.0634c0 0.36328 0.19222 0.69889 0.50669 0.87811l4.3834 2.5323c0.34369 0.18308 0.67257 0.16105 1.0158-5e-4l2.1929-1.2661c0.84844-0.48774 1.8974-0.48774 2.7459 0 0.84487 0.48769 1.3729 1.4002 1.3729 2.3768v2.5323c0 0.4525 0.25018 0.73699 0.51012 0.88707l4.3834 2.5323c0.31128 0.18323 0.70228 0.18312 1.0134 0l4.3834-2.5323c0.31439-0.18029 0.50671-0.515 0.50671-0.8781v-5.0634c0-0.36328-0.1924-0.69893-0.50671-0.87811z',
10
+ },
6
11
  claude: 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z',
7
12
  zed: 'M2.25 1.5a.75.75 0 0 0-.75.75v16.5H0V2.25A2.25 2.25 0 0 1 2.25 0h20.095c1.002 0 1.504 1.212.795 1.92L10.764 14.298h3.486V12.75h1.5v1.922a1.125 1.125 0 0 1-1.125 1.125H9.264l-2.578 2.578h11.689V9h1.5v9.375a1.5 1.5 0 0 1-1.5 1.5H5.185L2.562 22.5H21.75a.75.75 0 0 0 .75-.75V5.25H24v16.5A2.25 2.25 0 0 1 21.75 24H1.655C.653 24 .151 22.788.86 22.08L13.19 9.75H9.75v1.5h-1.5V9.375A1.125 1.125 0 0 1 9.375 8.25h5.314l2.625-2.625H5.625V15h-1.5V5.625a1.5 1.5 0 0 1 1.5-1.5h13.19L21.438 1.5z',
13
+ opencode: {
14
+ viewBox: '0 0 24 30',
15
+ paths: [
16
+ { d: 'M18 24H6V12H18V24Z', fill: '#CFCECD' },
17
+ { d: 'M18 6H6V24H18V6ZM24 30H0V0H24V30Z', fill: '#656363' },
18
+ ],
19
+ },
8
20
  gemini: 'M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81',
21
+ codex: {
22
+ d: 'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z',
23
+ gradient: [
24
+ ['0%', '#B1A7FF'],
25
+ ['5%', '#7A9DFF'],
26
+ ['100%', '#3941FF'],
27
+ ],
28
+ },
9
29
  copilot: 'M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z',
10
30
  // VS Code - official bracket icon
11
31
  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
32
  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
33
  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
34
  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',
15
- kiro: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m-1.5 14.5v-9l7 4.5-7 4.5',
35
+ kiro: {
36
+ paths: [
37
+ { d: 'M18.8 0H5.2A5.2 5.2 0 000 5.2v13.6A5.2 5.2 0 005.2 24h13.6a5.2 5.2 0 005.2-5.2V5.2A5.2 5.2 0 0018.8 0z', fill: '#9046FF' },
38
+ { d: 'M7.97 16.376c-1.644 3.642 1.86 4.556 4.443 2.424.76 2.39 3.608.607 4.631-1.247 2.251-4.084 1.342-8.249 1.108-9.108-1.6-5.859-9.6-5.869-10.976.03-.323 1.033-.328 2.206-.507 3.423-.09.617-.16 1.009-.393 1.655-.139.373-.323.7-.62 1.257-.458.865-.264 2.53 2.101 1.665l.224-.1h-.01l-.001.001z', fill: '#fff' },
39
+ { d: 'M12.722 10.985c-.656 0-.755-.785-.755-1.252 0-.423.074-.756.218-.97a.61.61 0 01.537-.283c.229 0 .428.095.567.289.159.218.243.55.243.964 0 .785-.303 1.252-.805 1.252h-.005zm2.703 0c-.656 0-.755-.785-.755-1.252 0-.423.074-.756.219-.97a.61.61 0 01.536-.283c.229 0 .428.095.567.289.159.218.243.55.243.964 0 .785-.303 1.252-.805 1.252h-.005z', fill: '#000' },
40
+ ],
41
+ },
16
42
  // Terminal icon for generic editors
17
43
  terminal: 'M4 17.27V19h16v-1.73ZM4 5v1.73l7.07 4.55L4 15.82v1.73l10-6.46Z',
18
44
  }
@@ -21,8 +47,8 @@ const PATHS = {
21
47
  const EDITOR_ICONS = {
22
48
  'cursor': 'cursor',
23
49
  'cursor-agent': 'cursor',
24
- 'windsurf': 'windsurf',
25
- 'windsurf-next': 'windsurf',
50
+ 'devin': 'devin',
51
+ 'devin-next': 'devin',
26
52
  'antigravity': 'antigravity',
27
53
  'claude-code': 'claude',
28
54
  'claude': 'claude',
@@ -31,8 +57,8 @@ const EDITOR_ICONS = {
31
57
  'zed': 'zed',
32
58
  'gemini-cli': 'gemini',
33
59
  'copilot-cli': 'copilot',
34
- 'opencode': 'terminal',
35
- 'codex': 'terminal',
60
+ 'opencode': 'opencode',
61
+ 'codex': 'codex',
36
62
  'commandcode': 'command',
37
63
  'goose': 'goose',
38
64
  'kiro': 'kiro',
@@ -40,10 +66,14 @@ const EDITOR_ICONS = {
40
66
 
41
67
  export default function EditorIcon({ source, size = 16, className = '' }) {
42
68
  const pathKey = EDITOR_ICONS[source] || 'terminal'
43
- const d = PATHS[pathKey]
69
+ const icon = PATHS[pathKey]
44
70
  const color = editorColor(source)
71
+ const pathData = typeof icon === 'string' ? { d: icon, viewBox: '0 0 24 24' } : icon
72
+ const rawId = useId()
73
+ const gradientId = pathData?.gradient ? `icon-${rawId.replace(/:/g, '')}-${pathKey}` : null
74
+ const fill = gradientId ? `url(#${gradientId})` : color
45
75
 
46
- if (!d) {
76
+ if (!pathData?.d && !pathData?.paths?.length) {
47
77
  return (
48
78
  <span
49
79
  className={`inline-block rounded-full flex-shrink-0 ${className}`}
@@ -54,14 +84,33 @@ export default function EditorIcon({ source, size = 16, className = '' }) {
54
84
 
55
85
  return (
56
86
  <svg
57
- viewBox="0 0 24 24"
87
+ viewBox={pathData.viewBox || '0 0 24 24'}
58
88
  width={size}
59
89
  height={size}
60
90
  className={`inline-block flex-shrink-0 ${className}`}
61
- fill={color}
91
+ fill={fill}
62
92
  xmlns="http://www.w3.org/2000/svg"
63
93
  >
64
- <path d={d} />
94
+ {pathData.gradient ? (
95
+ <defs>
96
+ <linearGradient id={gradientId} x1="12" x2="12" y1="0" y2="24" gradientUnits="userSpaceOnUse">
97
+ {pathData.gradient.map(([offset, stopColor]) => (
98
+ <stop key={offset} offset={offset} stopColor={stopColor} />
99
+ ))}
100
+ </linearGradient>
101
+ </defs>
102
+ ) : null}
103
+ {pathData.transform ? (
104
+ <g transform={pathData.transform}>
105
+ <path d={pathData.d} />
106
+ </g>
107
+ ) : pathData.paths ? (
108
+ pathData.paths.map((path) => (
109
+ <path key={path.d} d={path.d} fill={path.fill || fill} fillOpacity={path.fillOpacity} />
110
+ ))
111
+ ) : (
112
+ <path d={pathData.d} />
113
+ )}
65
114
  </svg>
66
115
  )
67
116
  }