kodelyth-ecc 1.2.1 → 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/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/package.json +1 -1
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/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",
|