shiva-code 0.8.11 → 0.8.13
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/dist/analytics-6WNUPA56.js +337 -0
- package/dist/index.js +204 -159
- package/package.json +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import {
|
|
2
|
+
colors,
|
|
3
|
+
log
|
|
4
|
+
} from "./chunk-5RLMQDLT.js";
|
|
5
|
+
import {
|
|
6
|
+
getAnalyticsConfig,
|
|
7
|
+
getAnalyticsFilePath,
|
|
8
|
+
getAnalyticsSummaryPath,
|
|
9
|
+
hasShivaDir,
|
|
10
|
+
initShivaDir
|
|
11
|
+
} from "./chunk-PMA6MGQW.js";
|
|
12
|
+
import "./chunk-3RG5ZIWI.js";
|
|
13
|
+
|
|
14
|
+
// src/services/project/analytics.ts
|
|
15
|
+
import * as fs from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
function getMonthlyData(projectPath, yearMonth) {
|
|
18
|
+
const filePath = getAnalyticsFilePath(projectPath, yearMonth);
|
|
19
|
+
if (!fs.existsSync(filePath)) {
|
|
20
|
+
const month = yearMonth || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
21
|
+
return {
|
|
22
|
+
month,
|
|
23
|
+
sessions: [],
|
|
24
|
+
totalDuration: 0,
|
|
25
|
+
totalInputTokens: 0,
|
|
26
|
+
totalOutputTokens: 0,
|
|
27
|
+
toolUsage: {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
} catch {
|
|
34
|
+
const month = yearMonth || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
35
|
+
return {
|
|
36
|
+
month,
|
|
37
|
+
sessions: [],
|
|
38
|
+
totalDuration: 0,
|
|
39
|
+
totalInputTokens: 0,
|
|
40
|
+
totalOutputTokens: 0,
|
|
41
|
+
toolUsage: {}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function saveMonthlyData(projectPath, data) {
|
|
46
|
+
if (!hasShivaDir(projectPath)) {
|
|
47
|
+
initShivaDir(projectPath);
|
|
48
|
+
}
|
|
49
|
+
const filePath = getAnalyticsFilePath(projectPath, data.month);
|
|
50
|
+
const dir = path.dirname(filePath);
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
55
|
+
}
|
|
56
|
+
function getSummary(projectPath) {
|
|
57
|
+
const filePath = getAnalyticsSummaryPath(projectPath);
|
|
58
|
+
if (!fs.existsSync(filePath)) {
|
|
59
|
+
return {
|
|
60
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
61
|
+
allTimeSessions: 0,
|
|
62
|
+
allTimeDuration: 0,
|
|
63
|
+
allTimeInputTokens: 0,
|
|
64
|
+
allTimeOutputTokens: 0,
|
|
65
|
+
topTools: []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
70
|
+
return JSON.parse(content);
|
|
71
|
+
} catch {
|
|
72
|
+
return {
|
|
73
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
+
allTimeSessions: 0,
|
|
75
|
+
allTimeDuration: 0,
|
|
76
|
+
allTimeInputTokens: 0,
|
|
77
|
+
allTimeOutputTokens: 0,
|
|
78
|
+
topTools: []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function saveSummary(projectPath, summary) {
|
|
83
|
+
if (!hasShivaDir(projectPath)) {
|
|
84
|
+
initShivaDir(projectPath);
|
|
85
|
+
}
|
|
86
|
+
const filePath = getAnalyticsSummaryPath(projectPath);
|
|
87
|
+
const dir = path.dirname(filePath);
|
|
88
|
+
if (!fs.existsSync(dir)) {
|
|
89
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
fs.writeFileSync(filePath, JSON.stringify(summary, null, 2), "utf-8");
|
|
92
|
+
}
|
|
93
|
+
var ProjectAnalyticsService = class {
|
|
94
|
+
projectPath;
|
|
95
|
+
config;
|
|
96
|
+
constructor(projectPath, config) {
|
|
97
|
+
this.projectPath = projectPath;
|
|
98
|
+
this.config = config;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if analytics is enabled
|
|
102
|
+
*/
|
|
103
|
+
isEnabled() {
|
|
104
|
+
return this.config.enabled;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Record a session
|
|
108
|
+
*/
|
|
109
|
+
recordSession(session) {
|
|
110
|
+
if (!this.config.enabled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const yearMonth = session.date.slice(0, 7);
|
|
114
|
+
const monthly = getMonthlyData(this.projectPath, yearMonth);
|
|
115
|
+
monthly.sessions.push(session);
|
|
116
|
+
monthly.totalDuration += session.duration;
|
|
117
|
+
monthly.totalInputTokens += session.inputTokens;
|
|
118
|
+
monthly.totalOutputTokens += session.outputTokens;
|
|
119
|
+
for (const [tool, count] of Object.entries(session.toolsUsed)) {
|
|
120
|
+
monthly.toolUsage[tool] = (monthly.toolUsage[tool] || 0) + count;
|
|
121
|
+
}
|
|
122
|
+
saveMonthlyData(this.projectPath, monthly);
|
|
123
|
+
this.updateSummary(session);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Update summary with new session data
|
|
127
|
+
*/
|
|
128
|
+
updateSummary(session) {
|
|
129
|
+
const summary = getSummary(this.projectPath);
|
|
130
|
+
summary.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
131
|
+
summary.allTimeSessions += 1;
|
|
132
|
+
summary.allTimeDuration += session.duration;
|
|
133
|
+
summary.allTimeInputTokens += session.inputTokens;
|
|
134
|
+
summary.allTimeOutputTokens += session.outputTokens;
|
|
135
|
+
const allToolUsage = {};
|
|
136
|
+
for (const [tool, count] of Object.entries(session.toolsUsed)) {
|
|
137
|
+
allToolUsage[tool] = (allToolUsage[tool] || 0) + count;
|
|
138
|
+
}
|
|
139
|
+
for (const { name, count } of summary.topTools) {
|
|
140
|
+
allToolUsage[name] = (allToolUsage[name] || 0) + count;
|
|
141
|
+
}
|
|
142
|
+
summary.topTools = Object.entries(allToolUsage).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
|
|
143
|
+
saveSummary(this.projectPath, summary);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get analytics for a period
|
|
147
|
+
*/
|
|
148
|
+
getAnalytics(days = 30) {
|
|
149
|
+
const summary = getSummary(this.projectPath);
|
|
150
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
151
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
152
|
+
const cutoff = cutoffDate.toISOString().split("T")[0];
|
|
153
|
+
const allSessions = [];
|
|
154
|
+
const toolUsage = {};
|
|
155
|
+
const sessionsPerDay = {};
|
|
156
|
+
const months = this.getMonthsInRange(cutoffDate, /* @__PURE__ */ new Date());
|
|
157
|
+
for (const month of months) {
|
|
158
|
+
const monthly = getMonthlyData(this.projectPath, month);
|
|
159
|
+
for (const session of monthly.sessions) {
|
|
160
|
+
if (session.date >= cutoff) {
|
|
161
|
+
allSessions.push(session);
|
|
162
|
+
sessionsPerDay[session.date] = (sessionsPerDay[session.date] || 0) + 1;
|
|
163
|
+
for (const [tool, count] of Object.entries(session.toolsUsed)) {
|
|
164
|
+
toolUsage[tool] = (toolUsage[tool] || 0) + count;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const totalDuration = allSessions.reduce((sum, s) => sum + s.duration, 0);
|
|
170
|
+
const totalInputTokens = allSessions.reduce((sum, s) => sum + s.inputTokens, 0);
|
|
171
|
+
const totalOutputTokens = allSessions.reduce((sum, s) => sum + s.outputTokens, 0);
|
|
172
|
+
const topTools = Object.entries(toolUsage).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([name, count]) => ({ name, count }));
|
|
173
|
+
const sessionsPerDayArray = Object.entries(sessionsPerDay).sort((a, b) => a[0].localeCompare(b[0])).map(([date, count]) => ({ date, count }));
|
|
174
|
+
return {
|
|
175
|
+
totalSessions: allSessions.length,
|
|
176
|
+
totalDuration,
|
|
177
|
+
totalInputTokens,
|
|
178
|
+
totalOutputTokens,
|
|
179
|
+
averageSessionDuration: allSessions.length > 0 ? Math.round(totalDuration / allSessions.length) : 0,
|
|
180
|
+
topTools,
|
|
181
|
+
sessionsPerDay: sessionsPerDayArray
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get list of months between two dates
|
|
186
|
+
*/
|
|
187
|
+
getMonthsInRange(start, end) {
|
|
188
|
+
const months = [];
|
|
189
|
+
const current = new Date(start);
|
|
190
|
+
current.setDate(1);
|
|
191
|
+
while (current <= end) {
|
|
192
|
+
months.push(current.toISOString().slice(0, 7));
|
|
193
|
+
current.setMonth(current.getMonth() + 1);
|
|
194
|
+
}
|
|
195
|
+
return months;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get all-time summary
|
|
199
|
+
*/
|
|
200
|
+
getAllTimeSummary() {
|
|
201
|
+
return getSummary(this.projectPath);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get analytics for a specific session by ID
|
|
205
|
+
* Searches through monthly files to find the session
|
|
206
|
+
*/
|
|
207
|
+
getSessionAnalytics(sessionId) {
|
|
208
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
209
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
210
|
+
startDate.setFullYear(startDate.getFullYear() - 1);
|
|
211
|
+
const months = this.getMonthsInRange(startDate, endDate);
|
|
212
|
+
for (const month of months) {
|
|
213
|
+
const monthly = getMonthlyData(this.projectPath, month);
|
|
214
|
+
const session = monthly.sessions.find((s) => s.sessionId === sessionId);
|
|
215
|
+
if (session) {
|
|
216
|
+
return session;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get aggregated analytics for sync (last session or summary)
|
|
223
|
+
*/
|
|
224
|
+
getAnalyticsForSync() {
|
|
225
|
+
const summary = this.getAllTimeSummary();
|
|
226
|
+
return {
|
|
227
|
+
totalTokens: summary.allTimeInputTokens + summary.allTimeOutputTokens,
|
|
228
|
+
inputTokens: summary.allTimeInputTokens,
|
|
229
|
+
outputTokens: summary.allTimeOutputTokens,
|
|
230
|
+
durationMinutes: summary.allTimeDuration,
|
|
231
|
+
toolUsage: Object.fromEntries(summary.topTools.map((t) => [t.name, t.count]))
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Clear old data based on retention policy
|
|
236
|
+
*/
|
|
237
|
+
cleanupOldData() {
|
|
238
|
+
const config = getAnalyticsConfig(this.projectPath);
|
|
239
|
+
const cutoffDate = /* @__PURE__ */ new Date();
|
|
240
|
+
cutoffDate.setDate(cutoffDate.getDate() - config.retentionDays);
|
|
241
|
+
const cutoffMonth = cutoffDate.toISOString().slice(0, 7);
|
|
242
|
+
const analyticsDir = path.dirname(getAnalyticsFilePath(this.projectPath));
|
|
243
|
+
if (!fs.existsSync(analyticsDir)) {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
const files = fs.readdirSync(analyticsDir);
|
|
247
|
+
let deletedCount = 0;
|
|
248
|
+
for (const file of files) {
|
|
249
|
+
if (file === "summary.json" || !file.endsWith(".json")) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const month = file.replace(".json", "");
|
|
253
|
+
if (month < cutoffMonth) {
|
|
254
|
+
fs.unlinkSync(path.join(analyticsDir, file));
|
|
255
|
+
deletedCount++;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return deletedCount;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Export analytics data
|
|
262
|
+
*/
|
|
263
|
+
exportData(format = "json") {
|
|
264
|
+
const analytics = this.getAnalytics(365);
|
|
265
|
+
const summary = this.getAllTimeSummary();
|
|
266
|
+
if (format === "json") {
|
|
267
|
+
return JSON.stringify({
|
|
268
|
+
summary,
|
|
269
|
+
periodAnalytics: analytics,
|
|
270
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
271
|
+
}, null, 2);
|
|
272
|
+
}
|
|
273
|
+
const lines = [];
|
|
274
|
+
lines.push("Type,Metric,Value");
|
|
275
|
+
lines.push(`Summary,Total Sessions,${summary.allTimeSessions}`);
|
|
276
|
+
lines.push(`Summary,Total Duration (min),${summary.allTimeDuration}`);
|
|
277
|
+
lines.push(`Summary,Total Input Tokens,${summary.allTimeInputTokens}`);
|
|
278
|
+
lines.push(`Summary,Total Output Tokens,${summary.allTimeOutputTokens}`);
|
|
279
|
+
for (const tool of summary.topTools) {
|
|
280
|
+
lines.push(`Tool,${tool.name},${tool.count}`);
|
|
281
|
+
}
|
|
282
|
+
for (const day of analytics.sessionsPerDay) {
|
|
283
|
+
lines.push(`Daily,${day.date},${day.count}`);
|
|
284
|
+
}
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
function createAnalyticsService(projectPath, config) {
|
|
289
|
+
return new ProjectAnalyticsService(projectPath, config);
|
|
290
|
+
}
|
|
291
|
+
function formatAnalytics(analytics) {
|
|
292
|
+
const lines = [];
|
|
293
|
+
lines.push(colors.orange.bold("Projekt-Analytics"));
|
|
294
|
+
lines.push(colors.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
295
|
+
lines.push("");
|
|
296
|
+
lines.push(`Sessions: ${analytics.totalSessions}`);
|
|
297
|
+
lines.push(`Gesamtdauer: ${formatDuration(analytics.totalDuration)}`);
|
|
298
|
+
lines.push(`Durchschnittl. Dauer: ${formatDuration(analytics.averageSessionDuration)}`);
|
|
299
|
+
lines.push("");
|
|
300
|
+
lines.push(`Input Tokens: ${analytics.totalInputTokens.toLocaleString()}`);
|
|
301
|
+
lines.push(`Output Tokens: ${analytics.totalOutputTokens.toLocaleString()}`);
|
|
302
|
+
lines.push("");
|
|
303
|
+
if (analytics.topTools.length > 0) {
|
|
304
|
+
lines.push("Top Tools:");
|
|
305
|
+
for (const tool of analytics.topTools.slice(0, 5)) {
|
|
306
|
+
lines.push(` ${tool.name}: ${tool.count}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return lines;
|
|
310
|
+
}
|
|
311
|
+
function formatDuration(minutes) {
|
|
312
|
+
if (minutes < 60) {
|
|
313
|
+
return `${minutes} min`;
|
|
314
|
+
}
|
|
315
|
+
const hours = Math.floor(minutes / 60);
|
|
316
|
+
const mins = minutes % 60;
|
|
317
|
+
if (hours < 24) {
|
|
318
|
+
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
319
|
+
}
|
|
320
|
+
const days = Math.floor(hours / 24);
|
|
321
|
+
const remainingHours = hours % 24;
|
|
322
|
+
return `${days}d ${remainingHours}h`;
|
|
323
|
+
}
|
|
324
|
+
function displayAnalytics(analytics) {
|
|
325
|
+
const lines = formatAnalytics(analytics);
|
|
326
|
+
log.newline();
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
console.log(line);
|
|
329
|
+
}
|
|
330
|
+
log.newline();
|
|
331
|
+
}
|
|
332
|
+
export {
|
|
333
|
+
ProjectAnalyticsService,
|
|
334
|
+
createAnalyticsService,
|
|
335
|
+
displayAnalytics,
|
|
336
|
+
formatAnalytics
|
|
337
|
+
};
|