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.
- package/AGENTS.md +101 -181
- package/CHANGELOG.md +32 -0
- package/CLAUDE.md +72 -63
- package/KODELYTH.md +79 -44
- package/README.md +189 -204
- package/VERSION +1 -1
- package/agents/dependency-doctor.md +120 -0
- package/agents/env-debugger.md +154 -0
- package/agents/flake-hunter.md +142 -0
- package/agents/git-rescue.md +133 -0
- package/agents/release-captain.md +190 -0
- package/install.ps1 +9 -9
- package/install.sh +11 -97
- package/package.json +2 -2
- package/rules/common/agent-intent-routing.md +337 -0
- package/skills/agent-handoff/SKILL.md +184 -0
- package/skills/intent-routing/SKILL.md +134 -0
- package/dashboard/lib/agent-tracker.js +0 -366
- package/dashboard/lib/aggregator.js +0 -119
- package/dashboard/lib/cost-calculator.js +0 -50
- package/dashboard/lib/platform-detector.js +0 -89
- package/dashboard/lib/readers/antigravity-reader.js +0 -113
- package/dashboard/lib/readers/claude-reader.js +0 -135
- package/dashboard/lib/readers/codex-reader.js +0 -192
- package/dashboard/lib/readers/cursor-reader.js +0 -135
- package/dashboard/lib/readers/opencode-reader.js +0 -201
- package/dashboard/lib/readers/windsurf-reader.js +0 -146
- package/dashboard/package.json +0 -24
- package/dashboard/public/index.html +0 -1221
- package/dashboard/server.js +0 -119
- package/scripts/agent-tracker-hook.js +0 -81
- package/social/readme-lens.svg +0 -140
- package/social/readme-savings.svg +0 -56
|
@@ -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
|
-
}
|