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,119 +0,0 @@
1
- #!/usr/bin/env node
2
- // Kodelyth Lens — AI Coding Agent Dashboard
3
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
4
- // Tracks usage across Claude Code, Cursor, Windsurf, Codex, Antigravity
5
-
6
- import http from 'node:http';
7
- import fs from 'node:fs';
8
- import path from 'node:path';
9
- import { fileURLToPath } from 'node:url';
10
- import { aggregateAll } from './lib/aggregator.js';
11
-
12
- // Load .env if present
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- const envFile = path.join(__dirname, '.env');
15
- if (fs.existsSync(envFile)) {
16
- for (const line of fs.readFileSync(envFile, 'utf-8').split('\n')) {
17
- const m = line.match(/^([^#=\s]+)\s*=\s*(.*)$/);
18
- if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
19
- }
20
- }
21
-
22
- const PORT = parseInt(process.env.PORT || '3456', 10);
23
- const PUBLIC = path.join(__dirname, 'public');
24
- const CACHE_TTL = 30_000; // 30 seconds
25
-
26
- const MIME = {
27
- '.html': 'text/html; charset=utf-8',
28
- '.css': 'text/css',
29
- '.js': 'application/javascript',
30
- '.json': 'application/json',
31
- '.svg': 'image/svg+xml',
32
- '.ico': 'image/x-icon',
33
- };
34
-
35
- // Simple in-memory cache
36
- let _cache = { data: null, ts: 0 };
37
-
38
- async function getData(force = false) {
39
- const now = Date.now();
40
- if (!force && _cache.data && now - _cache.ts < CACHE_TTL) return _cache.data;
41
- _cache.data = await aggregateAll();
42
- _cache.ts = now;
43
- return _cache.data;
44
- }
45
-
46
- const server = http.createServer(async (req, res) => {
47
- const url = new URL(req.url, `http://localhost:${PORT}`);
48
-
49
- // ── API ──────────────────────────────────────────────────────────────────
50
- if (url.pathname.startsWith('/api/')) {
51
- res.setHeader('Content-Type', 'application/json');
52
- res.setHeader('Access-Control-Allow-Origin', '*');
53
- res.setHeader('Cache-Control', 'no-cache');
54
-
55
- try {
56
- const force = url.searchParams.get('refresh') === '1';
57
- const data = await getData(force);
58
-
59
- const routes = {
60
- '/api/all': () => data,
61
- '/api/stats': () => data.stats,
62
- '/api/agents': () => data.agents,
63
- '/api/sessions': () => data.sessions,
64
- '/api/costs': () => data.costs,
65
- '/api/platforms': () => data.platforms,
66
- };
67
-
68
- const handler = routes[url.pathname];
69
- if (handler) {
70
- res.end(JSON.stringify(handler()));
71
- } else {
72
- res.statusCode = 404;
73
- res.end(JSON.stringify({ error: 'Not found' }));
74
- }
75
- } catch (err) {
76
- res.statusCode = 500;
77
- res.end(JSON.stringify({ error: err.message }));
78
- }
79
- return;
80
- }
81
-
82
- // ── Static files ─────────────────────────────────────────────────────────
83
- let file = url.pathname === '/' ? 'index.html' : url.pathname.slice(1);
84
- const filePath = path.resolve(PUBLIC, file);
85
-
86
- // Prevent path traversal attacks
87
- if (!filePath.startsWith(PUBLIC + path.sep) && filePath !== path.resolve(PUBLIC, 'index.html')) {
88
- res.writeHead(403, { 'Content-Type': 'text/plain' });
89
- res.end('Forbidden');
90
- return;
91
- }
92
-
93
- if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
94
- const ext = path.extname(filePath);
95
- res.setHeader('Content-Type', MIME[ext] || 'text/plain');
96
- res.end(fs.readFileSync(filePath));
97
- } else {
98
- // SPA fallback
99
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
100
- res.end(fs.readFileSync(path.join(PUBLIC, 'index.html')));
101
- }
102
- });
103
-
104
- server.listen(PORT, () => {
105
- console.log('');
106
- console.log(' \x1b[36m\x1b[1m██╗ ██╗ ██████╗ ██████╗ ███████╗██╗ ██╗ ██╗████████╗██╗ ██╗\x1b[0m');
107
- console.log(' \x1b[36m\x1b[1m██║ ██╔╝██╔═══██╗██╔══██╗██╔════╝██║ ╚██╗ ██╔╝╚══██╔══╝██║ ██║\x1b[0m');
108
- console.log(' \x1b[36m\x1b[1m█████╔╝ ██║ ██║██║ ██║█████╗ ██║ ╚████╔╝ ██║ ███████║\x1b[0m');
109
- console.log(' \x1b[36m\x1b[1m██╔═██╗ ██║ ██║██║ ██║██╔══╝ ██║ ╚██╔╝ ██║ ██╔══██║\x1b[0m');
110
- console.log(' \x1b[36m\x1b[1m██║ ██╗╚██████╔╝██████╔╝███████╗███████╗██║ ██║ ██║ ██║\x1b[0m');
111
- console.log(' \x1b[36m\x1b[1m╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝\x1b[0m');
112
- console.log('');
113
- console.log(' \x1b[1mKodelyth Lens\x1b[0m — AI Coding Agent Dashboard');
114
- console.log(` \x1b[32m\x1b[1m→ http://localhost:${PORT}\x1b[0m`);
115
- console.log('');
116
- console.log(' Tracking: Claude Code · Cursor · Windsurf · Codex · OpenCode · Antigravity');
117
- console.log(' Press Ctrl+C to stop.');
118
- console.log('');
119
- });
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env node
2
- // Kodelyth Lens — Agent Auto-Tracker Hook
3
- // Runs as a PostToolUse hook in Claude Code.
4
- // Detects ECC agent invocations from tool calls and writes to
5
- // ~/.claude/kodelyth-agent-tracking.jsonl for the dashboard to read.
6
- // Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
7
-
8
- import fs from 'node:fs';
9
- import path from 'node:path';
10
- import os from 'node:os';
11
-
12
- const ECC_AGENTS = [
13
- 'kodelyth-advisor', 'debug-detective', 'ux-reviewer', 'api-guardian',
14
- 'pair-programmer', 'migration-guide', 'code-reviewer', 'typescript-reviewer',
15
- 'python-reviewer', 'go-reviewer', 'rust-reviewer', 'java-reviewer',
16
- 'kotlin-reviewer', 'cpp-reviewer', 'csharp-reviewer', 'flutter-reviewer',
17
- 'build-error-resolver', 'go-build-resolver', 'rust-build-resolver',
18
- 'java-build-resolver', 'kotlin-build-resolver', 'cpp-build-resolver',
19
- 'dart-build-resolver', 'pytorch-build-resolver', 'planner', 'architect',
20
- 'code-architect', 'code-explorer', 'chief-of-staff', 'security-reviewer',
21
- 'healthcare-reviewer', 'refactor-cleaner', 'code-simplifier',
22
- 'performance-optimizer', 'type-design-analyzer', 'silent-failure-hunter',
23
- 'tdd-guide', 'e2e-runner', 'pr-test-analyzer', 'doc-updater',
24
- 'docs-lookup', 'comment-analyzer', 'opensource-forker', 'opensource-sanitizer',
25
- 'opensource-packager', 'seo-specialist', 'database-reviewer',
26
- 'gan-planner', 'gan-generator', 'gan-evaluator', 'loop-operator',
27
- 'harness-optimizer', 'claude-code-guide',
28
- ];
29
-
30
- const INVOKE_PATTERNS = [
31
- /\buse\s+([\w-]+)/gi,
32
- /@([\w-]+)/g,
33
- /invoke\s+([\w-]+)/gi,
34
- /delegate\s+to\s+([\w-]+)/gi,
35
- /launch\s+([\w-]+)/gi,
36
- ];
37
-
38
- function detectAgents(text) {
39
- const found = new Set();
40
- for (const pattern of INVOKE_PATTERNS) {
41
- pattern.lastIndex = 0;
42
- let match;
43
- while ((match = pattern.exec(text)) !== null) {
44
- const candidate = match[1].toLowerCase();
45
- if (ECC_AGENTS.includes(candidate)) found.add(candidate);
46
- }
47
- }
48
- return [...found];
49
- }
50
-
51
- // Read hook payload from stdin (Claude Code passes JSON on stdin)
52
- let raw = '';
53
- process.stdin.on('data', chunk => raw += chunk);
54
- process.stdin.on('end', () => {
55
- try {
56
- const payload = raw ? JSON.parse(raw) : {};
57
- const toolName = payload.tool_name || '';
58
- const toolInput = payload.tool_input || {};
59
- const toolResult = payload.tool_result|| {};
60
-
61
- // Collect all text from tool input and result
62
- const text = JSON.stringify({ toolName, toolInput, toolResult });
63
- const agents = detectAgents(text);
64
-
65
- if (!agents.length) process.exit(0);
66
-
67
- const trackingFile = path.join(os.homedir(), '.claude', 'kodelyth-agent-tracking.jsonl');
68
- const now = new Date().toISOString();
69
- const date = now.slice(0, 10);
70
-
71
- const lines = agents.map(agent =>
72
- JSON.stringify({ agent, timestamp: now, date, tool: toolName, source: 'hook' })
73
- ).join('\n') + '\n';
74
-
75
- fs.mkdirSync(path.dirname(trackingFile), { recursive: true });
76
- fs.appendFileSync(trackingFile, lines);
77
- } catch (e) {
78
- // Never crash — hooks must be silent
79
- }
80
- process.exit(0);
81
- });
@@ -1,140 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 360" width="900" height="360">
2
- <defs>
3
- <linearGradient id="klLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4
- <stop offset="0%" style="stop-color:#7c3aed;stop-opacity:1" />
5
- <stop offset="100%" style="stop-color:#a78bfa;stop-opacity:1" />
6
- </linearGradient>
7
- <filter id="cardShadow" x="-5%" y="-5%" width="110%" height="120%">
8
- <feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000000" flood-opacity="0.08"/>
9
- </filter>
10
- </defs>
11
-
12
- <!-- White background -->
13
- <rect width="900" height="360" fill="#f8fafc" rx="12"/>
14
-
15
- <!-- TOP BAR -->
16
- <rect x="0" y="0" width="900" height="52" fill="#0f172a" rx="12"/>
17
- <rect x="0" y="40" width="900" height="12" fill="#0f172a"/>
18
-
19
- <!-- KL Logo box -->
20
- <rect x="16" y="10" width="34" height="32" rx="6" fill="url(#klLogoGrad)"/>
21
- <text x="33" y="31" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="800" fill="#ffffff" text-anchor="middle">KL</text>
22
-
23
- <!-- Kodelyth Lens text -->
24
- <text x="60" y="28" font-family="system-ui, -apple-system, sans-serif" font-size="16" font-weight="700" fill="#ffffff">Kodelyth Lens</text>
25
-
26
- <!-- v1.1 badge -->
27
- <rect x="188" y="16" width="36" height="20" rx="4" fill="#7c3aed" fill-opacity="0.3" stroke="#7c3aed" stroke-opacity="0.5" stroke-width="1"/>
28
- <text x="206" y="30" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#a78bfa" text-anchor="middle" font-weight="600">v1.1</text>
29
-
30
- <!-- Updated indicator -->
31
- <circle cx="244" cy="26" r="4" fill="#10b981"/>
32
- <text x="252" y="30" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#94a3b8"> Updated 03:25:55</text>
33
-
34
- <!-- Refresh button -->
35
- <rect x="806" y="14" width="68" height="24" rx="5" fill="#1e293b" stroke="#334155" stroke-width="1"/>
36
- <text x="840" y="30" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#94a3b8" text-anchor="middle">⟳ Refresh</text>
37
-
38
- <!-- STAT CARDS ROW -->
39
- <!-- Card 1: Agent Calls -->
40
- <rect x="14" y="64" width="164" height="76" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
41
- <rect x="14" y="64" width="4" height="76" rx="2" fill="#7c3aed"/>
42
- <text x="32" y="90" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" fill="#0f172a">5.2K</text>
43
- <text x="32" y="108" font-family="system-ui, -apple-system, sans-serif" font-size="9" fill="#64748b" letter-spacing="1" font-weight="600">AGENT CALLS</text>
44
- <text x="32" y="125" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#a78bfa">↑ total invocations</text>
45
-
46
- <!-- Card 2: Total Sessions -->
47
- <rect x="188" y="64" width="164" height="76" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
48
- <rect x="188" y="64" width="4" height="76" rx="2" fill="#3b82f6"/>
49
- <text x="206" y="90" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" fill="#0f172a">41</text>
50
- <text x="206" y="108" font-family="system-ui, -apple-system, sans-serif" font-size="9" fill="#64748b" letter-spacing="1" font-weight="600">TOTAL SESSIONS</text>
51
- <text x="206" y="125" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#3b82f6">↑ all time</text>
52
-
53
- <!-- Card 3: Cache Hit Rate -->
54
- <rect x="362" y="64" width="164" height="76" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
55
- <rect x="362" y="64" width="4" height="76" rx="2" fill="#10b981"/>
56
- <text x="380" y="90" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" fill="#0f172a">98%</text>
57
- <text x="380" y="108" font-family="system-ui, -apple-system, sans-serif" font-size="9" fill="#64748b" letter-spacing="1" font-weight="600">CACHE HIT RATE</text>
58
- <text x="380" y="125" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#10b981">↑ excellent</text>
59
-
60
- <!-- Card 4: Total Cost -->
61
- <rect x="536" y="64" width="164" height="76" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
62
- <rect x="536" y="64" width="4" height="76" rx="2" fill="#f59e0b"/>
63
- <text x="554" y="90" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="700" fill="#0f172a">$596.63</text>
64
- <text x="554" y="108" font-family="system-ui, -apple-system, sans-serif" font-size="9" fill="#64748b" letter-spacing="1" font-weight="600">TOTAL COST</text>
65
- <text x="554" y="125" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#f59e0b">↓ vs $3,074 baseline</text>
66
-
67
- <!-- Card 5: Tokens Used -->
68
- <rect x="710" y="64" width="176" height="76" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
69
- <rect x="710" y="64" width="4" height="76" rx="2" fill="#06b6d4"/>
70
- <text x="728" y="90" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="700" fill="#0f172a">31.7M tok</text>
71
- <text x="728" y="108" font-family="system-ui, -apple-system, sans-serif" font-size="9" fill="#64748b" letter-spacing="1" font-weight="600">TOKENS USED</text>
72
- <text x="728" y="125" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#06b6d4">↑ total processed</text>
73
-
74
- <!-- BOTTOM SECTION -->
75
- <!-- Left panel: Agent Leaderboard -->
76
- <rect x="14" y="156" width="540" height="190" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
77
- <text x="30" y="180" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="700" fill="#0f172a">Agent Leaderboard</text>
78
- <text x="530" y="180" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#94a3b8" text-anchor="end">calls</text>
79
-
80
- <!-- Divider -->
81
- <line x1="30" y1="188" x2="540" y2="188" stroke="#f1f5f9" stroke-width="1"/>
82
-
83
- <!-- Row 1: ux-reviewer -->
84
- <text x="30" y="210" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#64748b" font-weight="600">#1</text>
85
- <text x="55" y="210" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06b6d4" font-weight="600">ux-reviewer</text>
86
- <rect x="200" y="200" width="280" height="14" rx="7" fill="#f1f5f9"/>
87
- <rect x="200" y="200" width="252" height="14" rx="7" fill="#06b6d4" fill-opacity="0.7"/>
88
- <text x="495" y="212" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#0f172a" text-anchor="end" font-weight="600">2.0K</text>
89
-
90
- <!-- Row 2: database-reviewer -->
91
- <text x="30" y="235" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#64748b" font-weight="600">#2</text>
92
- <text x="55" y="235" font-family="'Courier New', Courier, monospace" font-size="11" fill="#3b82f6" font-weight="600">database-reviewer</text>
93
- <rect x="200" y="225" width="280" height="14" rx="7" fill="#f1f5f9"/>
94
- <rect x="200" y="225" width="140" height="14" rx="7" fill="#3b82f6" fill-opacity="0.7"/>
95
- <text x="495" y="237" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#0f172a" text-anchor="end" font-weight="600">1.0K</text>
96
-
97
- <!-- Row 3: security-reviewer -->
98
- <text x="30" y="260" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#64748b" font-weight="600">#3</text>
99
- <text x="55" y="260" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ef4444" font-weight="600">security-reviewer</text>
100
- <rect x="200" y="250" width="280" height="14" rx="7" fill="#f1f5f9"/>
101
- <rect x="200" y="250" width="67" height="14" rx="7" fill="#ef4444" fill-opacity="0.7"/>
102
- <text x="495" y="262" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#0f172a" text-anchor="end" font-weight="600">478</text>
103
-
104
- <!-- Row 4: debug-detective -->
105
- <text x="30" y="285" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#64748b" font-weight="600">#4</text>
106
- <text x="55" y="285" font-family="'Courier New', Courier, monospace" font-size="11" fill="#f97316" font-weight="600">debug-detective</text>
107
- <rect x="200" y="275" width="280" height="14" rx="7" fill="#f1f5f9"/>
108
- <rect x="200" y="275" width="35" height="14" rx="7" fill="#f97316" fill-opacity="0.7"/>
109
- <text x="495" y="287" font-family="system-ui, -apple-system, sans-serif" font-size="11" fill="#0f172a" text-anchor="end" font-weight="600">250</text>
110
-
111
- <text x="30" y="334" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#94a3b8">Showing top 4 of 53 agents</text>
112
-
113
- <!-- Right panel: Platforms -->
114
- <rect x="566" y="156" width="320" height="190" rx="8" fill="#ffffff" filter="url(#cardShadow)" stroke="#e2e8f0" stroke-width="1"/>
115
- <text x="582" y="180" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="700" fill="#0f172a">Platforms</text>
116
- <line x1="582" y1="188" x2="874" y2="188" stroke="#f1f5f9" stroke-width="1"/>
117
-
118
- <!-- Platform cards 2x2 grid -->
119
- <!-- Claude Code -->
120
- <rect x="582" y="198" width="140" height="58" rx="6" fill="#fff7ed" stroke="#f97316" stroke-opacity="0.4" stroke-width="1.5"/>
121
- <text x="592" y="218" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="700" fill="#ea580c">Claude Code</text>
122
- <text x="592" y="237" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="800" fill="#f97316">33</text>
123
- <text x="712" y="237" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#9a3412" text-anchor="end">sessions</text>
124
-
125
- <!-- Codex CLI -->
126
- <rect x="732" y="198" width="130" height="58" rx="6" fill="#f0fdf4" stroke="#22c55e" stroke-opacity="0.4" stroke-width="1.5"/>
127
- <text x="742" y="218" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="700" fill="#16a34a">Codex CLI</text>
128
- <text x="742" y="237" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="800" fill="#22c55e">3</text>
129
- <text x="852" y="237" font-family="system-ui, -apple-system, sans-serif" font-size="10" fill="#166534" text-anchor="end">sessions</text>
130
-
131
- <!-- Windsurf -->
132
- <rect x="582" y="266" width="140" height="58" rx="6" fill="#ecfeff" stroke="#06b6d4" stroke-opacity="0.4" stroke-width="1.5"/>
133
- <text x="592" y="286" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="700" fill="#0891b2">Windsurf ☁</text>
134
- <text x="592" y="307" font-family="system-ui, -apple-system, sans-serif" font-size="18" font-weight="800" fill="#06b6d4">Active</text>
135
-
136
- <!-- Antigravity -->
137
- <rect x="732" y="266" width="130" height="58" rx="6" fill="#faf5ff" stroke="#a78bfa" stroke-opacity="0.4" stroke-width="1.5"/>
138
- <text x="742" y="286" font-family="system-ui, -apple-system, sans-serif" font-size="11" font-weight="700" fill="#7c3aed">Antigravity</text>
139
- <text x="742" y="305" font-family="system-ui, -apple-system, sans-serif" font-size="18" font-weight="800" fill="#a78bfa">5 proj</text>
140
- </svg>
@@ -1,56 +0,0 @@
1
- <svg width="900" height="280" viewBox="0 0 900 280" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif">
2
- <defs>
3
- <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
4
- <stop offset="0%" stop-color="#f0fdf4"/>
5
- <stop offset="100%" stop-color="#ede9fe"/>
6
- </linearGradient>
7
- <linearGradient id="bar" x1="0" y1="0" x2="1" y2="0">
8
- <stop offset="0%" stop-color="#7c3aed"/>
9
- <stop offset="100%" stop-color="#059669"/>
10
- </linearGradient>
11
- <linearGradient id="barBg" x1="0" y1="0" x2="1" y2="0">
12
- <stop offset="0%" stop-color="#dc2626" stop-opacity=".15"/>
13
- <stop offset="100%" stop-color="#dc2626" stop-opacity=".08"/>
14
- </linearGradient>
15
- <filter id="shadow">
16
- <feDropShadow dx="0" dy="4" stdDeviation="12" flood-opacity=".12"/>
17
- </filter>
18
- </defs>
19
-
20
- <!-- Background -->
21
- <rect width="900" height="280" rx="16" fill="url(#bg)"/>
22
- <rect width="900" height="280" rx="16" fill="none" stroke="#e2e8f0" stroke-width="1"/>
23
-
24
- <!-- Header -->
25
- <text x="44" y="52" font-size="13" font-weight="600" fill="#7c3aed" letter-spacing=".5">KODELYTH ECC — COST SAVINGS</text>
26
- <text x="44" y="80" font-size="28" font-weight="800" fill="#0f172a" letter-spacing="-1">Prompt caching cuts your AI bill by ~40%</text>
27
-
28
- <!-- Three metric cards -->
29
- <!-- Without ECC -->
30
- <rect x="44" y="108" width="240" height="100" rx="12" fill="#fff" filter="url(#shadow)"/>
31
- <rect x="44" y="108" width="240" height="4" rx="2" fill="#dc2626"/>
32
- <text x="64" y="138" font-size="11" font-weight="600" fill="#94a3b8" letter-spacing=".6">WITHOUT ECC (est.)</text>
33
- <text x="64" y="172" font-size="32" font-weight="800" fill="#dc2626" letter-spacing="-1.5">$143.26</text>
34
- <text x="64" y="193" font-size="12" fill="#94a3b8">full prompt cost every session</text>
35
-
36
- <!-- With ECC -->
37
- <rect x="306" y="108" width="240" height="100" rx="12" fill="#fff" filter="url(#shadow)"/>
38
- <rect x="306" y="108" width="240" height="4" rx="2" fill="#059669"/>
39
- <text x="326" y="138" font-size="11" font-weight="600" fill="#94a3b8" letter-spacing=".6">WITH ECC (actual)</text>
40
- <text x="326" y="172" font-size="32" font-weight="800" fill="#059669" letter-spacing="-1.5">$83.24</text>
41
- <text x="326" y="193" font-size="12" fill="#94a3b8">cache read tokens billed at 10%</text>
42
-
43
- <!-- Saved -->
44
- <rect x="568" y="108" width="288" height="100" rx="12" fill="#fff" filter="url(#shadow)"/>
45
- <rect x="568" y="108" width="288" height="4" rx="2" fill="#7c3aed"/>
46
- <text x="588" y="138" font-size="11" font-weight="600" fill="#94a3b8" letter-spacing=".6">YOU SAVED</text>
47
- <text x="588" y="172" font-size="32" font-weight="800" fill="#7c3aed" letter-spacing="-1.5">$60.02</text>
48
- <text x="588" y="193" font-size="12" fill="#94a3b8">41.9% reduction · 68% cache hit rate</text>
49
-
50
- <!-- Progress bar -->
51
- <rect x="44" y="228" width="812" height="18" rx="9" fill="url(#barBg)"/>
52
- <rect x="44" y="228" width="340" height="18" rx="9" fill="url(#bar)"/>
53
- <text x="44" y="261" font-size="11" fill="#dc2626">$83.24 paid</text>
54
- <text x="440" y="261" font-size="11" fill="#7c3aed" font-weight="700" text-anchor="middle">68% cache hit rate</text>
55
- <text x="856" y="261" font-size="11" fill="#059669" text-anchor="end">$60.02 saved</text>
56
- </svg>