kodelyth-ecc 1.2.2 → 1.3.0

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.
@@ -1,366 +0,0 @@
1
- // Kodelyth Lens — Agent Auto-Tracker
2
- // Detects which ECC agents were used from conversation messages
3
- // Works across Claude Code, Cursor, Windsurf, Codex session logs
4
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
5
-
6
- export const ECC_AGENTS = [
7
- // Kodelyth Exclusive
8
- 'kodelyth-advisor', 'debug-detective', 'ux-reviewer', 'api-guardian',
9
- 'pair-programmer', 'migration-guide',
10
- // Code Review
11
- 'code-reviewer', 'typescript-reviewer', 'python-reviewer', 'go-reviewer',
12
- 'rust-reviewer', 'java-reviewer', 'kotlin-reviewer', 'cpp-reviewer',
13
- 'csharp-reviewer', 'flutter-reviewer',
14
- // Build Fixers
15
- 'build-error-resolver', 'go-build-resolver', 'rust-build-resolver',
16
- 'java-build-resolver', 'kotlin-build-resolver', 'cpp-build-resolver',
17
- 'dart-build-resolver', 'pytorch-build-resolver',
18
- // Planning
19
- 'planner', 'architect', 'code-architect', 'code-explorer', 'chief-of-staff',
20
- // Security
21
- 'security-reviewer', 'healthcare-reviewer',
22
- // Quality
23
- 'refactor-cleaner', 'code-simplifier', 'performance-optimizer',
24
- 'type-design-analyzer', 'silent-failure-hunter',
25
- // Testing
26
- 'tdd-guide', 'e2e-runner', 'pr-test-analyzer',
27
- // Documentation
28
- 'doc-updater', 'docs-lookup', 'comment-analyzer',
29
- // Open Source
30
- 'opensource-forker', 'opensource-sanitizer', 'opensource-packager',
31
- // Specialized
32
- 'seo-specialist', 'database-reviewer', 'loop-operator', 'harness-optimizer',
33
- // GAN
34
- 'gan-planner', 'gan-generator', 'gan-evaluator',
35
- // Meta
36
- 'claude-code-guide', 'conversation-analyzer',
37
- ];
38
-
39
- // Agent category colors for dashboard
40
- export const AGENT_COLORS = {
41
- 'kodelyth-advisor': '#7c3aed',
42
- 'debug-detective': '#dc2626',
43
- 'ux-reviewer': '#0891b2',
44
- 'api-guardian': '#f59e0b',
45
- 'pair-programmer': '#10b981',
46
- 'migration-guide': '#8b5cf6',
47
- 'code-reviewer': '#3b82f6',
48
- 'security-reviewer': '#ef4444',
49
- 'tdd-guide': '#06b6d4',
50
- 'performance-optimizer': '#f97316',
51
- 'planner': '#6366f1',
52
- 'architect': '#8b5cf6',
53
- };
54
-
55
- const DEFAULT_COLOR = '#475569';
56
-
57
- export function getAgentColor(name) {
58
- return AGENT_COLORS[name] || DEFAULT_COLOR;
59
- }
60
-
61
- // ── Intent / Emotion Detection ────────────────────────────────────────────────
62
- // Maps user emotional state and task keywords → recommended ECC agents
63
- // This layer runs in ADDITION to explicit invocation patterns —
64
- // it infers agent usage from what the user actually needed, not just what they typed.
65
- const INTENT_MAP = [
66
- // ── Stuck / Lost / Don't know where to start ─────────────────────────────
67
- {
68
- patterns: [
69
- /i[' ]?m\s+(stuck|lost|confused|overwhelmed|not sure|unsure|clueless)/i,
70
- /don[' ]?t\s+know\s+(where|how|what)\s+to\s+start/i,
71
- /where\s+do\s+i\s+(even\s+)?start/i,
72
- /help\s+me\s+figure\s+out/i,
73
- /what\s+(should|do)\s+i\s+do/i,
74
- /i\s+don[' ]?t\s+understand\s+this/i,
75
- /not\s+sure\s+what\s+to\s+do\s+next/i,
76
- ],
77
- agents: ['kodelyth-advisor'],
78
- },
79
-
80
- // ── Bug / Error / Something broke ────────────────────────────────────────
81
- {
82
- patterns: [
83
- /\b(bug|bugs|buggy)\b/i,
84
- /\b(broken|broke|breaking)\b/i,
85
- /\berror\b.*\b(can[' ]?t|cannot|don[' ]?t|wont|won[' ]?t)\s+fix/i,
86
- /why\s+(is|does|isn[' ]?t|doesn[' ]?t)\s+this\s+(work|working|fail|failing)/i,
87
- /\b(crash|crashed|crashing|exception|traceback|stack\s*trace)\b/i,
88
- /i[' ]?ve\s+been\s+(trying|debugging|fighting)\s+this\s+(for|all)/i,
89
- /can[' ]?t\s+figure\s+out\s+(what|why|where|how)/i,
90
- /this\s+(code|thing|function|module|component)\s+(won[' ]?t|doesn[' ]?t)\s+work/i,
91
- /something\s+is\s+(wrong|broken|off|weird)\s+with/i,
92
- /\bnot\s+working\b/i,
93
- ],
94
- agents: ['debug-detective'],
95
- },
96
-
97
- // ── Build failure ─────────────────────────────────────────────────────────
98
- {
99
- patterns: [
100
- /build\s+(fail|failed|failing|error|broke)/i,
101
- /\bcompile\s+(error|fail|failed)\b/i,
102
- /\btype\s+error\b/i,
103
- /\bts\s*\(\d+\)\b/i,
104
- /\bcargo\s+build\b/i,
105
- /\bnpm\s+(run\s+build|build)\b.*\bfail/i,
106
- /can[' ]?t\s+build/i,
107
- ],
108
- agents: ['build-error-resolver'],
109
- },
110
-
111
- // ── Security / Vulnerability ──────────────────────────────────────────────
112
- {
113
- patterns: [
114
- /\b(security|secure|insecure|vulnerability|vulnerabilities|vulnerable)\b/i,
115
- /\b(sql\s+injection|xss|csrf|authentication|authorization|auth\s+bypass)\b/i,
116
- /\b(hack|hacked|exposed|leaked|leak|breach|exploit)\b/i,
117
- /is\s+this\s+(safe|secure|vulnerable)/i,
118
- /\b(api\s+key|secret|credential|password)\s+(exposed|leaked|in\s+(code|git|repo))\b/i,
119
- ],
120
- agents: ['security-reviewer'],
121
- },
122
-
123
- // ── Performance / Slow / Bottleneck ──────────────────────────────────────
124
- {
125
- patterns: [
126
- /\b(slow|sluggish|laggy|performance|performant|optimize|optimization)\b/i,
127
- /\b(memory\s+(leak|usage|pressure)|high\s+cpu|high\s+memory)\b/i,
128
- /\b(bottleneck|profiling|bundle\s+size|load\s+time|render\s+time)\b/i,
129
- /why\s+is\s+this\s+(so\s+)?slow/i,
130
- /make\s+this\s+(faster|more\s+efficient|quicker)/i,
131
- /\b(n\+1|n\s*\+\s*1|query\s+optimization)\b/i,
132
- ],
133
- agents: ['performance-optimizer'],
134
- },
135
-
136
- // ── Planning / Architecture / Design ─────────────────────────────────────
137
- {
138
- patterns: [
139
- /how\s+should\s+i\s+(structure|design|architect|build|implement)\s+this/i,
140
- /\b(plan|planning|roadmap|approach|strategy)\b.*\b(feature|system|project|app)\b/i,
141
- /\b(architecture|architectural|system\s+design)\b/i,
142
- /where\s+should\s+(this|the\s+code|it)\s+go/i,
143
- /best\s+(way|approach|practice)\s+to\s+(build|implement|design)/i,
144
- /should\s+i\s+(use|create|add|build)\s+a/i,
145
- ],
146
- agents: ['planner', 'architect'],
147
- },
148
-
149
- // ── Code review / Quality ─────────────────────────────────────────────────
150
- {
151
- patterns: [
152
- /\b(review|code\s+review|review\s+this\s+code|lgtm|looks\s+good)\b/i,
153
- /\b(clean\s+(up|code)|refactor|dead\s+code|unused|duplicate)\b/i,
154
- /is\s+this\s+(code\s+)?(good|clean|correct|right|okay|ok)\??/i,
155
- /anything\s+(wrong|bad|to\s+improve)\s+(with|in)\s+this/i,
156
- ],
157
- agents: ['code-reviewer'],
158
- },
159
-
160
- // ── Tests / Testing / Coverage ────────────────────────────────────────────
161
- {
162
- patterns: [
163
- /\b(test|tests|testing|unit\s+test|integration\s+test|e2e)\b/i,
164
- /\b(coverage|tdd|test\s+driven|failing\s+test)\b/i,
165
- /write\s+(a\s+)?test\s+for/i,
166
- /how\s+do\s+i\s+test\s+this/i,
167
- ],
168
- agents: ['tdd-guide'],
169
- },
170
-
171
- // ── UI / UX / Frontend / Accessibility ───────────────────────────────────
172
- {
173
- patterns: [
174
- /\b(ui|ux|user\s+(interface|experience)|frontend|front[- ]end)\b/i,
175
- /\b(accessibility|a11y|wcag|screen\s+reader|aria)\b/i,
176
- /\b(usability|interaction|click|hover|form|button|modal)\b/i,
177
- /does\s+this\s+(look|feel|work)\s+(right|good|okay)\s+for\s+(users|mobile)/i,
178
- ],
179
- agents: ['ux-reviewer'],
180
- },
181
-
182
- // ── Documentation ─────────────────────────────────────────────────────────
183
- {
184
- patterns: [
185
- /\b(document|docs|documentation|readme|jsdoc|comment)\b/i,
186
- /add\s+(docs|documentation|comments|jsdoc)/i,
187
- /how\s+do\s+i\s+document/i,
188
- ],
189
- agents: ['doc-updater'],
190
- },
191
-
192
- // ── Database / SQL / Query ────────────────────────────────────────────────
193
- {
194
- patterns: [
195
- /\b(sql|database|db|query|migration|schema|index|postgres|mysql|supabase)\b/i,
196
- /\b(slow\s+query|n\+1|missing\s+index|table\s+scan)\b/i,
197
- /how\s+should\s+i\s+(model|structure|design)\s+(the\s+)?(database|schema|table)/i,
198
- ],
199
- agents: ['database-reviewer'],
200
- },
201
-
202
- // ── Open source / Release ─────────────────────────────────────────────────
203
- {
204
- patterns: [
205
- /\b(open[\s-]?source|publish|release|public\s+repo|make\s+(it\s+)?public)\b/i,
206
- /\b(sanitize|strip\s+secrets|remove\s+(credentials|api\s+keys))\b/i,
207
- ],
208
- agents: ['opensource-forker', 'opensource-sanitizer'],
209
- },
210
- ];
211
-
212
- /**
213
- * Detect ECC agents from user intent/emotion — goes beyond explicit invocations.
214
- * Maps what the user says they're experiencing → the agent that helps with that.
215
- * @param {Array<{role, content, timestamp}>} messages
216
- * @returns {Array<{agent, timestamp, role, source}>}
217
- */
218
- export function detectAgentsByIntent(messages) {
219
- const found = [];
220
-
221
- for (const msg of messages) {
222
- if (!msg.content) continue;
223
- // Only scan user messages for intent signals
224
- if (msg.role && msg.role !== 'user') continue;
225
-
226
- const text = typeof msg.content === 'string'
227
- ? msg.content
228
- : JSON.stringify(msg.content);
229
-
230
- for (const { patterns, agents } of INTENT_MAP) {
231
- const matched = patterns.some(p => p.test(text));
232
- if (!matched) continue;
233
-
234
- for (const agent of agents) {
235
- found.push({
236
- agent,
237
- timestamp: msg.timestamp || null,
238
- role: 'user',
239
- source: 'intent',
240
- });
241
- }
242
- }
243
- }
244
-
245
- // Deduplicate by agent+timestamp
246
- const seen = new Set();
247
- return found.filter(f => {
248
- const key = `${f.agent}|${f.timestamp}`;
249
- if (seen.has(key)) return false;
250
- seen.add(key);
251
- return true;
252
- });
253
- }
254
-
255
- // Patterns that indicate ECC agent invocation
256
- const INVOKE_PATTERNS = [
257
- /\buse\s+([\w-]+)/gi,
258
- /\brun\s+([\w-]+)\s+agent/gi,
259
- /@([\w-]+)/g,
260
- /invoke\s+([\w-]+)/gi,
261
- /delegate\s+to\s+([\w-]+)/gi,
262
- /ask\s+([\w-]+)\s+to/gi,
263
- /launch\s+([\w-]+)/gi,
264
- ];
265
-
266
- /**
267
- * Detect ECC agent names in a list of messages.
268
- * Combines two detection layers:
269
- * 1. Explicit invocation — "use debug-detective", "@code-reviewer", etc.
270
- * 2. Intent / emotion — "I'm stuck on this bug" → debug-detective
271
- * @param {Array<{role, content, timestamp}>} messages
272
- * @returns {Array<{agent, timestamp, role, source}>}
273
- */
274
- export function detectAgentsInMessages(messages) {
275
- const explicit = detectAgentsExplicit(messages);
276
- const intent = detectAgentsByIntent(messages);
277
-
278
- // Merge — prefer explicit over intent for the same agent+timestamp
279
- const seen = new Set();
280
- const merged = [];
281
-
282
- for (const f of [...explicit, ...intent]) {
283
- const key = `${f.agent}|${f.timestamp}`;
284
- if (seen.has(key)) continue;
285
- seen.add(key);
286
- merged.push(f);
287
- }
288
-
289
- return merged;
290
- }
291
-
292
- /**
293
- * Detect ECC agents via explicit invocation patterns only.
294
- * @param {Array<{role, content, timestamp}>} messages
295
- * @returns {Array<{agent, timestamp, role, source}>}
296
- */
297
- function detectAgentsExplicit(messages) {
298
- const found = [];
299
-
300
- for (const msg of messages) {
301
- if (!msg.content) continue;
302
- const text = typeof msg.content === 'string'
303
- ? msg.content
304
- : JSON.stringify(msg.content);
305
-
306
- for (const pattern of INVOKE_PATTERNS) {
307
- pattern.lastIndex = 0;
308
- let match;
309
- while ((match = pattern.exec(text)) !== null) {
310
- const candidate = match[1].toLowerCase().trim();
311
- if (ECC_AGENTS.includes(candidate)) {
312
- found.push({
313
- agent: candidate,
314
- timestamp: msg.timestamp || null,
315
- role: msg.role || 'user',
316
- source: 'explicit',
317
- });
318
- }
319
- }
320
- }
321
- }
322
-
323
- // Deduplicate by agent+timestamp
324
- const seen = new Set();
325
- return found.filter(f => {
326
- const key = `${f.agent}|${f.timestamp}`;
327
- if (seen.has(key)) return false;
328
- seen.add(key);
329
- return true;
330
- });
331
- }
332
-
333
- /**
334
- * Aggregate agent usage across all sessions into leaderboard
335
- * @param {Array} sessions
336
- * @returns {Array<AgentStat>}
337
- */
338
- export function buildAgentLeaderboard(sessions) {
339
- const map = {};
340
-
341
- for (const session of sessions) {
342
- for (const use of (session.agents || [])) {
343
- const { agent } = use;
344
- if (!map[agent]) {
345
- map[agent] = {
346
- name: agent,
347
- color: getAgentColor(agent),
348
- calls: 0,
349
- sessionIds: new Set(),
350
- platforms: new Set(),
351
- lastUsed: null,
352
- };
353
- }
354
- map[agent].calls++;
355
- map[agent].sessionIds.add(session.id || session.timestamp || Math.random());
356
- map[agent].platforms.add(session.platform || 'unknown');
357
- if (!map[agent].lastUsed || (use.timestamp && use.timestamp > map[agent].lastUsed)) {
358
- map[agent].lastUsed = use.timestamp;
359
- }
360
- }
361
- }
362
-
363
- return Object.values(map)
364
- .map(a => ({ ...a, sessions: a.sessionIds.size, platforms: [...a.platforms] }))
365
- .sort((a, b) => b.calls - a.calls);
366
- }
@@ -1,119 +0,0 @@
1
- // Kodelyth Lens — Data Aggregator
2
- // Merges data from all platforms into unified stats
3
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
4
-
5
- import { detectPlatforms } from './platform-detector.js';
6
- import { readClaudeData } from './readers/claude-reader.js';
7
- import { readCursorData } from './readers/cursor-reader.js';
8
- import { readWindsurfData } from './readers/windsurf-reader.js';
9
- import { readCodexData } from './readers/codex-reader.js';
10
- import { readOpenCodeData } from './readers/opencode-reader.js';
11
- import { readAntigravityData } from './readers/antigravity-reader.js';
12
- import { buildAgentLeaderboard } from './agent-tracker.js';
13
- import { buildDailyCosts, cacheSavings } from './cost-calculator.js';
14
-
15
- export async function aggregateAll() {
16
- const platforms = detectPlatforms();
17
-
18
- // Read all platforms in parallel, never crash if one fails
19
- const [claude, cursor, windsurf, codex, opencode, antigravity] = await Promise.all([
20
- platforms.claude.installed ? readClaudeData(platforms.claude.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
21
- platforms.cursor.installed ? readCursorData(platforms.cursor.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
22
- platforms.windsurf.installed ? readWindsurfData(platforms.windsurf.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
23
- platforms.codex.installed ? readCodexData(platforms.codex.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
24
- platforms.opencode.installed ? readOpenCodeData(platforms.opencode.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
25
- platforms.antigravity.installed ? readAntigravityData(platforms.antigravity.dataDir).catch(() => ({ sessions: [] })) : { sessions: [] },
26
- ]);
27
-
28
- const allSessions = [
29
- ...claude.sessions,
30
- ...cursor.sessions,
31
- ...windsurf.sessions,
32
- ...codex.sessions,
33
- ...opencode.sessions,
34
- ...antigravity.sessions,
35
- ].sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
36
-
37
- const today = new Date().toISOString().slice(0, 10);
38
- // A session counts as "today" if it started today OR was last active today
39
- // (long-running sessions that span multiple days still appear in today's stats)
40
- const todaySessions = allSessions.filter(s => s.date === today || s.lastDate === today);
41
-
42
- const totalCacheSaved = allSessions.reduce((n, s) => n + (s.cacheSaved || 0), 0);
43
-
44
- const stats = {
45
- today: {
46
- sessions: todaySessions.length,
47
- tokens: sumField(todaySessions, 'tokens.total'),
48
- cost: round(sumField(todaySessions, 'cost')),
49
- agentCalls: sumField(todaySessions, 'agentCalls'),
50
- },
51
- allTime: {
52
- sessions: allSessions.length,
53
- tokens: sumField(allSessions, 'tokens.total'),
54
- cost: round(sumField(allSessions, 'cost')),
55
- agentCalls: sumField(allSessions, 'agentCalls'),
56
- cacheSaved: round(totalCacheSaved),
57
- cacheHitRate: calcCacheHitRate(allSessions),
58
- },
59
- };
60
-
61
- const platformStats = buildPlatformStats(allSessions, platforms);
62
- const agents = buildAgentLeaderboard(allSessions);
63
- const dailyCosts = buildDailyCosts(allSessions);
64
-
65
- return {
66
- stats,
67
- agents,
68
- sessions: allSessions.slice(0, 100),
69
- costs: dailyCosts,
70
- platforms: platformStats,
71
- windsurf: { quotaData: windsurf.quotaData || null },
72
- updatedAt: new Date().toISOString(),
73
- };
74
- }
75
-
76
- function sumField(arr, field) {
77
- return arr.reduce((n, s) => {
78
- const parts = field.split('.');
79
- let val = s;
80
- for (const p of parts) val = val?.[p];
81
- return n + (Number(val) || 0);
82
- }, 0);
83
- }
84
-
85
- function round(n) { return Math.round(n * 10000) / 10000; }
86
-
87
- function calcCacheHitRate(sessions) {
88
- const totalInput = sumField(sessions, 'tokens.input');
89
- const totalRead = sumField(sessions, 'tokens.cacheRead');
90
- if (!totalInput && !totalRead) return 0;
91
- return Math.round((totalRead / (totalInput + totalRead)) * 100);
92
- }
93
-
94
- function buildPlatformStats(sessions, platforms) {
95
- const map = {};
96
- for (const [key, info] of Object.entries(platforms)) {
97
- map[key] = {
98
- label: info.label,
99
- color: info.color,
100
- installed: info.installed,
101
- sessions: 0,
102
- tokens: 0,
103
- cost: 0,
104
- agents: 0,
105
- };
106
- }
107
-
108
- for (const s of sessions) {
109
- const p = map[s.platform];
110
- if (!p) continue;
111
- p.sessions++;
112
- p.tokens += s.tokens?.total || 0;
113
- p.cost += s.cost || 0;
114
- p.agents += s.agentCalls || 0;
115
- }
116
-
117
- for (const p of Object.values(map)) p.cost = round(p.cost);
118
- return map;
119
- }
@@ -1,50 +0,0 @@
1
- // Kodelyth Lens — Cost Calculator
2
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
3
-
4
- const PRESETS = {
5
- anthropic: { input: 3.00, output: 15.00, cacheRead: 0.30, cacheWrite: 3.75 },
6
- bedrock: { input: 5.00, output: 25.00, cacheRead: 0.50, cacheWrite: 6.25 },
7
- };
8
-
9
- export function getRates() {
10
- const preset = process.env.PRICING_PRESET || 'anthropic';
11
- const base = PRESETS[preset] || PRESETS.anthropic;
12
- return {
13
- input: parseFloat(process.env.RATE_INPUT || base.input),
14
- output: parseFloat(process.env.RATE_OUTPUT || base.output),
15
- cacheRead: parseFloat(process.env.RATE_CACHE_READ || base.cacheRead),
16
- cacheWrite: parseFloat(process.env.RATE_CACHE_WRITE|| base.cacheWrite),
17
- };
18
- }
19
-
20
- export function tokensToCost(tokens) {
21
- const r = getRates();
22
- return (
23
- ((tokens.input || 0) / 1_000_000) * r.input +
24
- ((tokens.output || 0) / 1_000_000) * r.output +
25
- ((tokens.cacheRead || 0) / 1_000_000) * r.cacheRead +
26
- ((tokens.cacheWrite || 0) / 1_000_000) * r.cacheWrite
27
- );
28
- }
29
-
30
- export function cacheSavings(tokens) {
31
- const r = getRates();
32
- // Savings = what cache reads would have cost as input tokens minus what they actually cost
33
- const saved = ((tokens.cacheRead || 0) / 1_000_000) * (r.input - r.cacheRead);
34
- return Math.max(0, saved);
35
- }
36
-
37
- export function buildDailyCosts(sessions) {
38
- const byDay = {};
39
- for (const s of sessions) {
40
- const day = s.date;
41
- if (!byDay[day]) byDay[day] = { date: day, cost: 0, tokens: 0, sessions: 0, agents: 0 };
42
- byDay[day].cost += s.cost || 0;
43
- byDay[day].tokens += (s.tokens?.total || 0);
44
- byDay[day].sessions += 1;
45
- byDay[day].agents += s.agentCalls || 0;
46
- }
47
- return Object.values(byDay)
48
- .sort((a, b) => a.date.localeCompare(b.date))
49
- .slice(-30); // last 30 days
50
- }
@@ -1,89 +0,0 @@
1
- // Kodelyth Lens — Platform Detector
2
- // Auto-detects installed AI coding platforms
3
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
4
-
5
- import fs from 'node:fs';
6
- import path from 'node:path';
7
- import os from 'node:os';
8
-
9
- const HOME = os.homedir();
10
-
11
- function exists(p) {
12
- try { return fs.existsSync(p); } catch { return false; }
13
- }
14
-
15
- export function detectPlatforms() {
16
- const appSupport = path.join(HOME, 'Library', 'Application Support'); // macOS
17
- const appData = path.join(HOME, 'AppData', 'Roaming'); // Windows
18
-
19
- return {
20
- claude: {
21
- installed: exists(path.join(HOME, '.claude')),
22
- dataDir: process.env.CLAUDE_DIR || path.join(HOME, '.claude'),
23
- label: 'Claude Code',
24
- color: '#f97316',
25
- },
26
- cursor: {
27
- installed: exists(path.join(HOME, '.cursor')) ||
28
- exists(path.join(appSupport, 'Cursor')) ||
29
- exists(path.join(appData, 'Cursor')) ||
30
- exists(path.join(HOME, '.config', 'Cursor')),
31
- dataDir: process.env.CURSOR_DIR ||
32
- (exists(path.join(appSupport, 'Cursor'))
33
- ? path.join(appSupport, 'Cursor')
34
- : exists(path.join(HOME, '.config', 'Cursor'))
35
- ? path.join(HOME, '.config', 'Cursor')
36
- : path.join(HOME, '.cursor')),
37
- label: 'Cursor',
38
- color: '#3b82f6',
39
- },
40
- windsurf: {
41
- installed: exists(path.join(HOME, '.codeium', 'windsurf')) ||
42
- exists(path.join(appSupport, 'Windsurf')) ||
43
- exists(path.join(appData, 'Windsurf')),
44
- dataDir: process.env.WINDSURF_DIR ||
45
- (exists(path.join(appSupport, 'Windsurf'))
46
- ? path.join(appSupport, 'Windsurf')
47
- : path.join(HOME, '.codeium', 'windsurf')),
48
- label: 'Windsurf',
49
- color: '#06b6d4',
50
- },
51
- codex: {
52
- installed: exists(path.join(HOME, '.codex')),
53
- dataDir: process.env.CODEX_DIR || path.join(HOME, '.codex'),
54
- label: 'Codex CLI',
55
- color: '#10b981',
56
- },
57
- antigravity: {
58
- installed: exists(path.join(appSupport, 'Antigravity')) ||
59
- exists(path.join(appData, 'Antigravity')) ||
60
- exists(path.join(HOME, '.antigravity')) ||
61
- exists(path.join(process.cwd(), '.agent')),
62
- dataDir: process.env.ANTIGRAVITY_DIR ||
63
- (exists(path.join(appSupport, 'Antigravity'))
64
- ? path.join(appSupport, 'Antigravity')
65
- : exists(path.join(appData, 'Antigravity'))
66
- ? path.join(appData, 'Antigravity')
67
- : path.join(HOME, '.antigravity')),
68
- label: 'Antigravity',
69
- color: '#7c3aed',
70
- },
71
- opencode: {
72
- installed: exists(path.join(HOME, '.opencode')) ||
73
- exists(path.join(HOME, '.config', 'opencode')) ||
74
- exists(path.join(appSupport, 'opencode')) ||
75
- exists(path.join(appData, 'opencode')) ||
76
- exists(path.join(process.cwd(), '.opencode')),
77
- dataDir: process.env.OPENCODE_DIR ||
78
- (exists(path.join(HOME, '.opencode'))
79
- ? path.join(HOME, '.opencode')
80
- : exists(path.join(HOME, '.config', 'opencode'))
81
- ? path.join(HOME, '.config', 'opencode')
82
- : exists(path.join(appSupport, 'opencode'))
83
- ? path.join(appSupport, 'opencode')
84
- : path.join(process.cwd(), '.opencode')),
85
- label: 'OpenCode',
86
- color: '#f43f5e',
87
- },
88
- };
89
- }