crg-dev-kit 2.0.0 → 2.0.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/lib/analytics.js +7 -2
- package/lib/roi.js +31 -10
- package/package.json +1 -1
- package/server.js +16 -8
package/lib/analytics.js
CHANGED
|
@@ -156,9 +156,14 @@ function getProjectStats(projectPath) {
|
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
function getAllStats() {
|
|
159
|
+
function getAllStats(scopeProject) {
|
|
160
160
|
const sessions = readJSON(SESSIONS_FILE, []);
|
|
161
|
-
|
|
161
|
+
let completedSessions = sessions.filter(s => s.status === 'completed');
|
|
162
|
+
|
|
163
|
+
if (scopeProject) {
|
|
164
|
+
const resolved = path.resolve(scopeProject);
|
|
165
|
+
completedSessions = completedSessions.filter(s => s.project === resolved);
|
|
166
|
+
}
|
|
162
167
|
|
|
163
168
|
if (completedSessions.length === 0) return null;
|
|
164
169
|
|
package/lib/roi.js
CHANGED
|
@@ -6,10 +6,20 @@ function getInstallDate(projectPath) {
|
|
|
6
6
|
const installFile = path.join(ANALYTICS_DIR, 'install.json');
|
|
7
7
|
try {
|
|
8
8
|
const data = JSON.parse(fs.readFileSync(installFile, 'utf8'));
|
|
9
|
+
// Support both old format (single object) and new format (keyed by path)
|
|
10
|
+
if (data.projectPath) {
|
|
11
|
+
// Old format: { projectPath, installedAt }
|
|
12
|
+
if (projectPath) return data.projectPath === projectPath ? data.installedAt : null;
|
|
13
|
+
return data.installedAt;
|
|
14
|
+
}
|
|
15
|
+
// New format: { "/path": { installedAt } }
|
|
9
16
|
if (projectPath) {
|
|
10
|
-
|
|
17
|
+
const resolved = path.resolve(projectPath);
|
|
18
|
+
return data[resolved] ? data[resolved].installedAt : null;
|
|
11
19
|
}
|
|
12
|
-
|
|
20
|
+
// Return first entry
|
|
21
|
+
const first = Object.values(data)[0];
|
|
22
|
+
return first ? first.installedAt : null;
|
|
13
23
|
} catch {
|
|
14
24
|
return null;
|
|
15
25
|
}
|
|
@@ -18,12 +28,16 @@ function getInstallDate(projectPath) {
|
|
|
18
28
|
function setInstallDate(projectPath) {
|
|
19
29
|
ensureDir();
|
|
20
30
|
const installFile = path.join(ANALYTICS_DIR, 'install.json');
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
const resolved = path.resolve(projectPath);
|
|
32
|
+
let data = {};
|
|
33
|
+
try { data = JSON.parse(fs.readFileSync(installFile, 'utf8')); } catch {}
|
|
34
|
+
// Migrate old format
|
|
35
|
+
if (data.projectPath) {
|
|
36
|
+
data = { [data.projectPath]: { installedAt: data.installedAt } };
|
|
37
|
+
}
|
|
38
|
+
data[resolved] = { installedAt: new Date().toISOString() };
|
|
39
|
+
fs.writeFileSync(installFile, JSON.stringify(data, null, 2));
|
|
40
|
+
return data[resolved];
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
function parseHistory(fromDate, toDate) {
|
|
@@ -161,11 +175,18 @@ function calculateROI(projectPath) {
|
|
|
161
175
|
};
|
|
162
176
|
}
|
|
163
177
|
|
|
164
|
-
function getAllProjectsROI() {
|
|
178
|
+
function getAllProjectsROI(scopeProject) {
|
|
179
|
+
if (scopeProject) {
|
|
180
|
+
return calculateROI(scopeProject);
|
|
181
|
+
}
|
|
165
182
|
const installFile = path.join(ANALYTICS_DIR, 'install.json');
|
|
166
183
|
try {
|
|
167
184
|
const installData = JSON.parse(fs.readFileSync(installFile, 'utf8'));
|
|
168
|
-
|
|
185
|
+
// Old format
|
|
186
|
+
if (installData.projectPath) return calculateROI(installData.projectPath);
|
|
187
|
+
// New format — return first
|
|
188
|
+
const first = Object.keys(installData)[0];
|
|
189
|
+
return first ? calculateROI(first) : { error: 'No installation found', installed: false };
|
|
169
190
|
} catch {
|
|
170
191
|
return { error: 'No installation found', installed: false };
|
|
171
192
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -359,10 +359,10 @@ function tabStatusFragment(statusData) {
|
|
|
359
359
|
</section>`;
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
function tabAnalytics() {
|
|
362
|
+
function tabAnalytics(cwd) {
|
|
363
363
|
let roiHtml = '';
|
|
364
364
|
try {
|
|
365
|
-
const roiData = roi.getAllProjectsROI();
|
|
365
|
+
const roiData = roi.getAllProjectsROI(cwd);
|
|
366
366
|
if (roiData.installed) {
|
|
367
367
|
const verdictColor = roiData.roi.verdict === 'positive' ? '#16a34a' : roiData.roi.verdict === 'neutral' ? '#ea580c' : '#dc2626';
|
|
368
368
|
const verdictIcon = roiData.roi.verdict === 'positive' ? '✓' : roiData.roi.verdict === 'neutral' ? '−' : '⚠';
|
|
@@ -428,7 +428,7 @@ function tabAnalytics() {
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
let tokenHtml = '';
|
|
431
|
-
const analyticsData = analytics.getAllStats();
|
|
431
|
+
const analyticsData = analytics.getAllStats(cwd);
|
|
432
432
|
if (analyticsData) {
|
|
433
433
|
const savingsColor = analyticsData.avgSavingsPercent >= 70 ? '#16a34a' : analyticsData.avgSavingsPercent >= 50 ? '#ea580c' : '#dc2626';
|
|
434
434
|
tokenHtml = `
|
|
@@ -563,7 +563,15 @@ function readBody(req, res, cb) {
|
|
|
563
563
|
req.on('end', () => {
|
|
564
564
|
if (res.writableEnded) return;
|
|
565
565
|
try {
|
|
566
|
-
|
|
566
|
+
const ct = (req.headers['content-type'] || '');
|
|
567
|
+
let data;
|
|
568
|
+
if (ct.includes('application/json')) {
|
|
569
|
+
data = JSON.parse(body);
|
|
570
|
+
} else {
|
|
571
|
+
// Parse URL-encoded form data (htmx default)
|
|
572
|
+
data = Object.fromEntries(new URLSearchParams(body));
|
|
573
|
+
}
|
|
574
|
+
cb(data);
|
|
567
575
|
} catch (e) {
|
|
568
576
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
569
577
|
res.end(JSON.stringify({ error: e.message }));
|
|
@@ -607,7 +615,7 @@ function start(port, noOpen) {
|
|
|
607
615
|
|
|
608
616
|
} else if (url.pathname === '/tab/analytics') {
|
|
609
617
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
610
|
-
res.end(tabAnalytics());
|
|
618
|
+
res.end(tabAnalytics(cwd));
|
|
611
619
|
|
|
612
620
|
} else if (url.pathname === '/tab/tools') {
|
|
613
621
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
@@ -659,12 +667,12 @@ function start(port, noOpen) {
|
|
|
659
667
|
|
|
660
668
|
/* ── Existing JSON API endpoints ────────────────────────────── */
|
|
661
669
|
} else if (url.pathname === '/api/analytics') {
|
|
662
|
-
const data = analytics.getAllStats();
|
|
670
|
+
const data = analytics.getAllStats(cwd);
|
|
663
671
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
664
672
|
res.end(JSON.stringify(data || { message: 'No analytics data yet' }));
|
|
665
673
|
|
|
666
674
|
} else if (url.pathname === '/api/roi') {
|
|
667
|
-
const data = roi.getAllProjectsROI();
|
|
675
|
+
const data = roi.getAllProjectsROI(cwd);
|
|
668
676
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
669
677
|
res.end(JSON.stringify(data));
|
|
670
678
|
|
|
@@ -768,7 +776,7 @@ function start(port, noOpen) {
|
|
|
768
776
|
const url = `http://localhost:${availablePort}`;
|
|
769
777
|
const portMsg = availablePort !== port ? ` (port ${port} was busy, using ${availablePort})` : '';
|
|
770
778
|
console.log(`\n \x1b[36mCRG Dev Kit\x1b[0m running at \x1b[1m${url}\x1b[0m${portMsg}\n`);
|
|
771
|
-
const analyticsData = analytics.getAllStats();
|
|
779
|
+
const analyticsData = analytics.getAllStats(cwd);
|
|
772
780
|
if (analyticsData) {
|
|
773
781
|
console.log(` \x1b[32m${analyticsData.avgSavingsPercent}% avg token savings\x1b[0m across \x1b[1m${analyticsData.totalSessions}\x1b[0m sessions\n`);
|
|
774
782
|
}
|