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.
@@ -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
+ };