@codemieai/code 0.0.12 → 0.0.14
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 +65 -778
- package/dist/agents/core/BaseAgentAdapter.d.ts.map +1 -1
- package/dist/agents/core/BaseAgentAdapter.js +31 -3
- package/dist/agents/core/BaseAgentAdapter.js.map +1 -1
- package/dist/agents/core/types.d.ts +18 -0
- package/dist/agents/core/types.d.ts.map +1 -1
- package/dist/agents/plugins/claude.plugin.d.ts +0 -3
- package/dist/agents/plugins/claude.plugin.d.ts.map +1 -1
- package/dist/agents/plugins/claude.plugin.js +12 -1
- package/dist/agents/plugins/claude.plugin.js.map +1 -1
- package/dist/agents/plugins/codex.plugin.d.ts.map +1 -1
- package/dist/agents/plugins/codex.plugin.js +40 -4
- package/dist/agents/plugins/codex.plugin.js.map +1 -1
- package/dist/agents/plugins/gemini.plugin.d.ts.map +1 -1
- package/dist/agents/plugins/gemini.plugin.js +50 -5
- package/dist/agents/plugins/gemini.plugin.js.map +1 -1
- package/dist/agents/registry.d.ts +15 -2
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +30 -6
- package/dist/agents/registry.js.map +1 -1
- package/dist/analytics/aggregation/adapters/claude.adapter.d.ts +37 -0
- package/dist/analytics/aggregation/adapters/claude.adapter.d.ts.map +1 -0
- package/dist/analytics/aggregation/adapters/claude.adapter.js +471 -0
- package/dist/analytics/aggregation/adapters/claude.adapter.js.map +1 -0
- package/dist/analytics/aggregation/adapters/codex.adapter.d.ts +25 -0
- package/dist/analytics/aggregation/adapters/codex.adapter.d.ts.map +1 -0
- package/dist/analytics/aggregation/adapters/codex.adapter.js +376 -0
- package/dist/analytics/aggregation/adapters/codex.adapter.js.map +1 -0
- package/dist/analytics/aggregation/adapters/gemini.adapter.d.ts +28 -0
- package/dist/analytics/aggregation/adapters/gemini.adapter.d.ts.map +1 -0
- package/dist/analytics/aggregation/adapters/gemini.adapter.js +320 -0
- package/dist/analytics/aggregation/adapters/gemini.adapter.js.map +1 -0
- package/dist/analytics/aggregation/adapters/index.d.ts +7 -0
- package/dist/analytics/aggregation/adapters/index.d.ts.map +1 -0
- package/dist/analytics/aggregation/adapters/index.js +7 -0
- package/dist/analytics/aggregation/adapters/index.js.map +1 -0
- package/dist/analytics/aggregation/aggregator.d.ts +49 -0
- package/dist/analytics/aggregation/aggregator.d.ts.map +1 -0
- package/dist/analytics/aggregation/aggregator.js +239 -0
- package/dist/analytics/aggregation/aggregator.js.map +1 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.d.ts +63 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.js +58 -0
- package/dist/analytics/aggregation/core/BaseAnalyticsAdapter.js.map +1 -0
- package/dist/analytics/aggregation/core/adapter.interface.d.ts +65 -0
- package/dist/analytics/aggregation/core/adapter.interface.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/adapter.interface.js +9 -0
- package/dist/analytics/aggregation/core/adapter.interface.js.map +1 -0
- package/dist/analytics/aggregation/core/aggregation-utils.d.ts +66 -0
- package/dist/analytics/aggregation/core/aggregation-utils.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/aggregation-utils.js +83 -0
- package/dist/analytics/aggregation/core/aggregation-utils.js.map +1 -0
- package/dist/analytics/aggregation/core/discovery.d.ts +40 -0
- package/dist/analytics/aggregation/core/discovery.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/discovery.js +132 -0
- package/dist/analytics/aggregation/core/discovery.js.map +1 -0
- package/dist/analytics/aggregation/core/file-utils.d.ts +23 -0
- package/dist/analytics/aggregation/core/file-utils.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/file-utils.js +208 -0
- package/dist/analytics/aggregation/core/file-utils.js.map +1 -0
- package/dist/analytics/aggregation/core/index.d.ts +11 -0
- package/dist/analytics/aggregation/core/index.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/index.js +11 -0
- package/dist/analytics/aggregation/core/index.js.map +1 -0
- package/dist/analytics/aggregation/core/project-mapping.d.ts +50 -0
- package/dist/analytics/aggregation/core/project-mapping.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/project-mapping.js +102 -0
- package/dist/analytics/aggregation/core/project-mapping.js.map +1 -0
- package/dist/analytics/aggregation/core/streaming.d.ts +26 -0
- package/dist/analytics/aggregation/core/streaming.d.ts.map +1 -0
- package/dist/analytics/aggregation/core/streaming.js +58 -0
- package/dist/analytics/aggregation/core/streaming.js.map +1 -0
- package/dist/analytics/aggregation/index.d.ts +8 -0
- package/dist/analytics/aggregation/index.d.ts.map +1 -0
- package/dist/analytics/aggregation/index.js +8 -0
- package/dist/analytics/aggregation/index.js.map +1 -0
- package/dist/analytics/aggregation/types.d.ts +258 -0
- package/dist/analytics/aggregation/types.d.ts.map +1 -0
- package/dist/analytics/aggregation/types.js +8 -0
- package/dist/analytics/aggregation/types.js.map +1 -0
- package/dist/analytics/config.d.ts +1 -0
- package/dist/analytics/config.d.ts.map +1 -1
- package/dist/analytics/config.js +20 -1
- package/dist/analytics/config.js.map +1 -1
- package/dist/analytics/index.d.ts +0 -9
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +14 -48
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/plugins/api-metrics.plugin.d.ts +4 -1
- package/dist/analytics/plugins/api-metrics.plugin.d.ts.map +1 -1
- package/dist/analytics/plugins/api-metrics.plugin.js +6 -22
- package/dist/analytics/plugins/api-metrics.plugin.js.map +1 -1
- package/dist/analytics/plugins/model-metrics.plugin.d.ts +4 -1
- package/dist/analytics/plugins/model-metrics.plugin.d.ts.map +1 -1
- package/dist/analytics/plugins/model-metrics.plugin.js +7 -17
- package/dist/analytics/plugins/model-metrics.plugin.js.map +1 -1
- package/dist/analytics/plugins/provider-metrics.plugin.d.ts +4 -1
- package/dist/analytics/plugins/provider-metrics.plugin.d.ts.map +1 -1
- package/dist/analytics/plugins/provider-metrics.plugin.js +7 -22
- package/dist/analytics/plugins/provider-metrics.plugin.js.map +1 -1
- package/dist/analytics/plugins/types.d.ts +0 -2
- package/dist/analytics/plugins/types.d.ts.map +1 -1
- package/dist/analytics/plugins/types.js.map +1 -1
- package/dist/analytics/session.d.ts +2 -2
- package/dist/analytics/session.d.ts.map +1 -1
- package/dist/analytics/session.js +3 -3
- package/dist/analytics/session.js.map +1 -1
- package/dist/analytics/types.d.ts +1 -1
- package/dist/analytics/types.d.ts.map +1 -1
- package/dist/cli/commands/analytics.d.ts.map +1 -1
- package/dist/cli/commands/analytics.js +512 -252
- package/dist/cli/commands/analytics.js.map +1 -1
- package/dist/cli/commands/setup.js +70 -1
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/utils/codemie-proxy.d.ts.map +1 -1
- package/dist/utils/codemie-proxy.js +16 -3
- package/dist/utils/codemie-proxy.js.map +1 -1
- package/dist/utils/proxy/http-client.d.ts.map +1 -1
- package/dist/utils/proxy/http-client.js +8 -2
- package/dist/utils/proxy/http-client.js.map +1 -1
- package/dist/utils/proxy/interceptors.d.ts.map +1 -1
- package/dist/utils/proxy/interceptors.js +4 -15
- package/dist/utils/proxy/interceptors.js.map +1 -1
- package/package.json +6 -2
|
@@ -1,43 +1,20 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
4
|
import { loadAnalyticsConfig } from '../../analytics/config.js';
|
|
5
5
|
import { logger } from '../../utils/logger.js';
|
|
6
|
-
import { readAnalyticsForLocalDate, readAnalyticsForLocalDateRange, filterEvents, calculateStats, exportToCSV, exportToJSON, listAnalyticsFiles, clearOldAnalytics, formatFileSize } from '../../utils/analytics-reader.js';
|
|
7
|
-
import { getLocalToday, getDefaultLocalDateRange, getLocalDateString } from '../../utils/date-formatter.js';
|
|
8
6
|
import { AgentRegistry } from '../../agents/registry.js';
|
|
7
|
+
import { ConfigLoader } from '../../utils/config-loader.js';
|
|
8
|
+
import { CodemieAnalyticsAggregator } from '../../analytics/aggregation/index.js';
|
|
9
9
|
export function createAnalyticsCommand() {
|
|
10
10
|
const command = new Command('analytics');
|
|
11
11
|
command
|
|
12
12
|
.description('Analytics management and insights')
|
|
13
|
-
.
|
|
14
|
-
.addCommand(createStatsCommand())
|
|
15
|
-
.addCommand(createExportCommand())
|
|
16
|
-
.addCommand(createClearCommand());
|
|
17
|
-
return command;
|
|
18
|
-
}
|
|
19
|
-
function createStatusCommand() {
|
|
20
|
-
const command = new Command('status');
|
|
21
|
-
command
|
|
22
|
-
.description('Show analytics configuration and today\'s statistics')
|
|
23
|
-
.option('--json', 'Output as JSON')
|
|
24
|
-
.action(async (options) => {
|
|
13
|
+
.action(async () => {
|
|
25
14
|
try {
|
|
26
15
|
const config = loadAnalyticsConfig();
|
|
27
|
-
//
|
|
28
|
-
if (options.json) {
|
|
29
|
-
const today = getLocalToday();
|
|
30
|
-
const todayEvents = await readAnalyticsForLocalDate(today);
|
|
31
|
-
const stats = todayEvents.length > 0 ? calculateStats(todayEvents) : null;
|
|
32
|
-
console.log(JSON.stringify({
|
|
33
|
-
config,
|
|
34
|
-
todayStats: stats
|
|
35
|
-
}, null, 2));
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// Human-readable output
|
|
16
|
+
// Show configuration
|
|
39
17
|
console.log(chalk.bold.cyan('\n📊 Analytics Configuration\n'));
|
|
40
|
-
// Configuration
|
|
41
18
|
console.log(chalk.cyan('Status: ') + (config.enabled ? chalk.green('Enabled') : chalk.red('Disabled')));
|
|
42
19
|
console.log(chalk.cyan('Target: ') + chalk.white(config.target));
|
|
43
20
|
console.log(chalk.cyan('Local Path: ') + chalk.white(config.localPath));
|
|
@@ -46,274 +23,557 @@ function createStatusCommand() {
|
|
|
46
23
|
}
|
|
47
24
|
console.log(chalk.cyan('Flush Interval: ') + chalk.white(`${config.flushInterval}ms`));
|
|
48
25
|
console.log(chalk.cyan('Buffer Size: ') + chalk.white(`${config.maxBufferSize} events`));
|
|
49
|
-
//
|
|
50
|
-
console.log(chalk.bold.cyan('\n
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(chalk.white('No analytics data for today yet.\n'));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const stats = calculateStats(todayEvents);
|
|
58
|
-
console.log(chalk.cyan('Sessions: ') + chalk.white(stats.totalSessions));
|
|
59
|
-
console.log(chalk.cyan('API Calls: ') + chalk.white(stats.apiRequests));
|
|
60
|
-
console.log(chalk.cyan('Success Rate: ') + chalk.white(`${stats.successRate.toFixed(1)}%`));
|
|
61
|
-
console.log(chalk.cyan('Avg Latency: ') + chalk.white(`${Math.round(stats.avgLatency)}ms`));
|
|
62
|
-
if (Object.keys(stats.agentUsage).length > 0) {
|
|
63
|
-
console.log(chalk.bold.cyan('\nAgent Activity:\n'));
|
|
64
|
-
Object.entries(stats.agentUsage)
|
|
65
|
-
.sort(([, a], [, b]) => b.sessions - a.sessions)
|
|
66
|
-
.slice(0, 5)
|
|
67
|
-
.forEach(([, usage]) => {
|
|
68
|
-
console.log(` ${chalk.white(usage.displayName.padEnd(20))} ${chalk.white(usage.sessions)} sessions, ${chalk.white(usage.apiCalls)} API calls`);
|
|
69
|
-
});
|
|
70
|
-
}
|
|
26
|
+
// Show help
|
|
27
|
+
console.log(chalk.bold.cyan('\n📋 Available Commands\n'));
|
|
28
|
+
console.log(chalk.white(' codemie analytics enable ') + chalk.gray('Enable analytics collection'));
|
|
29
|
+
console.log(chalk.white(' codemie analytics disable ') + chalk.gray('Disable analytics collection'));
|
|
30
|
+
console.log(chalk.white(' codemie analytics show ') + chalk.gray('Show analytics from all agents'));
|
|
71
31
|
console.log();
|
|
72
32
|
}
|
|
73
33
|
catch (error) {
|
|
74
|
-
logger.error('Failed to show analytics
|
|
34
|
+
logger.error('Failed to show analytics configuration:', error);
|
|
75
35
|
process.exit(1);
|
|
76
36
|
}
|
|
77
|
-
})
|
|
37
|
+
})
|
|
38
|
+
.addCommand(createEnableCommand())
|
|
39
|
+
.addCommand(createDisableCommand())
|
|
40
|
+
.addCommand(createShowCommand());
|
|
78
41
|
return command;
|
|
79
42
|
}
|
|
80
|
-
function
|
|
81
|
-
const command = new Command('
|
|
82
|
-
const defaultRange = getDefaultLocalDateRange(7);
|
|
43
|
+
function createEnableCommand() {
|
|
44
|
+
const command = new Command('enable');
|
|
83
45
|
command
|
|
84
|
-
.description('
|
|
85
|
-
.
|
|
86
|
-
.option('--to <date>', 'End date (YYYY-MM-DD, local timezone)', defaultRange.to)
|
|
87
|
-
.option('--agent <name>', 'Filter by agent name')
|
|
88
|
-
.action(async (options) => {
|
|
46
|
+
.description('Enable analytics collection')
|
|
47
|
+
.action(async () => {
|
|
89
48
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const availableAgents = AgentRegistry.getAllAgents()
|
|
95
|
-
.map(a => `${a.name} (${a.displayName})`)
|
|
96
|
-
.join(', ');
|
|
97
|
-
logger.error(`Unknown agent: ${options.agent}`);
|
|
98
|
-
console.log(chalk.yellow(`\nAvailable agents: ${availableAgents}\n`));
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
console.log(chalk.bold.cyan('\n📊 Analytics Statistics\n'));
|
|
103
|
-
console.log(chalk.white(`Date Range: ${options.from} to ${options.to} (local timezone)`));
|
|
104
|
-
if (options.agent) {
|
|
105
|
-
const adapter = AgentRegistry.getAgent(options.agent);
|
|
106
|
-
const displayName = adapter?.displayName || options.agent;
|
|
107
|
-
console.log(chalk.white(`Agent: ${displayName}`));
|
|
108
|
-
}
|
|
109
|
-
console.log();
|
|
110
|
-
// Read events using local dates
|
|
111
|
-
const events = await readAnalyticsForLocalDateRange(options.from, options.to);
|
|
112
|
-
if (events.length === 0) {
|
|
113
|
-
console.log(chalk.yellow('No analytics data found for the specified period.\n'));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
// Apply filters
|
|
117
|
-
const filters = {};
|
|
118
|
-
if (options.agent) {
|
|
119
|
-
filters.agent = options.agent;
|
|
120
|
-
}
|
|
121
|
-
const filteredEvents = filterEvents(events, filters);
|
|
122
|
-
if (filteredEvents.length === 0) {
|
|
123
|
-
console.log(chalk.yellow('No events match the specified filters.\n'));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
// Calculate statistics
|
|
127
|
-
const stats = calculateStats(filteredEvents);
|
|
128
|
-
// Display overview
|
|
129
|
-
console.log(chalk.bold.cyan('📋 Overview\n'));
|
|
130
|
-
console.log(chalk.cyan('Sessions: ') + chalk.white(stats.totalSessions));
|
|
131
|
-
console.log(chalk.cyan('API Calls: ') + chalk.white(stats.apiRequests));
|
|
132
|
-
if (stats.apiErrors > 0) {
|
|
133
|
-
console.log(chalk.cyan('API Errors: ') + chalk.red(stats.apiErrors));
|
|
134
|
-
}
|
|
135
|
-
// Display performance
|
|
136
|
-
console.log(chalk.bold.cyan('\n⚡ Performance\n'));
|
|
137
|
-
console.log(chalk.cyan('Avg Latency: ') + chalk.white(`${Math.round(stats.avgLatency)}ms`));
|
|
138
|
-
console.log(chalk.cyan('Success Rate: ') + chalk.white(`${stats.successRate.toFixed(1)}%`));
|
|
139
|
-
// Display agent usage
|
|
140
|
-
if (Object.keys(stats.agentUsage).length > 0) {
|
|
141
|
-
console.log(chalk.bold.cyan('\n🤖 Agent Usage\n'));
|
|
142
|
-
const totalApiCalls = Object.values(stats.agentUsage).reduce((sum, usage) => sum + usage.apiCalls, 0);
|
|
143
|
-
Object.entries(stats.agentUsage)
|
|
144
|
-
.sort(([, a], [, b]) => b.apiCalls - a.apiCalls)
|
|
145
|
-
.forEach(([, usage]) => {
|
|
146
|
-
const percentage = totalApiCalls > 0 ? ((usage.apiCalls / totalApiCalls) * 100).toFixed(1) : '0.0';
|
|
147
|
-
console.log(` ${chalk.white(usage.displayName.padEnd(20))} ${chalk.white(usage.sessions.toString().padStart(3))} sessions ${chalk.white(usage.apiCalls.toString().padStart(4))} API calls (${percentage}%)`);
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
// Display model usage
|
|
151
|
-
if (Object.keys(stats.modelUsage).length > 0) {
|
|
152
|
-
console.log(chalk.bold.cyan('\n🎯 Model Usage\n'));
|
|
153
|
-
const totalApiCalls = Object.values(stats.modelUsage).reduce((sum, usage) => sum + usage.apiCalls, 0);
|
|
154
|
-
Object.entries(stats.modelUsage)
|
|
155
|
-
.sort(([, a], [, b]) => b.apiCalls - a.apiCalls)
|
|
156
|
-
.slice(0, 10)
|
|
157
|
-
.forEach(([model, usage]) => {
|
|
158
|
-
const percentage = ((usage.apiCalls / totalApiCalls) * 100).toFixed(1);
|
|
159
|
-
console.log(` ${chalk.white(model.padEnd(30))} ${chalk.white(usage.apiCalls.toString().padStart(4))} calls (${percentage}%)`);
|
|
160
|
-
});
|
|
161
|
-
}
|
|
49
|
+
await updateAnalyticsEnabled(true);
|
|
50
|
+
logger.success('Analytics enabled');
|
|
51
|
+
console.log(chalk.white('Analytics data will be collected to:'));
|
|
52
|
+
console.log(chalk.cyan(' ~/.codemie/analytics/'));
|
|
162
53
|
console.log();
|
|
163
54
|
}
|
|
164
55
|
catch (error) {
|
|
165
|
-
logger.error('Failed to
|
|
56
|
+
logger.error('Failed to enable analytics:', error);
|
|
166
57
|
process.exit(1);
|
|
167
58
|
}
|
|
168
59
|
});
|
|
169
60
|
return command;
|
|
170
61
|
}
|
|
171
|
-
function
|
|
172
|
-
const command = new Command('
|
|
173
|
-
const defaultRange = getDefaultLocalDateRange(7);
|
|
62
|
+
function createDisableCommand() {
|
|
63
|
+
const command = new Command('disable');
|
|
174
64
|
command
|
|
175
|
-
.description('
|
|
176
|
-
.
|
|
177
|
-
.option('--output <path>', 'Output file path', getDefaultExportPath())
|
|
178
|
-
.option('--from <date>', 'Start date (YYYY-MM-DD, local timezone)', defaultRange.from)
|
|
179
|
-
.option('--to <date>', 'End date (YYYY-MM-DD, local timezone)', defaultRange.to)
|
|
180
|
-
.option('--agent <name>', 'Filter by agent name')
|
|
181
|
-
.option('--event-type <type>', 'Filter by event type')
|
|
182
|
-
.action(async (options) => {
|
|
65
|
+
.description('Disable analytics collection')
|
|
66
|
+
.action(async () => {
|
|
183
67
|
try {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
// Validate agent filter against registry
|
|
190
|
-
if (options.agent) {
|
|
191
|
-
const agentNames = AgentRegistry.getAgentNames();
|
|
192
|
-
if (!agentNames.includes(options.agent)) {
|
|
193
|
-
const availableAgents = AgentRegistry.getAllAgents()
|
|
194
|
-
.map(a => `${a.name} (${a.displayName})`)
|
|
195
|
-
.join(', ');
|
|
196
|
-
logger.error(`Unknown agent: ${options.agent}`);
|
|
197
|
-
console.log(chalk.yellow(`\nAvailable agents: ${availableAgents}\n`));
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
console.log(chalk.bold.cyan('\n📤 Exporting Analytics Data\n'));
|
|
202
|
-
console.log(chalk.white(`Date Range: ${options.from} to ${options.to} (local timezone)`));
|
|
203
|
-
console.log(chalk.white(`Format: ${options.format.toUpperCase()}`));
|
|
204
|
-
if (options.agent) {
|
|
205
|
-
const adapter = AgentRegistry.getAgent(options.agent);
|
|
206
|
-
const displayName = adapter?.displayName || options.agent;
|
|
207
|
-
console.log(chalk.white(`Agent Filter: ${displayName}`));
|
|
208
|
-
}
|
|
209
|
-
if (options.eventType) {
|
|
210
|
-
console.log(chalk.white(`Event Type Filter: ${options.eventType}`));
|
|
211
|
-
}
|
|
212
|
-
console.log();
|
|
213
|
-
// Read events using local dates
|
|
214
|
-
const events = await readAnalyticsForLocalDateRange(options.from, options.to);
|
|
215
|
-
if (events.length === 0) {
|
|
216
|
-
console.log(chalk.yellow('No analytics data found for the specified period.\n'));
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// Apply filters
|
|
220
|
-
const filters = {};
|
|
221
|
-
if (options.agent) {
|
|
222
|
-
filters.agent = options.agent;
|
|
223
|
-
}
|
|
224
|
-
if (options.eventType) {
|
|
225
|
-
filters.eventType = options.eventType;
|
|
226
|
-
}
|
|
227
|
-
const filteredEvents = filterEvents(events, filters);
|
|
228
|
-
if (filteredEvents.length === 0) {
|
|
229
|
-
console.log(chalk.yellow('No events match the specified filters.\n'));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
// Export
|
|
233
|
-
console.log(chalk.white(`Exporting ${filteredEvents.length} analytics records...`));
|
|
234
|
-
if (options.format === 'csv') {
|
|
235
|
-
await exportToCSV(filteredEvents, options.output);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
await exportToJSON(filteredEvents, options.output);
|
|
239
|
-
}
|
|
240
|
-
logger.success(`Exported to ${options.output}`);
|
|
241
|
-
console.log(chalk.white(`Total records: ${filteredEvents.length}`));
|
|
68
|
+
await updateAnalyticsEnabled(false);
|
|
69
|
+
logger.success('Analytics disabled');
|
|
70
|
+
console.log(chalk.white('No analytics data will be collected.'));
|
|
242
71
|
console.log();
|
|
243
72
|
}
|
|
244
73
|
catch (error) {
|
|
245
|
-
logger.error('Failed to
|
|
74
|
+
logger.error('Failed to disable analytics:', error);
|
|
246
75
|
process.exit(1);
|
|
247
76
|
}
|
|
248
77
|
});
|
|
249
78
|
return command;
|
|
250
79
|
}
|
|
251
|
-
function
|
|
252
|
-
const command = new Command('
|
|
80
|
+
function createShowCommand() {
|
|
81
|
+
const command = new Command('show');
|
|
253
82
|
command
|
|
254
|
-
.description('
|
|
255
|
-
.option('--
|
|
256
|
-
.option('
|
|
83
|
+
.description('Show analytics from all agents (Gemini, Claude, Codex, etc.)')
|
|
84
|
+
.option('--from <date>', 'Start date (YYYY-MM-DD)')
|
|
85
|
+
.option('--to <date>', 'End date (YYYY-MM-DD)')
|
|
86
|
+
.option('--agent <name>', 'Filter by agent (gemini|claude|codex|codemie-code)')
|
|
87
|
+
.option('--project <path>', 'Filter by project path')
|
|
88
|
+
.option('--format <format>', 'Output format (json|table)', 'table')
|
|
89
|
+
.option('--output <file>', 'Output file (for JSON format)')
|
|
257
90
|
.action(async (options) => {
|
|
258
91
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
92
|
+
// Check if analytics is disabled
|
|
93
|
+
const config = loadAnalyticsConfig();
|
|
94
|
+
if (!config.enabled) {
|
|
95
|
+
console.log(chalk.yellow('\nAnalytics is disabled. No data is being collected.\n'));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.bold.cyan('\n📊 CodeMie Analytics Aggregation\n'));
|
|
99
|
+
// Parse dates
|
|
100
|
+
const dateFrom = options.from ? new Date(options.from) : undefined;
|
|
101
|
+
const dateTo = options.to ? new Date(options.to) : undefined;
|
|
102
|
+
// Validate dates
|
|
103
|
+
if (dateFrom && isNaN(dateFrom.getTime())) {
|
|
104
|
+
logger.error('Invalid start date format. Use YYYY-MM-DD');
|
|
262
105
|
process.exit(1);
|
|
263
106
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const threshold = new Date();
|
|
268
|
-
threshold.setDate(threshold.getDate() - days);
|
|
269
|
-
const filesToDelete = files.filter(file => new Date(file.date) < threshold);
|
|
270
|
-
if (filesToDelete.length === 0) {
|
|
271
|
-
console.log(chalk.white(`No analytics files older than ${days} days.\n`));
|
|
272
|
-
return;
|
|
107
|
+
if (dateTo && isNaN(dateTo.getTime())) {
|
|
108
|
+
logger.error('Invalid end date format. Use YYYY-MM-DD');
|
|
109
|
+
process.exit(1);
|
|
273
110
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
111
|
+
// Validate agent if specified
|
|
112
|
+
if (options.agent) {
|
|
113
|
+
const validAgents = AgentRegistry.getAgentNames();
|
|
114
|
+
if (!validAgents.includes(options.agent)) {
|
|
115
|
+
logger.error(`Invalid agent: ${options.agent}`);
|
|
116
|
+
console.log(chalk.white('Available agents:'));
|
|
117
|
+
validAgents.forEach(name => {
|
|
118
|
+
const adapter = AgentRegistry.getAgent(name);
|
|
119
|
+
console.log(` ${chalk.cyan(name.padEnd(15))} ${chalk.white(adapter?.displayName || '')}`);
|
|
120
|
+
});
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Create aggregator and fetch sessions
|
|
125
|
+
const aggregator = new CodemieAnalyticsAggregator();
|
|
126
|
+
const sessions = await aggregator.aggregateSessions({
|
|
127
|
+
dateFrom,
|
|
128
|
+
dateTo,
|
|
129
|
+
agent: options.agent,
|
|
130
|
+
projectPath: options.project
|
|
282
131
|
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
132
|
+
if (sessions.length === 0) {
|
|
133
|
+
console.log(chalk.yellow('No sessions found matching the criteria.\n'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Output based on format
|
|
137
|
+
if (options.format === 'json') {
|
|
138
|
+
const output = {
|
|
139
|
+
version: '1.0.0',
|
|
140
|
+
exportedAt: new Date().toISOString(),
|
|
141
|
+
filters: {
|
|
142
|
+
dateFrom: dateFrom?.toISOString(),
|
|
143
|
+
dateTo: dateTo?.toISOString(),
|
|
144
|
+
agent: options.agent,
|
|
145
|
+
projectPath: options.project
|
|
146
|
+
},
|
|
147
|
+
sessions
|
|
148
|
+
};
|
|
149
|
+
if (options.output) {
|
|
150
|
+
const { writeFile } = await import('node:fs/promises');
|
|
151
|
+
await writeFile(options.output, JSON.stringify(output, null, 2));
|
|
152
|
+
logger.success(`Exported to ${options.output}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(JSON.stringify(output, null, 2));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Table format
|
|
160
|
+
console.log(chalk.white(`Found ${chalk.cyan(sessions.length.toString())} sessions\n`));
|
|
161
|
+
// Show date range of found sessions
|
|
162
|
+
if (sessions.length > 0) {
|
|
163
|
+
const startDates = sessions.map(s => s.startTime.getTime());
|
|
164
|
+
const endDates = sessions.map(s => s.endTime?.getTime() || s.startTime.getTime());
|
|
165
|
+
const earliestDate = new Date(Math.min(...startDates));
|
|
166
|
+
const latestDate = new Date(Math.max(...endDates));
|
|
167
|
+
console.log(chalk.bold.white('📅 Date Range\n'));
|
|
168
|
+
const dateRangeTable = new Table({
|
|
169
|
+
head: [chalk.cyan('Period'), chalk.cyan('Date')],
|
|
170
|
+
style: {
|
|
171
|
+
head: [],
|
|
172
|
+
border: ['grey']
|
|
173
|
+
},
|
|
174
|
+
colWidths: [25, 60]
|
|
175
|
+
});
|
|
176
|
+
dateRangeTable.push([chalk.white('From'), chalk.white(earliestDate.toLocaleString())], [chalk.white('To'), chalk.white(latestDate.toLocaleString())]);
|
|
177
|
+
console.log(dateRangeTable.toString());
|
|
178
|
+
console.log();
|
|
179
|
+
}
|
|
180
|
+
// Calculate summary statistics
|
|
181
|
+
const totalMessages = sessions.reduce((sum, s) => sum + s.userMessageCount + s.assistantMessageCount, 0);
|
|
182
|
+
const totalTokens = sessions.reduce((sum, s) => sum + s.tokens.total, 0);
|
|
183
|
+
const totalInputTokens = sessions.reduce((sum, s) => sum + s.tokens.input, 0);
|
|
184
|
+
const totalOutputTokens = sessions.reduce((sum, s) => sum + s.tokens.output, 0);
|
|
185
|
+
const totalCacheRead = sessions.reduce((sum, s) => sum + s.tokens.cacheRead, 0);
|
|
186
|
+
const totalCacheCreation = sessions.reduce((sum, s) => sum + s.tokens.cacheCreation, 0);
|
|
187
|
+
const totalThoughts = sessions.reduce((sum, s) => sum + (s.tokens.thoughts || 0), 0);
|
|
188
|
+
const totalReasoning = sessions.reduce((sum, s) => sum + (s.tokens.reasoning || 0), 0);
|
|
189
|
+
const totalToolTokens = sessions.reduce((sum, s) => sum + (s.tokens.tool || 0), 0);
|
|
190
|
+
const totalToolCalls = sessions.reduce((sum, s) => sum + s.toolCallCount, 0);
|
|
191
|
+
const totalFileModifications = sessions.reduce((sum, s) => sum + s.fileModifications, 0);
|
|
192
|
+
// 1. SUMMARY - Sessions & Messages
|
|
193
|
+
console.log(chalk.bold.white('📈 Summary Statistics\n'));
|
|
194
|
+
const summaryTable = new Table({
|
|
195
|
+
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
|
196
|
+
style: {
|
|
197
|
+
head: [],
|
|
198
|
+
border: ['grey']
|
|
199
|
+
},
|
|
200
|
+
colWidths: [25, 60]
|
|
201
|
+
});
|
|
202
|
+
const totalPrompts = sessions.reduce((sum, s) => sum + s.userMessageCount, 0);
|
|
203
|
+
const totalResponses = sessions.reduce((sum, s) => sum + s.assistantMessageCount, 0);
|
|
204
|
+
summaryTable.push([chalk.white('Sessions'), chalk.white(sessions.length.toString())], [chalk.white('Messages'), chalk.white(`${totalMessages.toLocaleString()} (${totalPrompts.toLocaleString()} prompts + ${totalResponses.toLocaleString()} responses)`)]);
|
|
205
|
+
console.log(summaryTable.toString());
|
|
206
|
+
console.log();
|
|
207
|
+
// 2. Code Generation Statistics
|
|
208
|
+
const sessionsWithFileStats = sessions.filter(s => s.fileStats);
|
|
209
|
+
if (sessionsWithFileStats.length > 0) {
|
|
210
|
+
const totalLinesGenerated = sessionsWithFileStats.reduce((sum, s) => sum + (s.fileStats?.totalLinesAdded || 0), 0);
|
|
211
|
+
const totalLinesRemoved = sessionsWithFileStats.reduce((sum, s) => sum + (s.fileStats?.totalLinesRemoved || 0), 0);
|
|
212
|
+
const totalFilesCreated = sessionsWithFileStats.reduce((sum, s) => sum + (s.fileStats?.filesCreated || 0), 0);
|
|
213
|
+
const totalFilesModified = sessionsWithFileStats.reduce((sum, s) => sum + (s.fileStats?.filesModified || 0), 0);
|
|
214
|
+
const netLines = totalLinesGenerated - totalLinesRemoved;
|
|
215
|
+
console.log(chalk.bold.white('📝 Code Generation\n'));
|
|
216
|
+
const codeGenTable = new Table({
|
|
217
|
+
head: [chalk.cyan('Metric'), chalk.cyan('Value')],
|
|
218
|
+
style: {
|
|
219
|
+
head: [],
|
|
220
|
+
border: ['grey']
|
|
221
|
+
},
|
|
222
|
+
colWidths: [25, 60]
|
|
223
|
+
});
|
|
224
|
+
codeGenTable.push([chalk.white('Lines Generated'), chalk.white(totalLinesGenerated.toLocaleString())]);
|
|
225
|
+
if (totalLinesRemoved > 0) {
|
|
226
|
+
codeGenTable.push([chalk.white('Lines Removed'), chalk.white(totalLinesRemoved.toLocaleString())], [chalk.white('Net Lines'), chalk.white(netLines.toLocaleString())]);
|
|
227
|
+
}
|
|
228
|
+
codeGenTable.push([chalk.white('Files Created'), chalk.white(totalFilesCreated.toString())], [chalk.white('Files Modified'), chalk.white(totalFilesModified.toString())], [chalk.white('File Modifications'), chalk.white(totalFileModifications.toString())]);
|
|
229
|
+
console.log(codeGenTable.toString());
|
|
230
|
+
console.log();
|
|
231
|
+
}
|
|
232
|
+
// 3. Token Breakdown
|
|
233
|
+
console.log(chalk.bold.white('💰 Token Usage\n'));
|
|
234
|
+
const tokenTable = new Table({
|
|
235
|
+
head: [chalk.cyan('Type'), chalk.cyan('Tokens')],
|
|
236
|
+
style: {
|
|
237
|
+
head: [],
|
|
238
|
+
border: ['grey']
|
|
239
|
+
},
|
|
240
|
+
colWidths: [25, 60]
|
|
241
|
+
});
|
|
242
|
+
tokenTable.push([chalk.white('Total'), chalk.white(totalTokens.toLocaleString())], [chalk.white('Input'), chalk.white(totalInputTokens.toLocaleString())], [chalk.white('Output'), chalk.white(totalOutputTokens.toLocaleString())], [chalk.white('Cache Read'), chalk.white(totalCacheRead.toLocaleString())]);
|
|
243
|
+
if (totalCacheCreation > 0) {
|
|
244
|
+
tokenTable.push([chalk.white('Cache Creation'), chalk.white(totalCacheCreation.toLocaleString())]);
|
|
245
|
+
}
|
|
246
|
+
if (totalThoughts > 0) {
|
|
247
|
+
tokenTable.push([chalk.white('Thoughts'), chalk.white(totalThoughts.toLocaleString())]);
|
|
248
|
+
}
|
|
249
|
+
if (totalReasoning > 0) {
|
|
250
|
+
tokenTable.push([chalk.white('Reasoning'), chalk.white(totalReasoning.toLocaleString())]);
|
|
251
|
+
}
|
|
252
|
+
if (totalToolTokens > 0) {
|
|
253
|
+
tokenTable.push([chalk.white('Tool'), chalk.white(totalToolTokens.toLocaleString())]);
|
|
254
|
+
}
|
|
255
|
+
console.log(tokenTable.toString());
|
|
256
|
+
console.log();
|
|
257
|
+
// 4. BY AGENT - Agent breakdown
|
|
258
|
+
const byAgent = {};
|
|
259
|
+
for (const session of sessions) {
|
|
260
|
+
if (!byAgent[session.agent]) {
|
|
261
|
+
const adapter = AgentRegistry.getAgent(session.agent);
|
|
262
|
+
byAgent[session.agent] = {
|
|
263
|
+
sessions: 0,
|
|
264
|
+
tokens: 0,
|
|
265
|
+
prompts: 0,
|
|
266
|
+
apiCalls: 0,
|
|
267
|
+
displayName: adapter?.displayName || session.agent
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
byAgent[session.agent].sessions++;
|
|
271
|
+
byAgent[session.agent].tokens += session.tokens.total;
|
|
272
|
+
byAgent[session.agent].prompts += session.userMessageCount;
|
|
273
|
+
// Count API calls from model usage
|
|
274
|
+
byAgent[session.agent].apiCalls += Object.values(session.modelUsage).reduce((sum, count) => sum + count, 0);
|
|
275
|
+
}
|
|
276
|
+
console.log(chalk.bold.white('🤖 Breakdown by Agent\n'));
|
|
277
|
+
const sortedAgents = Object.entries(byAgent)
|
|
278
|
+
.sort((a, b) => b[1].sessions - a[1].sessions);
|
|
279
|
+
// Create table for agent breakdown
|
|
280
|
+
const agentTable = new Table({
|
|
281
|
+
head: [chalk.cyan('Agent'), chalk.cyan('Prompts'), chalk.cyan('Sessions'), chalk.cyan('API Calls'), chalk.cyan('Share')],
|
|
282
|
+
style: {
|
|
283
|
+
head: [],
|
|
284
|
+
border: ['grey']
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
for (const [, stats] of sortedAgents) {
|
|
288
|
+
const percentage = ((stats.sessions / sessions.length) * 100).toFixed(1);
|
|
289
|
+
agentTable.push([
|
|
290
|
+
chalk.white(stats.displayName),
|
|
291
|
+
chalk.white(stats.prompts.toString()),
|
|
292
|
+
chalk.white(stats.sessions.toString()),
|
|
293
|
+
chalk.white(stats.apiCalls.toString()),
|
|
294
|
+
chalk.white(`${percentage}%`)
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
console.log(agentTable.toString());
|
|
298
|
+
console.log();
|
|
299
|
+
// Show model usage breakdown
|
|
300
|
+
const modelBreakdown = {};
|
|
301
|
+
for (const session of sessions) {
|
|
302
|
+
for (const [modelName, count] of Object.entries(session.modelUsage)) {
|
|
303
|
+
modelBreakdown[modelName] = (modelBreakdown[modelName] || 0) + count;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (Object.keys(modelBreakdown).length > 0) {
|
|
307
|
+
const totalModelCalls = Object.values(modelBreakdown).reduce((sum, count) => sum + count, 0);
|
|
308
|
+
console.log(chalk.bold.white(' Models:\n'));
|
|
309
|
+
const sortedModels = Object.entries(modelBreakdown)
|
|
310
|
+
.sort((a, b) => b[1] - a[1]);
|
|
311
|
+
// Create table for models
|
|
312
|
+
const modelTable = new Table({
|
|
313
|
+
head: [chalk.cyan('Model'), chalk.cyan('Calls'), chalk.cyan('Share')],
|
|
314
|
+
style: {
|
|
315
|
+
head: [],
|
|
316
|
+
border: ['grey']
|
|
317
|
+
},
|
|
318
|
+
colWidths: [55, 12, 12]
|
|
319
|
+
});
|
|
320
|
+
for (const [modelName, count] of sortedModels) {
|
|
321
|
+
const percentage = ((count / totalModelCalls) * 100).toFixed(1);
|
|
322
|
+
modelTable.push([
|
|
323
|
+
chalk.white(modelName),
|
|
324
|
+
chalk.white(count.toString()),
|
|
325
|
+
chalk.white(`${percentage}%`)
|
|
326
|
+
]);
|
|
327
|
+
}
|
|
328
|
+
console.log(' ' + modelTable.toString().split('\n').join('\n '));
|
|
329
|
+
console.log();
|
|
330
|
+
}
|
|
331
|
+
// 5. TOOL USAGE
|
|
332
|
+
if (totalToolCalls > 0) {
|
|
333
|
+
const toolBreakdown = {};
|
|
334
|
+
const toolStatusBreakdown = {};
|
|
335
|
+
for (const session of sessions) {
|
|
336
|
+
for (const [toolName, count] of Object.entries(session.toolUsage)) {
|
|
337
|
+
toolBreakdown[toolName] = (toolBreakdown[toolName] || 0) + count;
|
|
338
|
+
}
|
|
339
|
+
for (const [toolName, status] of Object.entries(session.toolStatus)) {
|
|
340
|
+
if (!toolStatusBreakdown[toolName]) {
|
|
341
|
+
toolStatusBreakdown[toolName] = { success: 0, failure: 0 };
|
|
342
|
+
}
|
|
343
|
+
toolStatusBreakdown[toolName].success += status.success;
|
|
344
|
+
toolStatusBreakdown[toolName].failure += status.failure;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (Object.keys(toolBreakdown).length > 0) {
|
|
348
|
+
console.log(chalk.bold.white('🔧 Tool Usage\n'));
|
|
349
|
+
const sortedTools = Object.entries(toolBreakdown)
|
|
350
|
+
.sort((a, b) => b[1] - a[1]);
|
|
351
|
+
// Create table for tools
|
|
352
|
+
const toolTable = new Table({
|
|
353
|
+
head: [chalk.cyan('Tool'), chalk.cyan('Calls'), chalk.cyan('Share'), chalk.cyan('Status'), chalk.cyan('Success Rate')],
|
|
354
|
+
style: {
|
|
355
|
+
head: [],
|
|
356
|
+
border: ['grey']
|
|
357
|
+
},
|
|
358
|
+
colWidths: [20, 10, 8, 20, 15]
|
|
359
|
+
});
|
|
360
|
+
for (const [toolName, count] of sortedTools) {
|
|
361
|
+
const percentage = ((count / totalToolCalls) * 100).toFixed(1);
|
|
362
|
+
const status = toolStatusBreakdown[toolName];
|
|
363
|
+
const successRate = status ? ((status.success / count) * 100).toFixed(1) : '0.0';
|
|
364
|
+
// Build status text with color coding
|
|
365
|
+
let statusText;
|
|
366
|
+
let successRateText;
|
|
367
|
+
if (status && status.failure > 0 && status.success === 0) {
|
|
368
|
+
// Critical: 0% success rate with failures - RED
|
|
369
|
+
statusText = chalk.red(`(${status.success} ✓, ${status.failure} ✗)`);
|
|
370
|
+
successRateText = chalk.red(`${successRate}%`);
|
|
371
|
+
}
|
|
372
|
+
else if (status && status.failure > 0) {
|
|
373
|
+
// Warning: some failures but has successes - YELLOW
|
|
374
|
+
statusText = chalk.yellow(`(${status.success} ✓, ${status.failure} ✗)`);
|
|
375
|
+
successRateText = chalk.yellow(`${successRate}%`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Success: no failures - GREEN
|
|
379
|
+
statusText = chalk.green(`(${status?.success || 0} ✓)`);
|
|
380
|
+
successRateText = chalk.green(`${successRate}%`);
|
|
381
|
+
}
|
|
382
|
+
toolTable.push([
|
|
383
|
+
chalk.white(toolName),
|
|
384
|
+
chalk.white(count.toString()),
|
|
385
|
+
chalk.white(`${percentage}%`),
|
|
386
|
+
statusText,
|
|
387
|
+
successRateText
|
|
388
|
+
]);
|
|
389
|
+
}
|
|
390
|
+
console.log(toolTable.toString());
|
|
391
|
+
console.log();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// 6. BREAKDOWN BY PROJECT
|
|
395
|
+
const byProject = {};
|
|
396
|
+
for (const session of sessions) {
|
|
397
|
+
const projectPath = session.projectPath || 'other';
|
|
398
|
+
if (!byProject[projectPath]) {
|
|
399
|
+
byProject[projectPath] = {
|
|
400
|
+
sessions: 0,
|
|
401
|
+
tokens: 0,
|
|
402
|
+
linesGenerated: 0,
|
|
403
|
+
filesCreated: 0,
|
|
404
|
+
filesModified: 0,
|
|
405
|
+
languageStats: {},
|
|
406
|
+
formatStats: {}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const project = byProject[projectPath];
|
|
410
|
+
project.sessions++;
|
|
411
|
+
project.tokens += session.tokens.total;
|
|
412
|
+
project.linesGenerated += session.fileStats?.totalLinesAdded || 0;
|
|
413
|
+
project.filesCreated += session.fileStats?.filesCreated || 0;
|
|
414
|
+
project.filesModified += session.fileStats?.filesModified || 0;
|
|
415
|
+
// Aggregate language stats for this project
|
|
416
|
+
// We track unique sessions per language using Set
|
|
417
|
+
if (session.fileStats?.byLanguage) {
|
|
418
|
+
for (const [lang, stats] of Object.entries(session.fileStats.byLanguage)) {
|
|
419
|
+
if (!project.languageStats[lang]) {
|
|
420
|
+
project.languageStats[lang] = {
|
|
421
|
+
sessionIds: new Set(),
|
|
422
|
+
tokens: 0,
|
|
423
|
+
lines: 0,
|
|
424
|
+
filesCreated: 0,
|
|
425
|
+
filesModified: 0
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Track unique session ID
|
|
429
|
+
project.languageStats[lang].sessionIds.add(session.sessionId);
|
|
430
|
+
// Attribute tokens proportionally based on lines generated
|
|
431
|
+
const totalLinesInSession = session.fileStats.totalLinesAdded || 1;
|
|
432
|
+
const tokenShare = (stats.linesAdded / totalLinesInSession) * session.tokens.total;
|
|
433
|
+
project.languageStats[lang].tokens += tokenShare;
|
|
434
|
+
project.languageStats[lang].lines += stats.linesAdded;
|
|
435
|
+
project.languageStats[lang].filesCreated += stats.filesCreated;
|
|
436
|
+
project.languageStats[lang].filesModified += stats.filesModified;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Aggregate format stats for this project
|
|
440
|
+
if (session.fileStats?.byFormat) {
|
|
441
|
+
for (const [format, stats] of Object.entries(session.fileStats.byFormat)) {
|
|
442
|
+
if (!project.formatStats[format]) {
|
|
443
|
+
project.formatStats[format] = {
|
|
444
|
+
sessionIds: new Set(),
|
|
445
|
+
tokens: 0,
|
|
446
|
+
lines: 0,
|
|
447
|
+
filesCreated: 0,
|
|
448
|
+
filesModified: 0
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
// Track unique session ID
|
|
452
|
+
project.formatStats[format].sessionIds.add(session.sessionId);
|
|
453
|
+
// Attribute tokens proportionally based on lines generated
|
|
454
|
+
const totalLinesInSession = session.fileStats.totalLinesAdded || 1;
|
|
455
|
+
const tokenShare = (stats.linesAdded / totalLinesInSession) * session.tokens.total;
|
|
456
|
+
project.formatStats[format].tokens += tokenShare;
|
|
457
|
+
project.formatStats[format].lines += stats.linesAdded;
|
|
458
|
+
project.formatStats[format].filesCreated += stats.filesCreated;
|
|
459
|
+
project.formatStats[format].filesModified += stats.filesModified;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (Object.keys(byProject).length > 0) {
|
|
464
|
+
console.log(chalk.bold.white('📁 Breakdown by Project\n'));
|
|
465
|
+
const sortedProjects = Object.entries(byProject)
|
|
466
|
+
.sort((a, b) => b[1].linesGenerated - a[1].linesGenerated);
|
|
467
|
+
for (const [projectPath, stats] of sortedProjects) {
|
|
468
|
+
const totalLinesInProjects = Object.values(byProject).reduce((sum, p) => sum + p.linesGenerated, 0);
|
|
469
|
+
const percentage = totalLinesInProjects > 0 ? ((stats.linesGenerated / totalLinesInProjects) * 100).toFixed(1) : '0.0';
|
|
470
|
+
console.log(chalk.cyan(`\n ${projectPath}\n`));
|
|
471
|
+
// Project summary table
|
|
472
|
+
const projectSummaryTable = new Table({
|
|
473
|
+
head: [chalk.cyan('Sessions'), chalk.cyan('Tokens'), chalk.cyan('Lines'), chalk.cyan('Created'), chalk.cyan('Modified'), chalk.cyan('Share')],
|
|
474
|
+
style: {
|
|
475
|
+
head: [],
|
|
476
|
+
border: ['grey']
|
|
477
|
+
},
|
|
478
|
+
colWidths: [12, 16, 10, 11, 12, 10]
|
|
479
|
+
});
|
|
480
|
+
projectSummaryTable.push([
|
|
481
|
+
chalk.white(stats.sessions.toString()),
|
|
482
|
+
chalk.white(stats.tokens.toLocaleString()),
|
|
483
|
+
chalk.white(stats.linesGenerated.toLocaleString()),
|
|
484
|
+
chalk.white(stats.filesCreated.toString()),
|
|
485
|
+
chalk.white(stats.filesModified.toString()),
|
|
486
|
+
chalk.white(`${percentage}%`)
|
|
487
|
+
]);
|
|
488
|
+
console.log(' ' + projectSummaryTable.toString().split('\n').join('\n '));
|
|
489
|
+
// Show language breakdown for this project
|
|
490
|
+
if (Object.keys(stats.languageStats).length > 0) {
|
|
491
|
+
console.log(chalk.white('\n By Language:\n'));
|
|
492
|
+
const sortedLangs = Object.entries(stats.languageStats)
|
|
493
|
+
.sort((a, b) => b[1].lines - a[1].lines)
|
|
494
|
+
.slice(0, 10);
|
|
495
|
+
const langTable = new Table({
|
|
496
|
+
head: [chalk.cyan('Language'), chalk.cyan('Sessions'), chalk.cyan('Tokens'), chalk.cyan('Lines'), chalk.cyan('Created'), chalk.cyan('Modified'), chalk.cyan('Share')],
|
|
497
|
+
style: {
|
|
498
|
+
head: [],
|
|
499
|
+
border: ['grey']
|
|
500
|
+
},
|
|
501
|
+
colWidths: [18, 12, 16, 10, 11, 12, 9]
|
|
502
|
+
});
|
|
503
|
+
for (const [lang, langStats] of sortedLangs) {
|
|
504
|
+
const langPercentage = stats.linesGenerated > 0 ? ((langStats.lines / stats.linesGenerated) * 100).toFixed(1) : '0.0';
|
|
505
|
+
const sessionCount = langStats.sessionIds.size;
|
|
506
|
+
langTable.push([
|
|
507
|
+
chalk.white(lang),
|
|
508
|
+
chalk.white(sessionCount.toString()),
|
|
509
|
+
chalk.white(Math.round(langStats.tokens).toLocaleString()),
|
|
510
|
+
chalk.white(langStats.lines.toLocaleString()),
|
|
511
|
+
chalk.white(langStats.filesCreated.toString()),
|
|
512
|
+
chalk.white(langStats.filesModified.toString()),
|
|
513
|
+
chalk.white(`${langPercentage}%`)
|
|
514
|
+
]);
|
|
515
|
+
}
|
|
516
|
+
console.log(' ' + langTable.toString().split('\n').join('\n '));
|
|
517
|
+
}
|
|
518
|
+
// Show format breakdown for this project
|
|
519
|
+
if (Object.keys(stats.formatStats).length > 0) {
|
|
520
|
+
console.log(chalk.white('\n By Format:\n'));
|
|
521
|
+
const sortedFormats = Object.entries(stats.formatStats)
|
|
522
|
+
.sort((a, b) => b[1].lines - a[1].lines)
|
|
523
|
+
.slice(0, 10);
|
|
524
|
+
const formatTable = new Table({
|
|
525
|
+
head: [chalk.cyan('Format'), chalk.cyan('Sessions'), chalk.cyan('Tokens'), chalk.cyan('Lines'), chalk.cyan('Created'), chalk.cyan('Modified'), chalk.cyan('Share')],
|
|
526
|
+
style: {
|
|
527
|
+
head: [],
|
|
528
|
+
border: ['grey']
|
|
529
|
+
},
|
|
530
|
+
colWidths: [18, 12, 16, 10, 11, 12, 9]
|
|
531
|
+
});
|
|
532
|
+
for (const [format, formatStats] of sortedFormats) {
|
|
533
|
+
const formatPercentage = stats.linesGenerated > 0 ? ((formatStats.lines / stats.linesGenerated) * 100).toFixed(1) : '0.0';
|
|
534
|
+
const sessionCount = formatStats.sessionIds.size;
|
|
535
|
+
formatTable.push([
|
|
536
|
+
chalk.white(format),
|
|
537
|
+
chalk.white(sessionCount.toString()),
|
|
538
|
+
chalk.white(Math.round(formatStats.tokens).toLocaleString()),
|
|
539
|
+
chalk.white(formatStats.lines.toLocaleString()),
|
|
540
|
+
chalk.white(formatStats.filesCreated.toString()),
|
|
541
|
+
chalk.white(formatStats.filesModified.toString()),
|
|
542
|
+
chalk.white(`${formatPercentage}%`)
|
|
543
|
+
]);
|
|
544
|
+
}
|
|
545
|
+
console.log(' ' + formatTable.toString().split('\n').join('\n '));
|
|
546
|
+
}
|
|
547
|
+
console.log();
|
|
294
548
|
}
|
|
295
|
-
]);
|
|
296
|
-
if (!confirm) {
|
|
297
|
-
console.log(chalk.yellow('\nDeletion cancelled.\n'));
|
|
298
|
-
return;
|
|
299
549
|
}
|
|
300
550
|
}
|
|
301
|
-
// Delete files
|
|
302
|
-
const deletedFiles = await clearOldAnalytics(days);
|
|
303
|
-
logger.success(`Deleted ${deletedFiles.length} file(s)`);
|
|
304
|
-
console.log(chalk.white(`Freed ${formatFileSize(totalSize)}`));
|
|
305
|
-
console.log();
|
|
306
551
|
}
|
|
307
552
|
catch (error) {
|
|
308
|
-
logger.error('Failed to
|
|
553
|
+
logger.error('Failed to aggregate analytics:', error);
|
|
309
554
|
process.exit(1);
|
|
310
555
|
}
|
|
311
556
|
});
|
|
312
557
|
return command;
|
|
313
558
|
}
|
|
314
559
|
// Helper functions
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
560
|
+
async function updateAnalyticsEnabled(enabled) {
|
|
561
|
+
// Load current multi-provider config
|
|
562
|
+
const config = await ConfigLoader.loadMultiProviderConfig();
|
|
563
|
+
// Update or create analytics config
|
|
564
|
+
if (!config.analytics) {
|
|
565
|
+
config.analytics = {
|
|
566
|
+
enabled,
|
|
567
|
+
target: 'local',
|
|
568
|
+
localPath: '~/.codemie/analytics',
|
|
569
|
+
flushInterval: 5000,
|
|
570
|
+
maxBufferSize: 100
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
config.analytics.enabled = enabled;
|
|
575
|
+
}
|
|
576
|
+
// Save updated config
|
|
577
|
+
await ConfigLoader.saveMultiProviderConfig(config);
|
|
318
578
|
}
|
|
319
579
|
//# sourceMappingURL=analytics.js.map
|