kodelyth-ecc 1.2.0 → 1.2.2
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/README.md +27 -13
- package/VERSION +1 -1
- package/dashboard/lib/aggregator.js +1 -0
- package/dashboard/lib/readers/windsurf-reader.js +125 -105
- package/dashboard/public/index.html +86 -12
- package/install.ps1 +58 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,30 +19,42 @@ Works with **Claude Code**, **Google Antigravity**, **Cursor**, **Codex CLI**, a
|
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
22
|
-
### Option 1 — npx (Node.js
|
|
22
|
+
### Option 1 — npx from npm (Node.js 18+, any platform)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
The simplest install. Node.js is the only requirement.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx
|
|
27
|
+
npx kodelyth-ecc
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Install for a specific platform:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npx
|
|
34
|
-
npx
|
|
35
|
-
npx
|
|
36
|
-
npx
|
|
37
|
-
npx
|
|
38
|
-
npx
|
|
33
|
+
npx kodelyth-ecc --target windsurf-project # Windsurf (project)
|
|
34
|
+
npx kodelyth-ecc --target windsurf-home # Windsurf (global)
|
|
35
|
+
npx kodelyth-ecc --target antigravity # Google Antigravity
|
|
36
|
+
npx kodelyth-ecc --target cursor-project # Cursor IDE
|
|
37
|
+
npx kodelyth-ecc --target codex-home # Codex CLI
|
|
38
|
+
npx kodelyth-ecc --target opencode # OpenCode
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
> Node.js not installed? Download it from [nodejs.org](https://nodejs.org) — LTS version recommended.
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
-
### Option 2 —
|
|
45
|
+
### Option 2 — npx from GitHub (always latest commit)
|
|
46
|
+
|
|
47
|
+
Use this if you want the very latest unreleased version directly from the main branch.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx github:sifxprime/kodelyth-ecc
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Same `--target` flags apply.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### Option 3 — curl (macOS / Linux)
|
|
46
58
|
|
|
47
59
|
```bash
|
|
48
60
|
curl -fsSL https://raw.githubusercontent.com/sifxprime/kodelyth-ecc/main/install.sh | bash
|
|
@@ -56,7 +68,7 @@ curl -fsSL https://raw.githubusercontent.com/sifxprime/kodelyth-ecc/main/install
|
|
|
56
68
|
|
|
57
69
|
---
|
|
58
70
|
|
|
59
|
-
### Option
|
|
71
|
+
### Option 4 — clone and run (all platforms)
|
|
60
72
|
|
|
61
73
|
```bash
|
|
62
74
|
git clone https://github.com/sifxprime/kodelyth-ecc.git
|
|
@@ -67,8 +79,10 @@ cd kodelyth-ecc
|
|
|
67
79
|
./install.sh --target windsurf-project # Windsurf
|
|
68
80
|
./install.sh --target antigravity # Google Antigravity
|
|
69
81
|
|
|
70
|
-
# Windows
|
|
82
|
+
# Windows (PowerShell)
|
|
71
83
|
.\install.ps1
|
|
84
|
+
.\install.ps1 -Target windsurf-project
|
|
85
|
+
.\install.ps1 -Target codex-home
|
|
72
86
|
```
|
|
73
87
|
|
|
74
88
|
---
|
|
@@ -138,7 +152,7 @@ PORT=3456 # Dashboard port (default: 3456)
|
|
|
138
152
|
| Skills | **185** | Domain knowledge — patterns, testing, security, DevOps |
|
|
139
153
|
| Commands | **79** | Slash command workflows (`/tdd`, `/plan`, `/code-review`, etc.) |
|
|
140
154
|
| Hooks | **15+** | Automated lifecycle triggers — quality gates, session memory, cost tracking |
|
|
141
|
-
| Rules | **
|
|
155
|
+
| Rules | **15** | Language-specific coding standards |
|
|
142
156
|
|
|
143
157
|
---
|
|
144
158
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.2
|
|
@@ -1,126 +1,146 @@
|
|
|
1
1
|
// Kodelyth Lens — Windsurf Reader
|
|
2
|
-
// Reads Windsurf (Codeium)
|
|
2
|
+
// Reads Windsurf (Codeium) real quota data from state.vscdb SQLite databases
|
|
3
3
|
// Part of Kodelyth ECC — github.com/sifxprime/kodelyth-ecc
|
|
4
4
|
|
|
5
|
-
import fs
|
|
6
|
-
import path
|
|
7
|
-
import os
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
8
9
|
import { detectAgentsInMessages } from '../agent-tracker.js';
|
|
9
10
|
|
|
10
11
|
const HOME = os.homedir();
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// ── SQLite helpers (same pattern as codex-reader) ──────────────────────────────
|
|
14
|
+
|
|
15
|
+
function sqlite3Available() {
|
|
16
|
+
try { execSync('sqlite3 --version', { stdio: 'pipe', timeout: 3000 }); return true; }
|
|
17
|
+
catch { return false; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function querySqliteJson(dbPath, sql) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = execSync(
|
|
23
|
+
`sqlite3 -json "${dbPath}" "${sql.replace(/"/g, '\\"')}"`,
|
|
24
|
+
{ stdio: 'pipe', timeout: 5000, encoding: 'utf-8' }
|
|
25
|
+
).trim();
|
|
26
|
+
return raw ? JSON.parse(raw) : [];
|
|
27
|
+
} catch { return []; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readDbKey(dbPath, key) {
|
|
31
|
+
const rows = querySqliteJson(dbPath, `SELECT value FROM ItemTable WHERE key='${key}'`);
|
|
32
|
+
if (!rows.length || !rows[0].value) return null;
|
|
33
|
+
try { return JSON.parse(rows[0].value); } catch { return rows[0].value; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Find Windsurf data directory ───────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function findWindsurfDir(override) {
|
|
39
|
+
return [
|
|
40
|
+
override,
|
|
15
41
|
process.env.WINDSURF_DIR,
|
|
16
42
|
path.join(HOME, 'Library', 'Application Support', 'Windsurf'),
|
|
17
43
|
path.join(HOME, 'AppData', 'Roaming', 'Windsurf'),
|
|
18
44
|
path.join(HOME, '.codeium', 'windsurf'),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const windsurfDir = candidates.find(d => d && fs.existsSync(d));
|
|
23
|
-
if (!windsurfDir) return { sessions: [], available: false };
|
|
24
|
-
|
|
25
|
-
const sessions = [];
|
|
26
|
-
|
|
27
|
-
// Windsurf stores session data similar to VS Code extension
|
|
28
|
-
const storageDir = path.join(windsurfDir, 'User', 'workspaceStorage');
|
|
29
|
-
if (fs.existsSync(storageDir)) {
|
|
30
|
-
const wsDirs = fs.readdirSync(storageDir, { withFileTypes: true })
|
|
31
|
-
.filter(d => d.isDirectory())
|
|
32
|
-
.map(d => path.join(storageDir, d.name));
|
|
45
|
+
].filter(Boolean).find(d => fs.existsSync(d)) || null;
|
|
46
|
+
}
|
|
33
47
|
|
|
34
|
-
|
|
35
|
-
const session = parseWindsurfWorkspace(ws);
|
|
36
|
-
if (session) sessions.push(session);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
48
|
+
// ── Main reader ────────────────────────────────────────────────────────────────
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
export async function readWindsurfData(dataDir) {
|
|
51
|
+
const windsurfDir = findWindsurfDir(dataDir);
|
|
52
|
+
if (!windsurfDir) return { sessions: [], available: false, quotaData: null };
|
|
53
|
+
|
|
54
|
+
const hasSqlite = sqlite3Available();
|
|
55
|
+
let quotaData = null;
|
|
56
|
+
|
|
57
|
+
// ── 1. Read real quota / plan info from global state.vscdb ─────────────────
|
|
58
|
+
const globalDb = path.join(windsurfDir, 'User', 'globalStorage', 'state.vscdb');
|
|
59
|
+
if (hasSqlite && fs.existsSync(globalDb)) {
|
|
60
|
+
const plan = readDbKey(globalDb, 'windsurf.settings.cachedPlanInfo');
|
|
61
|
+
if (plan?.usage) {
|
|
62
|
+
const daily = plan.quotaUsage?.dailyRemainingPercent ?? 100;
|
|
63
|
+
const weekly = plan.quotaUsage?.weeklyRemainingPercent ?? 100;
|
|
64
|
+
quotaData = {
|
|
65
|
+
planName: plan.planName || 'Unknown',
|
|
66
|
+
usedMessages: plan.usage.usedMessages || 0,
|
|
67
|
+
totalMessages: plan.usage.messages || 0,
|
|
68
|
+
usedFlowActions: plan.usage.usedFlowActions || 0,
|
|
69
|
+
totalFlowActions: plan.usage.flowActions || 0,
|
|
70
|
+
dailyUsedPercent: Math.round(100 - daily),
|
|
71
|
+
weeklyUsedPercent: Math.round(100 - weekly),
|
|
72
|
+
dailyRemainingPercent: Math.round(daily),
|
|
73
|
+
weeklyRemainingPercent:Math.round(weekly),
|
|
74
|
+
startDate: plan.startTimestamp ? new Date(plan.startTimestamp).toISOString().slice(0, 10) : null,
|
|
75
|
+
endDate: plan.endTimestamp ? new Date(plan.endTimestamp).toISOString().slice(0, 10) : null,
|
|
76
|
+
};
|
|
49
77
|
}
|
|
50
78
|
}
|
|
51
79
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
80
|
+
// ── 2. Read workspace sessions from workspaceStorage SQLite DBs ─────────────
|
|
81
|
+
const sessions = [];
|
|
82
|
+
const wsStorageDir = path.join(windsurfDir, 'User', 'workspaceStorage');
|
|
55
83
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
if (hasSqlite && fs.existsSync(wsStorageDir)) {
|
|
85
|
+
const wsDirs = fs.readdirSync(wsStorageDir, { withFileTypes: true })
|
|
86
|
+
.filter(d => d.isDirectory())
|
|
87
|
+
.map(d => path.join(wsStorageDir, d.name));
|
|
88
|
+
|
|
89
|
+
for (const wsDir of wsDirs) {
|
|
90
|
+
const db = path.join(wsDir, 'state.vscdb');
|
|
91
|
+
if (!fs.existsSync(db)) continue;
|
|
92
|
+
|
|
93
|
+
// Determine timestamp: readDateBaseline2 → epoch dirname → skip
|
|
94
|
+
const baseline = readDbKey(db, 'agentSessions.readDateBaseline2');
|
|
95
|
+
const dirName = path.basename(wsDir);
|
|
96
|
+
let ts = null;
|
|
97
|
+
if (baseline && !isNaN(parseInt(baseline, 10))) {
|
|
98
|
+
ts = new Date(parseInt(baseline, 10)).toISOString();
|
|
99
|
+
} else if (/^\d{13}$/.test(dirName)) {
|
|
100
|
+
ts = new Date(parseInt(dirName, 10)).toISOString();
|
|
101
|
+
}
|
|
102
|
+
if (!ts) continue;
|
|
103
|
+
|
|
104
|
+
// Resolve project name from workspace.json
|
|
105
|
+
let projectName = dirName;
|
|
106
|
+
const wsJson = path.join(wsDir, 'workspace.json');
|
|
107
|
+
if (fs.existsSync(wsJson)) {
|
|
108
|
+
try {
|
|
109
|
+
const wd = JSON.parse(fs.readFileSync(wsJson, 'utf-8'));
|
|
110
|
+
const uri = wd.folder || wd.workspace?.folders?.[0]?.uri || '';
|
|
111
|
+
if (uri) projectName = path.basename(decodeURIComponent(uri).replace(/\/$/, ''));
|
|
112
|
+
} catch { /* skip */ }
|
|
74
113
|
}
|
|
75
|
-
} catch { /* skip */ }
|
|
76
|
-
}
|
|
77
114
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
// Check for any agent usage in this workspace
|
|
116
|
+
const agentCache = readDbKey(db, 'agentSessions.state.cache') || [];
|
|
117
|
+
const messages = Array.isArray(agentCache)
|
|
118
|
+
? agentCache.flatMap(s => (s.messages || []).map(m => ({
|
|
119
|
+
role: m.role || 'user',
|
|
120
|
+
content: m.content || m.text || '',
|
|
121
|
+
})))
|
|
122
|
+
: [];
|
|
123
|
+
const agents = detectAgentsInMessages(messages);
|
|
124
|
+
|
|
125
|
+
sessions.push({
|
|
126
|
+
id: dirName,
|
|
127
|
+
platform: 'windsurf',
|
|
128
|
+
project: projectName,
|
|
129
|
+
date: ts.slice(0, 10),
|
|
130
|
+
lastDate: ts.slice(0, 10),
|
|
131
|
+
timestamp: ts,
|
|
132
|
+
lastActivity: ts,
|
|
133
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
134
|
+
cost: null, // Windsurf is quota-based, not per-token
|
|
135
|
+
cacheSaved: 0,
|
|
136
|
+
messageCount: messages.length,
|
|
137
|
+
agents,
|
|
138
|
+
agentCalls: agents.length,
|
|
139
|
+
cloudAI: true,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
98
143
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const messages = (data.messages || data.turns || []).map(m => ({
|
|
102
|
-
role: m.role || (m.type === 'human' ? 'user' : 'assistant'),
|
|
103
|
-
content: m.content || m.text || '',
|
|
104
|
-
timestamp: m.timestamp || null,
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
if (!messages.length) return null;
|
|
108
|
-
const ts = messages[0].timestamp || new Date().toISOString();
|
|
109
|
-
const agents = detectAgentsInMessages(messages);
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
id: path.basename(filePath, '.json'),
|
|
113
|
-
platform: 'windsurf',
|
|
114
|
-
project: path.basename(filePath, '.json'),
|
|
115
|
-
date: ts.slice(0, 10),
|
|
116
|
-
lastDate: ts.slice(0, 10),
|
|
117
|
-
timestamp: ts,
|
|
118
|
-
lastActivity: ts,
|
|
119
|
-
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
120
|
-
cost: 0,
|
|
121
|
-
cacheSaved: 0,
|
|
122
|
-
messageCount: messages.length,
|
|
123
|
-
agents,
|
|
124
|
-
agentCalls: agents.length,
|
|
125
|
-
};
|
|
144
|
+
sessions.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
|
|
145
|
+
return { sessions, available: true, quotaData };
|
|
126
146
|
}
|
|
@@ -727,6 +727,17 @@
|
|
|
727
727
|
</div>
|
|
728
728
|
</div>
|
|
729
729
|
|
|
730
|
+
<!-- Windsurf Quota Panel (hidden until data loads) -->
|
|
731
|
+
<div class="card" id="windsurfQuota" style="display:none;margin-bottom:0">
|
|
732
|
+
<div class="card-header" style="padding-bottom:8px">
|
|
733
|
+
<div class="card-title">
|
|
734
|
+
<div class="card-title-icon" style="background:#cffafe">🏄</div>
|
|
735
|
+
Windsurf Quota Usage
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="card-body"></div>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
730
741
|
<!-- Cost chart + Recent sessions -->
|
|
731
742
|
<div class="two-col">
|
|
732
743
|
<div class="card">
|
|
@@ -824,11 +835,12 @@ async function refreshData(force = false) {
|
|
|
824
835
|
function renderAll(d) {
|
|
825
836
|
renderStats(d.stats);
|
|
826
837
|
renderAgents(d.agents);
|
|
827
|
-
renderPlatforms(d.platforms);
|
|
838
|
+
renderPlatforms(d.platforms, d.windsurf);
|
|
828
839
|
renderCostChart(d.costs);
|
|
829
840
|
renderToday(d.stats);
|
|
830
841
|
renderSessions(d.sessions);
|
|
831
842
|
renderSavings(d.stats);
|
|
843
|
+
renderWindsurfQuota(d.windsurf?.quotaData);
|
|
832
844
|
|
|
833
845
|
// Dynamic subtitle — list installed platform names
|
|
834
846
|
if (d.platforms) {
|
|
@@ -915,12 +927,11 @@ function renderAgents(agents) {
|
|
|
915
927
|
}).join('');
|
|
916
928
|
}
|
|
917
929
|
|
|
918
|
-
// Cloud AI platforms — store conversations server-side
|
|
919
|
-
|
|
920
|
-
const CLOUD_AI_PLATFORMS = new Set(['antigravity']);
|
|
930
|
+
// Cloud AI platforms — store conversations server-side (no local token data)
|
|
931
|
+
const CLOUD_AI_PLATFORMS = new Set(['antigravity', 'windsurf']);
|
|
921
932
|
|
|
922
933
|
// ── Platforms ─────────────────────────────────────────────────────────────────
|
|
923
|
-
function renderPlatforms(platforms) {
|
|
934
|
+
function renderPlatforms(platforms, windsurfData) {
|
|
924
935
|
const grid = document.getElementById('platformGrid');
|
|
925
936
|
if (!platforms) { grid.innerHTML = '<div class="empty"><div class="empty-icon">❌</div></div>'; return; }
|
|
926
937
|
|
|
@@ -946,10 +957,39 @@ function renderPlatforms(platforms) {
|
|
|
946
957
|
statusClass = 'status-inactive'; statusText = '○ Installed';
|
|
947
958
|
}
|
|
948
959
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
960
|
+
let cloudBadge = '';
|
|
961
|
+
let sessDisplay, sessLabel;
|
|
962
|
+
if (key === 'windsurf' && present) {
|
|
963
|
+
const q = windsurfData?.quotaData;
|
|
964
|
+
if (q) {
|
|
965
|
+
const msgPct = q.totalMessages > 0 ? Math.round((q.usedMessages / q.totalMessages) * 100) : q.dailyUsedPercent;
|
|
966
|
+
sessDisplay = `${q.usedMessages.toLocaleString()} / ${q.totalMessages.toLocaleString()}`;
|
|
967
|
+
sessLabel = 'messages used';
|
|
968
|
+
cloudBadge = `
|
|
969
|
+
<div style="margin-top:8px;font-size:11px;color:var(--muted)">
|
|
970
|
+
<div style="display:flex;justify-content:space-between;margin-bottom:3px">
|
|
971
|
+
<span>${q.planName} plan</span><span>${q.dailyUsedPercent}% daily used</span>
|
|
972
|
+
</div>
|
|
973
|
+
<div style="background:#e2e8f0;border-radius:4px;height:4px;overflow:hidden">
|
|
974
|
+
<div style="background:${meta.color};height:4px;width:${Math.min(q.dailyUsedPercent,100)}%;transition:width .3s"></div>
|
|
975
|
+
</div>
|
|
976
|
+
<div style="display:flex;justify-content:space-between;margin-top:4px">
|
|
977
|
+
<span>Flow: ${q.usedFlowActions} / ${q.totalFlowActions}</span><span>${q.weeklyUsedPercent}% weekly</span>
|
|
978
|
+
</div>
|
|
979
|
+
</div>`;
|
|
980
|
+
} else {
|
|
981
|
+
sessDisplay = p.sessions > 0 ? fmtNum(p.sessions) + ' proj' : '—';
|
|
982
|
+
sessLabel = 'ECC projects';
|
|
983
|
+
cloudBadge = `<div class="cloud-badge">☁ Quota data loading…</div>`;
|
|
984
|
+
}
|
|
985
|
+
} else if (cloudAI && present) {
|
|
986
|
+
sessDisplay = p.sessions > 0 ? fmtNum(p.sessions) + ' proj' : '—';
|
|
987
|
+
sessLabel = 'ECC projects';
|
|
988
|
+
cloudBadge = `<div class="cloud-badge">☁ Cloud AI sessions</div>`;
|
|
989
|
+
} else {
|
|
990
|
+
sessDisplay = fmtNum(p.sessions);
|
|
991
|
+
sessLabel = 'sessions';
|
|
992
|
+
}
|
|
953
993
|
|
|
954
994
|
return `
|
|
955
995
|
<div class="platform-card ${active || (cloudAI && present) ? 'active' : ''}" style="color:${meta.color}">
|
|
@@ -957,7 +997,7 @@ function renderPlatforms(platforms) {
|
|
|
957
997
|
<span class="platform-dot-lg" style="background:${meta.color}"></span>
|
|
958
998
|
${meta.label}
|
|
959
999
|
</div>
|
|
960
|
-
<div class="platform-sessions" style="color:${(active || (cloudAI && present)) ? meta.color : 'var(--muted)'}">${sessDisplay}</div>
|
|
1000
|
+
<div class="platform-sessions" style="color:${(active || (cloudAI && present)) ? meta.color : 'var(--muted)'};font-size:${key==='windsurf'&&windsurfData?.quotaData?'14px':'22px'}">${sessDisplay}</div>
|
|
961
1001
|
<div class="platform-label">${sessLabel}</div>
|
|
962
1002
|
<div class="platform-status ${statusClass}">${statusText}</div>
|
|
963
1003
|
${cloudBadge}
|
|
@@ -965,6 +1005,40 @@ function renderPlatforms(platforms) {
|
|
|
965
1005
|
}).join('');
|
|
966
1006
|
}
|
|
967
1007
|
|
|
1008
|
+
// ── Windsurf Quota Panel ─────────────────────────────────────────────────────
|
|
1009
|
+
function renderWindsurfQuota(q) {
|
|
1010
|
+
const el = document.getElementById('windsurfQuota');
|
|
1011
|
+
if (!el) return;
|
|
1012
|
+
if (!q) { el.style.display = 'none'; return; }
|
|
1013
|
+
el.style.display = 'block';
|
|
1014
|
+
el.querySelector('.card-body').innerHTML = `
|
|
1015
|
+
<div style="display:flex;gap:24px;flex-wrap:wrap;align-items:center">
|
|
1016
|
+
<div>
|
|
1017
|
+
<div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px">Windsurf ${q.planName}</div>
|
|
1018
|
+
<div style="font-size:22px;font-weight:800;color:#06b6d4;font-variant-numeric:tabular-nums">${q.usedMessages.toLocaleString()} <span style="font-size:14px;font-weight:400;color:var(--muted)">/ ${q.totalMessages.toLocaleString()} msgs</span></div>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div style="flex:1;min-width:160px">
|
|
1021
|
+
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-bottom:4px">
|
|
1022
|
+
<span>Daily quota</span><span>${q.dailyUsedPercent}% used</span>
|
|
1023
|
+
</div>
|
|
1024
|
+
<div style="background:#e2e8f0;border-radius:6px;height:8px;overflow:hidden">
|
|
1025
|
+
<div style="background:#06b6d4;height:8px;width:${Math.min(q.dailyUsedPercent,100)}%;border-radius:6px;transition:width .4s"></div>
|
|
1026
|
+
</div>
|
|
1027
|
+
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin-top:6px">
|
|
1028
|
+
<span>Weekly quota</span><span>${q.weeklyUsedPercent}% used</span>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div style="background:#e2e8f0;border-radius:6px;height:8px;overflow:hidden;margin-top:4px">
|
|
1031
|
+
<div style="background:#0e7490;height:8px;width:${Math.min(q.weeklyUsedPercent,100)}%;border-radius:6px;transition:width .4s"></div>
|
|
1032
|
+
</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
<div style="text-align:right">
|
|
1035
|
+
<div style="font-size:11px;color:var(--muted)">Flow Actions</div>
|
|
1036
|
+
<div style="font-size:16px;font-weight:700;color:#06b6d4">${q.usedFlowActions.toLocaleString()} <span style="font-size:12px;color:var(--muted)">/ ${q.totalFlowActions.toLocaleString()}</span></div>
|
|
1037
|
+
${q.endDate ? `<div style="font-size:10px;color:var(--muted);margin-top:2px">Resets ${q.endDate}</div>` : ''}
|
|
1038
|
+
</div>
|
|
1039
|
+
</div>`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
968
1042
|
// ── Cost chart ────────────────────────────────────────────────────────────────
|
|
969
1043
|
function renderCostChart(dailyCosts) {
|
|
970
1044
|
const svg = document.getElementById('costChart');
|
|
@@ -1060,8 +1134,8 @@ function renderSessions(sessions) {
|
|
|
1060
1134
|
<td style="max-width:220px">
|
|
1061
1135
|
<div style="display:flex;flex-wrap:wrap;gap:4px">${agents}${moreAgents}</div>
|
|
1062
1136
|
</td>
|
|
1063
|
-
<td class="td-mono">${fmtTokens(s.tokens?.total || 0)}</td>
|
|
1064
|
-
<td class="td-mono">${s.cost > 0 ? '$' + s.cost.toFixed(4) : '—'}</td>
|
|
1137
|
+
<td class="td-mono">${s.cost === null ? '<span style="color:#94a3b8">quota</span>' : fmtTokens(s.tokens?.total || 0)}</td>
|
|
1138
|
+
<td class="td-mono">${s.cost === null ? '<span style="color:#94a3b8">—</span>' : s.cost > 0 ? '$' + s.cost.toFixed(4) : '—'}</td>
|
|
1065
1139
|
<td style="white-space:nowrap;color:var(--muted)">${fmtDate(s.date)}</td>
|
|
1066
1140
|
</tr>`;
|
|
1067
1141
|
}).join('');
|
package/install.ps1
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# =============================================================================
|
|
2
2
|
# Kodelyth ECC — Windows PowerShell Installer
|
|
3
|
-
# Supports: Claude Code, Antigravity, Cursor, Codex, OpenCode
|
|
3
|
+
# Supports: Claude Code, Antigravity, Cursor, Codex, Windsurf, OpenCode
|
|
4
4
|
# Usage:
|
|
5
|
-
# .\install.ps1
|
|
6
|
-
# .\install.ps1 -Target
|
|
7
|
-
# .\install.ps1 -Target
|
|
8
|
-
# .\install.ps1 -Target
|
|
5
|
+
# .\install.ps1 # Claude Code (default)
|
|
6
|
+
# .\install.ps1 -Target windsurf-project # Windsurf (project)
|
|
7
|
+
# .\install.ps1 -Target windsurf-home # Windsurf (global)
|
|
8
|
+
# .\install.ps1 -Target antigravity # Google Antigravity
|
|
9
|
+
# .\install.ps1 -Target cursor-project # Cursor IDE
|
|
10
|
+
# .\install.ps1 -Target codex-home # OpenAI Codex CLI
|
|
11
|
+
# .\install.ps1 -Target opencode # OpenCode
|
|
9
12
|
# =============================================================================
|
|
10
13
|
|
|
11
14
|
param(
|
|
@@ -18,7 +21,7 @@ $ErrorActionPreference = "Stop"
|
|
|
18
21
|
# ── Banner ────────────────────────────────────────────────────────────────────
|
|
19
22
|
Write-Host ""
|
|
20
23
|
Write-Host " Kodelyth ECC — Production-grade AI coding agent toolkit" -ForegroundColor Cyan
|
|
21
|
-
Write-Host "
|
|
24
|
+
Write-Host " 53 agents · 185 skills · 79 commands · 18+ hooks" -ForegroundColor Gray
|
|
22
25
|
Write-Host ""
|
|
23
26
|
|
|
24
27
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
@@ -35,6 +38,22 @@ switch ($Target) {
|
|
|
35
38
|
$HooksDest = "$Dest\hooks"
|
|
36
39
|
$RulesDest = "$Dest\rules"
|
|
37
40
|
}
|
|
41
|
+
"windsurf-project" {
|
|
42
|
+
$Dest = "$(Get-Location)\.windsurf"
|
|
43
|
+
$AgentsDest = "$Dest\agents"
|
|
44
|
+
$SkillsDest = "$Dest\skills"
|
|
45
|
+
$CommandsDest = ""
|
|
46
|
+
$HooksDest = ""
|
|
47
|
+
$RulesDest = "$Dest\rules"
|
|
48
|
+
}
|
|
49
|
+
"windsurf-home" {
|
|
50
|
+
$Dest = "$HomeDir\.codeium\windsurf"
|
|
51
|
+
$AgentsDest = "$Dest\agents"
|
|
52
|
+
$SkillsDest = "$Dest\skills"
|
|
53
|
+
$CommandsDest = ""
|
|
54
|
+
$HooksDest = ""
|
|
55
|
+
$RulesDest = "$Dest\rules"
|
|
56
|
+
}
|
|
38
57
|
"antigravity" {
|
|
39
58
|
$Dest = "$(Get-Location)\.agent"
|
|
40
59
|
$AgentsDest = "$Dest\skills"
|
|
@@ -59,9 +78,17 @@ switch ($Target) {
|
|
|
59
78
|
$HooksDest = ""
|
|
60
79
|
$RulesDest = "$Dest\rules"
|
|
61
80
|
}
|
|
81
|
+
"opencode" {
|
|
82
|
+
$Dest = "$(Get-Location)\.opencode"
|
|
83
|
+
$AgentsDest = ""
|
|
84
|
+
$SkillsDest = ""
|
|
85
|
+
$CommandsDest = ""
|
|
86
|
+
$HooksDest = ""
|
|
87
|
+
$RulesDest = "$Dest\rules"
|
|
88
|
+
}
|
|
62
89
|
default {
|
|
63
90
|
Write-Host "Unknown target: $Target" -ForegroundColor Red
|
|
64
|
-
Write-Host "Valid targets: claude-home, antigravity, cursor-project, codex-home"
|
|
91
|
+
Write-Host "Valid targets: claude-home, windsurf-project, windsurf-home, antigravity, cursor-project, codex-home, opencode"
|
|
65
92
|
exit 1
|
|
66
93
|
}
|
|
67
94
|
}
|
|
@@ -104,8 +131,8 @@ Write-Host "Installing components..." -ForegroundColor Bold
|
|
|
104
131
|
|
|
105
132
|
switch ($Target) {
|
|
106
133
|
"claude-home" {
|
|
107
|
-
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents (
|
|
108
|
-
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (
|
|
134
|
+
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents (53)"
|
|
135
|
+
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (185)"
|
|
109
136
|
Install-Dir "$ScriptDir\commands" $CommandsDest "Commands (79)"
|
|
110
137
|
|
|
111
138
|
# Hooks
|
|
@@ -134,21 +161,38 @@ switch ($Target) {
|
|
|
134
161
|
Copy-Item "$ScriptDir\SOUL.md" "$Dest\SOUL.md" -Force -ErrorAction SilentlyContinue
|
|
135
162
|
Write-Host " [OK] CLAUDE.md + SOUL.md" -ForegroundColor Green
|
|
136
163
|
}
|
|
164
|
+
{ $_ -in "windsurf-project","windsurf-home" } {
|
|
165
|
+
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents (53)"
|
|
166
|
+
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (185)"
|
|
167
|
+
Install-Flat "$ScriptDir\rules" $RulesDest "Rules"
|
|
168
|
+
|
|
169
|
+
# Generate .windsurfrules from all common rules
|
|
170
|
+
$WindsurfRulesFile = if ($Target -eq "windsurf-project") { "$(Get-Location)\.windsurfrules" } else { "$HomeDir\.windsurfrules" }
|
|
171
|
+
$CommonRulesDir = "$ScriptDir\rules\common"
|
|
172
|
+
if (Test-Path $CommonRulesDir) {
|
|
173
|
+
$Combined = Get-ChildItem -Path $CommonRulesDir -Filter "*.md" | Sort-Object Name | ForEach-Object { Get-Content $_.FullName }
|
|
174
|
+
$Combined | Out-File -FilePath $WindsurfRulesFile -Encoding UTF8
|
|
175
|
+
Write-Host " [OK] .windsurfrules generated -> $WindsurfRulesFile" -ForegroundColor Green
|
|
176
|
+
}
|
|
177
|
+
}
|
|
137
178
|
"antigravity" {
|
|
138
|
-
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents -> skills (
|
|
179
|
+
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents -> skills (53)"
|
|
139
180
|
Install-Dir "$ScriptDir\commands" $CommandsDest "Commands -> workflows (79)"
|
|
140
181
|
Install-Flat "$ScriptDir\rules" $RulesDest "Rules"
|
|
141
182
|
}
|
|
142
183
|
"cursor-project" {
|
|
143
|
-
Install-Flat "$ScriptDir\rules" $RulesDest
|
|
144
|
-
Install-Dir "$ScriptDir\skills" $SkillsDest
|
|
184
|
+
Install-Flat "$ScriptDir\rules" $RulesDest "Rules"
|
|
185
|
+
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (185)"
|
|
145
186
|
}
|
|
146
187
|
"codex-home" {
|
|
147
|
-
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents (
|
|
148
|
-
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (
|
|
188
|
+
Install-Dir "$ScriptDir\agents" $AgentsDest "Agents (53)"
|
|
189
|
+
Install-Dir "$ScriptDir\skills" $SkillsDest "Skills (185)"
|
|
149
190
|
Install-Dir "$ScriptDir\commands" $CommandsDest "Commands (79)"
|
|
150
191
|
Install-Flat "$ScriptDir\rules" $RulesDest "Rules"
|
|
151
192
|
}
|
|
193
|
+
"opencode" {
|
|
194
|
+
Install-Flat "$ScriptDir\rules" $RulesDest "Rules"
|
|
195
|
+
}
|
|
152
196
|
}
|
|
153
197
|
|
|
154
198
|
# ── Write install state ───────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 53 agents, 185 skills, 79 commands. Works with Claude Code, Windsurf, Cursor, Codex, Antigravity, and OpenCode.",
|
|
5
5
|
"author": "Kodelyth <github.com/sifxprime>",
|
|
6
6
|
"license": "MIT",
|