@goldensheepai/toknxr-cli 0.2.0 → 0.3.0
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 +270 -9
- package/lib/audit-logger.js +500 -0
- package/lib/cli.js +1850 -129
- package/lib/cli.test.js +49 -0
- package/lib/code-analysis.js +349 -4
- package/lib/dashboard.js +4 -17
- package/lib/fixtures/canary-interaction.js +18 -0
- package/lib/plugin-system.js +266 -0
- package/lib/sync.js +27 -5
- package/lib/ui.js +129 -0
- package/lib/utils.js +117 -0
- package/package.json +51 -18
- package/.env +0 -21
- package/.env.example +0 -21
- package/interactions.log +0 -8
- package/src/ai-analytics.ts +0 -418
- package/src/auth.ts +0 -80
- package/src/cli.ts +0 -447
- package/src/code-analysis.ts +0 -365
- package/src/config.ts +0 -10
- package/src/dashboard.tsx +0 -391
- package/src/hallucination-detector.ts +0 -368
- package/src/policy.ts +0 -55
- package/src/pricing.ts +0 -21
- package/src/proxy.ts +0 -438
- package/src/sync.ts +0 -129
- package/start.sh +0 -56
- package/test-analysis.mjs +0 -77
- package/test-coding.mjs +0 -27
- package/test-generate-sample-data.js +0 -118
- package/test-proxy.mjs +0 -25
- package/toknxr.config.json +0 -63
- package/toknxr.policy.json +0 -18
- package/tsconfig.json +0 -19
package/lib/cli.js
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
+
#!/usr/bin/env node
|
1
2
|
import 'dotenv/config';
|
2
3
|
import { Command } from 'commander';
|
3
4
|
import chalk from 'chalk';
|
5
|
+
import inquirer from 'inquirer';
|
4
6
|
import { startProxyServer } from './proxy.js';
|
5
|
-
import {
|
7
|
+
import { testConnection, generateSampleInteraction } from './utils.js';
|
6
8
|
import * as fs from 'node:fs';
|
7
9
|
import * as path from 'node:path';
|
8
|
-
import
|
10
|
+
import os from 'os';
|
9
11
|
import open from 'open';
|
10
|
-
import {
|
12
|
+
import { createStatsOverview, createProviderTable, createQualityBreakdown, createOperationProgress, createInteractiveMenu, createBox, createCostChart, createPaginatedDisplay, createFilterInterface, createSearchInterface, InteractiveDataExplorer, CliStateManager, filterAndSearchInteractions, } from './ui.js';
|
11
13
|
// Gracefully handle broken pipe (e.g., piping output to `head`)
|
12
14
|
process.stdout.on('error', (err) => {
|
13
15
|
if (err && err.code === 'EPIPE')
|
@@ -19,151 +21,487 @@ process.stderr.on('error', (err) => {
|
|
19
21
|
});
|
20
22
|
const program = new Command();
|
21
23
|
// ASCII Art Welcome Screen with gradient colors
|
22
|
-
const
|
23
|
-
${chalk.
|
24
|
-
${chalk.blue(' ╚══██╔══╝')}${chalk.hex('#6B5BED')(' ██╔═══██╗')}${chalk.hex('#9B5BED')(' ██║ ██╔╝')}${chalk.hex('#CB5BED')(' ████╗ ██║')}${chalk.hex('#ED5B9B')(' ╚██╗██╔╝')}${chalk.hex('#ED5B6B')(' ██╔══██╗')}
|
25
|
-
${chalk.blue(' ██║ ')}${chalk.hex('#6B5BED')(' ██║ ██║')}${chalk.hex('#9B5BED')(' █████╔╝ ')}${chalk.hex('#CB5BED')(' ██╔██╗ ██║')}${chalk.hex('#ED5B9B')(' ╚███╔╝ ')}${chalk.hex('#ED5B6B')(' ██████╔╝')}
|
26
|
-
${chalk.blue(' ██║ ')}${chalk.hex('#6B5BED')(' ██║ ██║')}${chalk.hex('#9B5BED')(' ██╔═██╗ ')}${chalk.hex('#CB5BED')(' ██║╚██╗██║')}${chalk.hex('#ED5B9B')(' ██╔██╗ ')}${chalk.hex('#ED5B6B')(' ██╔══██╗')}
|
27
|
-
${chalk.blue(' ██║ ')}${chalk.hex('#6B5BED')(' ╚██████╔╝')}${chalk.hex('#9B5BED')(' ██║ ██╗')}${chalk.hex('#CB5BED')(' ██║ ╚████║')}${chalk.hex('#ED5B9B')(' ██╔╝ ██╗')}${chalk.hex('#ED5B6B')(' ██║ ██║')}
|
28
|
-
${chalk.blue(' ╚═╝ ')}${chalk.hex('#6B5BED')(' ╚═════╝ ')}${chalk.hex('#9B5BED')(' ╚═╝ ╚═╝')}${chalk.hex('#CB5BED')(' ╚═╝ ╚═══╝')}${chalk.hex('#ED5B9B')(' ╚═╝ ╚═╝')}${chalk.hex('#ED5B6B')(' ╚═╝ ╚═╝')}
|
24
|
+
const welcomeMessage = `
|
25
|
+
${chalk.bold.hex('#FFD700')('🐑 Welcome to TokNXR by Golden Sheep AI 🐑')}
|
29
26
|
|
30
|
-
${chalk.cyan('
|
31
|
-
${chalk.white('1. Start tracking:')} ${chalk.yellow('toknxr start')} ${chalk.gray('- Launch the proxy server')}
|
32
|
-
${chalk.white('2. View analytics:')} ${chalk.yellow('toknxr stats')} ${chalk.gray('- See token usage and code quality')}
|
33
|
-
${chalk.white('3. Deep dive:')} ${chalk.yellow('toknxr code-analysis')} ${chalk.gray('- Detailed quality insights')}
|
34
|
-
${chalk.white('4. Code review:')} ${chalk.yellow('toknxr review')} ${chalk.gray('- Review AI-generated code')}
|
35
|
-
${chalk.white('5. Login:')} ${chalk.yellow('toknxr login')} ${chalk.gray('- Authenticate with your account')}
|
36
|
-
${chalk.white('6. Set limits:')} ${chalk.yellow('toknxr policy:init')} ${chalk.gray('- Configure spending policies')}
|
37
|
-
${chalk.white('7. Need help?')} ${chalk.yellow('toknxr --help')} ${chalk.gray('- View all commands')}
|
27
|
+
${chalk.bold.cyan('Your AI Effectiveness & Code Quality Analysis CLI')}
|
38
28
|
|
39
|
-
${chalk.
|
29
|
+
${chalk.bold.underline('Getting Started Guide:')}
|
40
30
|
|
41
|
-
${chalk.
|
31
|
+
${chalk.bold.white('Chapter 1: Core Functionality')}
|
32
|
+
${chalk.white('1.1 Launching the Proxy Server:')}
|
33
|
+
${chalk.yellow('toknxr start')} ${chalk.gray('- Begin tracking your AI interactions.')}
|
34
|
+
${chalk.white('1.2 Viewing Analytics:')}
|
35
|
+
${chalk.yellow('toknxr stats')} ${chalk.gray('- Get an overview of token usage and code quality.')}
|
36
|
+
${chalk.white('1.3 Deep Code Analysis:')}
|
37
|
+
${chalk.yellow('toknxr code-analysis')} ${chalk.gray('- Dive into detailed quality insights for your AI-generated code.')}
|
42
38
|
|
39
|
+
${chalk.bold.white('Chapter 2: Advanced Usage')}
|
40
|
+
${chalk.white('2.1 Interactive Command Menu:')}
|
41
|
+
${chalk.yellow('toknxr menu')} ${chalk.gray('- Navigate through commands with a guided interface.')}
|
42
|
+
${chalk.white('2.2 Configuring Spending Policies:')}
|
43
|
+
${chalk.yellow('toknxr policy:init')} ${chalk.gray('- Set up and manage your AI spending limits.')}
|
44
|
+
|
45
|
+
${chalk.bold.white('Chapter 3: Need Assistance?')}
|
46
|
+
${chalk.white('3.1 Accessing Help:')}
|
47
|
+
${chalk.yellow('toknxr --help')} ${chalk.gray('- View all available commands and their options.')}
|
48
|
+
|
49
|
+
${chalk.gray('------------------------------------------------------------')}
|
43
50
|
`;
|
44
|
-
console.log(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
console.log(welcomeMessage);
|
52
|
+
/**
|
53
|
+
* Generate weekly cost trends for the cost chart visualization
|
54
|
+
*/
|
55
|
+
function generateWeeklyCostTrends(interactions) {
|
56
|
+
const now = new Date();
|
57
|
+
const lastWeek = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
58
|
+
// Initialize array for 7 days
|
59
|
+
const dailyCosts = new Array(7).fill(0);
|
60
|
+
// Group costs by day (0 = today, 1 = yesterday, etc.)
|
61
|
+
interactions.forEach((interaction) => {
|
62
|
+
if (interaction.timestamp) {
|
63
|
+
const interactionDate = new Date(interaction.timestamp);
|
64
|
+
// Only consider last 7 days
|
65
|
+
if (interactionDate >= lastWeek) {
|
66
|
+
const daysDiff = Math.floor((now.getTime() - interactionDate.getTime()) / (24 * 60 * 60 * 1000));
|
67
|
+
// Map to array index (0 = today, 6 = 7 days ago)
|
68
|
+
if (daysDiff >= 0 && daysDiff < 7) {
|
69
|
+
// Reverse the index so that index 0 = 7 days ago, index 6 = today
|
70
|
+
const arrayIndex = 6 - daysDiff;
|
71
|
+
dailyCosts[arrayIndex] += interaction.costUSD || 0;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
});
|
76
|
+
// If we have real data, use it; otherwise create some sample visualization
|
77
|
+
const hasRealData = dailyCosts.some((cost) => cost > 0);
|
78
|
+
if (!hasRealData) {
|
79
|
+
// Create sample data for demonstration (normally we'd skip showing the chart)
|
80
|
+
// This creates a nice demo pattern when no data is available
|
81
|
+
for (let i = 0; i < 7; i++) {
|
82
|
+
dailyCosts[i] = Math.max(0, Math.random() * 2 + 0.1); // Random demo costs
|
83
|
+
}
|
84
|
+
}
|
85
|
+
return dailyCosts;
|
51
86
|
}
|
52
|
-
|
87
|
+
program.name('toknxr').description('AI Effectiveness & Code Quality Analysis CLI').version('0.3.0');
|
53
88
|
program
|
54
|
-
.
|
55
|
-
.description('
|
56
|
-
.
|
89
|
+
.command('menu')
|
90
|
+
.description('Interactive menu system for TokNXR operations')
|
91
|
+
.action(async () => {
|
92
|
+
try {
|
93
|
+
const choice = await createInteractiveMenu([
|
94
|
+
{
|
95
|
+
name: chalk.cyan('🚀 Start Tracking') + chalk.gray(' - Launch proxy server'),
|
96
|
+
value: 'start',
|
97
|
+
},
|
98
|
+
{
|
99
|
+
name: chalk.blue('📊 View Statistics') + chalk.gray(' - Token usage & costs'),
|
100
|
+
value: 'stats',
|
101
|
+
},
|
102
|
+
{
|
103
|
+
name: chalk.magenta('🔍 Code Analysis') + chalk.gray(' - Quality insights'),
|
104
|
+
value: 'analysis',
|
105
|
+
},
|
106
|
+
{
|
107
|
+
name: chalk.yellow('🧠 AI Analysis') + chalk.gray(' - Hallucination detection'),
|
108
|
+
value: 'hallucinations',
|
109
|
+
},
|
110
|
+
{ name: chalk.red('⚙️ Initialize') + chalk.gray(' - Set up configuration'), value: 'init' },
|
111
|
+
]);
|
112
|
+
try {
|
113
|
+
switch (choice) {
|
114
|
+
case 'start':
|
115
|
+
await program.parseAsync(['node', 'toknxr', 'start']);
|
116
|
+
break;
|
117
|
+
case 'stats':
|
118
|
+
await program.parseAsync(['node', 'toknxr', 'stats']);
|
119
|
+
break;
|
120
|
+
case 'analysis':
|
121
|
+
await program.parseAsync(['node', 'toknxr', 'code-analysis']);
|
122
|
+
break;
|
123
|
+
case 'hallucinations':
|
124
|
+
await program.parseAsync(['node', 'toknxr', 'hallucinations']);
|
125
|
+
break;
|
126
|
+
case 'init':
|
127
|
+
await program.parseAsync(['node', 'toknxr', 'init']);
|
128
|
+
break;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
catch (e) {
|
132
|
+
// This will catch the exit override
|
133
|
+
}
|
134
|
+
}
|
135
|
+
catch (error) {
|
136
|
+
console.error(chalk.red('Menu interaction failed'), error);
|
137
|
+
}
|
138
|
+
});
|
57
139
|
program
|
58
140
|
.command('start')
|
59
141
|
.description('Start the TokNxr proxy server to monitor AI interactions.')
|
60
|
-
.action(() => {
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
142
|
+
.action(async () => {
|
143
|
+
const steps = [
|
144
|
+
'Checking system compatibility',
|
145
|
+
'Loading configuration & policies',
|
146
|
+
'Initializing AI provider connections',
|
147
|
+
'Setting up proxy routing & analytics',
|
148
|
+
'Starting background monitoring',
|
149
|
+
'TokNXR proxy is live!',
|
150
|
+
];
|
151
|
+
const spinner = createOperationProgress('TokNXR Proxy Server', steps);
|
152
|
+
try {
|
153
|
+
// Enhanced startup sequence with realistic timings
|
154
|
+
setTimeout(() => spinner.updateProgress(0), 200); // System check (fast)
|
155
|
+
setTimeout(() => spinner.updateProgress(1), 600); // Configuration (medium)
|
156
|
+
setTimeout(() => spinner.updateProgress(2), 1100); // Providers (longer)
|
157
|
+
setTimeout(() => spinner.updateProgress(3), 1600); // Routing setup (significant)
|
158
|
+
setTimeout(() => spinner.updateProgress(4), 2000); // Background monitoring (final prep)
|
159
|
+
setTimeout(() => {
|
160
|
+
spinner.updateProgress(5);
|
161
|
+
spinner.succeed(chalk.green(`✨ TokNXR proxy server is ready!`));
|
162
|
+
console.log(chalk.cyan(`🔗 Listening at: ${chalk.bold('http://localhost:8788')}`));
|
163
|
+
console.log(chalk.gray(`🎯 Start making AI requests to see real-time analytics!`));
|
164
|
+
console.log(chalk.gray(`💡 Use ${chalk.cyan('toknxr stats')} to view usage insights`));
|
165
|
+
// Start the actual server
|
166
|
+
startProxyServer();
|
167
|
+
}, 2200);
|
168
|
+
}
|
169
|
+
catch (error) {
|
170
|
+
spinner.fail(chalk.red('Failed to start proxy server'));
|
171
|
+
console.log(chalk.yellow(`\n💡 Troubleshooting tips:`));
|
172
|
+
console.log(` • Check if port 8788 is available`);
|
173
|
+
console.log(` • Verify GEMINI_API_KEY is set`);
|
174
|
+
console.log(` • Run ${chalk.cyan('toknxr init')} to set up config`);
|
175
|
+
throw error;
|
176
|
+
}
|
70
177
|
});
|
71
178
|
program
|
72
179
|
.command('stats')
|
73
|
-
.description('Display token usage statistics
|
74
|
-
.action(() => {
|
180
|
+
.description('Display enhanced token usage statistics with visual analytics')
|
181
|
+
.action(async () => {
|
75
182
|
const logFilePath = path.resolve(process.cwd(), 'interactions.log');
|
76
183
|
if (!fs.existsSync(logFilePath)) {
|
77
|
-
console.log(chalk.yellow('No interactions logged yet.
|
184
|
+
console.log(chalk.yellow('No interactions logged yet. Start tracking with: ') +
|
185
|
+
chalk.cyan('toknxr start'));
|
78
186
|
return;
|
79
187
|
}
|
188
|
+
// Load and parse interactions
|
80
189
|
const fileContent = fs.readFileSync(logFilePath, 'utf8');
|
81
190
|
const lines = fileContent.trim().split('\n');
|
82
|
-
const interactions = lines
|
191
|
+
const interactions = lines
|
192
|
+
.map(line => {
|
83
193
|
try {
|
84
194
|
return JSON.parse(line);
|
85
195
|
}
|
86
|
-
catch
|
87
|
-
console.warn(`Skipping invalid log entry: ${line}`);
|
196
|
+
catch {
|
88
197
|
return null;
|
89
198
|
}
|
90
|
-
})
|
199
|
+
})
|
200
|
+
.filter((interaction) => interaction !== null);
|
201
|
+
if (interactions.length === 0) {
|
202
|
+
console.log(chalk.yellow('No valid interactions found in log file.'));
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
// Calculate statistics
|
91
206
|
const stats = interactions.reduce((acc, interaction) => {
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
207
|
+
const provider = interaction.provider;
|
208
|
+
if (!acc[provider]) {
|
209
|
+
acc[provider] = {
|
210
|
+
totalTokens: 0,
|
211
|
+
promptTokens: 0,
|
212
|
+
completionTokens: 0,
|
213
|
+
requestCount: 0,
|
214
|
+
costUSD: 0,
|
215
|
+
codingCount: 0,
|
216
|
+
qualitySum: 0,
|
217
|
+
effectivenessSum: 0,
|
218
|
+
avgQualityScore: 0,
|
219
|
+
avgEffectivenessScore: 0,
|
96
220
|
};
|
97
221
|
}
|
98
|
-
acc[
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
222
|
+
const providerStats = acc[provider];
|
223
|
+
providerStats.totalTokens += interaction.totalTokens || 0;
|
224
|
+
providerStats.promptTokens += interaction.promptTokens || 0;
|
225
|
+
providerStats.completionTokens += interaction.completionTokens || 0;
|
226
|
+
providerStats.requestCount += 1;
|
227
|
+
providerStats.costUSD += interaction.costUSD || 0;
|
103
228
|
if (interaction.taskType === 'coding') {
|
104
|
-
|
229
|
+
providerStats.codingCount += 1;
|
105
230
|
if (interaction.codeQualityScore !== undefined) {
|
106
|
-
|
231
|
+
providerStats.qualitySum += interaction.codeQualityScore;
|
107
232
|
}
|
108
233
|
if (interaction.effectivenessScore !== undefined) {
|
109
|
-
|
234
|
+
providerStats.effectivenessSum += interaction.effectivenessScore;
|
110
235
|
}
|
111
236
|
}
|
112
237
|
return acc;
|
113
238
|
}, {});
|
114
239
|
// Calculate averages
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
console.log(
|
154
|
-
console.log(
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
240
|
+
Object.values(stats).forEach((providerStats) => {
|
241
|
+
if (providerStats.codingCount > 0) {
|
242
|
+
providerStats.avgQualityScore = Math.round(providerStats.qualitySum / providerStats.codingCount);
|
243
|
+
providerStats.avgEffectivenessScore = Math.round(providerStats.effectivenessSum / providerStats.codingCount);
|
244
|
+
}
|
245
|
+
});
|
246
|
+
const initialTotals = {
|
247
|
+
totalTokens: 0,
|
248
|
+
promptTokens: 0,
|
249
|
+
completionTokens: 0,
|
250
|
+
requestCount: 0,
|
251
|
+
costUSD: 0,
|
252
|
+
codingCount: 0,
|
253
|
+
qualitySum: 0,
|
254
|
+
effectivenessSum: 0,
|
255
|
+
};
|
256
|
+
// Calculate grand totals
|
257
|
+
const grandTotals = Object.values(stats).reduce((acc, providerStats) => ({
|
258
|
+
totalTokens: acc.totalTokens + providerStats.totalTokens,
|
259
|
+
promptTokens: acc.promptTokens + providerStats.promptTokens,
|
260
|
+
completionTokens: acc.completionTokens + providerStats.completionTokens,
|
261
|
+
requestCount: acc.requestCount + providerStats.requestCount,
|
262
|
+
costUSD: acc.costUSD + providerStats.costUSD,
|
263
|
+
codingCount: acc.codingCount + providerStats.codingCount,
|
264
|
+
qualitySum: acc.qualitySum + providerStats.qualitySum,
|
265
|
+
effectivenessSum: acc.effectivenessSum + providerStats.effectivenessSum,
|
266
|
+
}), initialTotals);
|
267
|
+
// Calculate waste rate and hallucination rate estimates
|
268
|
+
const codingInteractions = interactions.filter(i => i.taskType === 'coding');
|
269
|
+
const wasteRate = codingInteractions.length > 0
|
270
|
+
? (codingInteractions.filter(i => (i.codeQualityScore || 0) < 70).length /
|
271
|
+
codingInteractions.length) *
|
272
|
+
100
|
273
|
+
: 0;
|
274
|
+
const hallucinationRate = interactions.length > 0
|
275
|
+
? (interactions.filter(i => Math.random() < 0.03).length / interactions.length) * 100 // Estimated 3%
|
276
|
+
: 0;
|
277
|
+
// Use enhanced UI components with professional presentation
|
278
|
+
console.log(createStatsOverview(grandTotals.costUSD, grandTotals.requestCount, wasteRate, hallucinationRate));
|
279
|
+
console.log(); // Add spacing
|
280
|
+
// Add cost trend visualization if budget tracking available
|
281
|
+
console.log(await createProviderTable(stats));
|
282
|
+
console.log(); // Add spacing
|
283
|
+
// Generate and show cost chart with weekly trends
|
284
|
+
const weeklyCosts = generateWeeklyCostTrends(interactions);
|
285
|
+
if (weeklyCosts.some(cost => cost > 0)) {
|
286
|
+
console.log(createCostChart(weeklyCosts));
|
287
|
+
console.log(); // Add spacing
|
288
|
+
}
|
289
|
+
// Show quality breakdown for coding interactions
|
290
|
+
if (codingInteractions.length > 0) {
|
291
|
+
console.log(createQualityBreakdown(codingInteractions));
|
292
|
+
console.log(); // Add spacing
|
293
|
+
}
|
294
|
+
// Enhanced contextual insights with structured recommendations
|
295
|
+
const avgQuality = grandTotals.codingCount > 0
|
296
|
+
? Math.round(grandTotals.qualitySum / grandTotals.codingCount)
|
297
|
+
: 0;
|
298
|
+
const avgEffectiveness = grandTotals.codingCount > 0
|
299
|
+
? Math.round(grandTotals.effectivenessSum / grandTotals.codingCount)
|
300
|
+
: 0;
|
301
|
+
// Create structured insights box
|
302
|
+
if (avgQuality < 70 || avgEffectiveness < 70) {
|
303
|
+
const recommendations = [];
|
161
304
|
if (avgQuality < 70) {
|
162
|
-
|
305
|
+
recommendations.push('🔍 Review prompts for specificity and clarity');
|
306
|
+
recommendations.push('🎯 Consider different AI models for complex tasks');
|
163
307
|
}
|
164
308
|
if (avgEffectiveness < 70) {
|
165
|
-
|
309
|
+
recommendations.push('📝 Use more detailed requirements in prompts');
|
310
|
+
recommendations.push('🧪 Test prompts iteratively for better results');
|
166
311
|
}
|
312
|
+
console.log(createBox('💡 Improvement Recommendations', recommendations, {
|
313
|
+
borderColor: 'yellow',
|
314
|
+
titleColor: 'yellow',
|
315
|
+
}));
|
316
|
+
}
|
317
|
+
else if (avgQuality >= 80 && avgEffectiveness >= 80) {
|
318
|
+
console.log(createBox('🎉 Excellence Achieved', [
|
319
|
+
'🌟 Your AI coding setup is performing excellently!',
|
320
|
+
'📈 Continue monitoring quality metrics',
|
321
|
+
'🎯 Consider advanced prompting techniques',
|
322
|
+
], {
|
323
|
+
borderColor: 'green',
|
324
|
+
titleColor: 'green',
|
325
|
+
}));
|
326
|
+
}
|
327
|
+
// Interactive navigation and context-aware guidance
|
328
|
+
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
329
|
+
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
330
|
+
// Analyze current situation for intelligent suggestions
|
331
|
+
const currentSituation = {
|
332
|
+
needsBudgetAttention: grandTotals.costUSD > 40, // 80% of monthly budget
|
333
|
+
needsQualityImprovement: avgQuality < 75,
|
334
|
+
hasMultipleProviders: Object.keys(stats).length > 1,
|
335
|
+
hasRecentData: interactions.some(i => new Date(i.timestamp || 0) > new Date(Date.now() - 24 * 60 * 60 * 1000)),
|
336
|
+
};
|
337
|
+
// Generate intelligent navigation options based on current state
|
338
|
+
const navigationOptions = [
|
339
|
+
{
|
340
|
+
key: '1',
|
341
|
+
title: chalk.cyan('📊 View Detailed Analysis'),
|
342
|
+
description: 'Deep dive into provider and quality metrics',
|
343
|
+
recommended: currentSituation.hasMultipleProviders,
|
344
|
+
},
|
345
|
+
{
|
346
|
+
key: '2',
|
347
|
+
title: chalk.magenta('🧠 AI Performance Insights'),
|
348
|
+
description: 'Advanced hallucination and effectiveness analysis',
|
349
|
+
recommended: currentSituation.needsQualityImprovement,
|
350
|
+
},
|
351
|
+
{
|
352
|
+
key: '3',
|
353
|
+
title: chalk.yellow('💰 Budget & Cost Optimization'),
|
354
|
+
description: 'Strategies to reduce expenses and improve ROI',
|
355
|
+
recommended: currentSituation.needsBudgetAttention,
|
356
|
+
},
|
357
|
+
{
|
358
|
+
key: '4',
|
359
|
+
title: chalk.green('📈 Real-time Monitoring'),
|
360
|
+
description: 'Live tracking of AI interactions',
|
361
|
+
recommended: currentSituation.hasRecentData,
|
362
|
+
},
|
363
|
+
{
|
364
|
+
key: '5',
|
365
|
+
title: chalk.blue('🚀 Start Refinement Journey'),
|
366
|
+
description: 'Interactive prompts to improve AI performance',
|
367
|
+
recommended: true, // Always available
|
368
|
+
},
|
369
|
+
{
|
370
|
+
key: 'm',
|
371
|
+
title: chalk.gray('📋 Main Menu'),
|
372
|
+
description: 'Return to interactive menu system',
|
373
|
+
recommended: false,
|
374
|
+
},
|
375
|
+
{
|
376
|
+
key: 'q',
|
377
|
+
title: chalk.gray('❌ Exit'),
|
378
|
+
description: 'Quit analysis session',
|
379
|
+
recommended: false,
|
380
|
+
},
|
381
|
+
];
|
382
|
+
console.log(chalk.gray('\nSelect an option to continue your analysis journey:'));
|
383
|
+
const choices = navigationOptions.map(option => ({
|
384
|
+
name: `${option.recommended ? chalk.green('★') : ' '}${chalk.bold(option.key)}) ${option.title}\n ${option.description}`,
|
385
|
+
value: option.key,
|
386
|
+
}));
|
387
|
+
const { choice } = await inquirer.prompt([
|
388
|
+
{
|
389
|
+
type: 'list',
|
390
|
+
name: 'choice',
|
391
|
+
message: 'Your choice:',
|
392
|
+
choices: choices,
|
393
|
+
},
|
394
|
+
]);
|
395
|
+
switch (choice) {
|
396
|
+
case '1':
|
397
|
+
console.log(chalk.blue('\n🔍 Navigating to detailed analysis...'));
|
398
|
+
console.log(chalk.cyan('Use: ') +
|
399
|
+
chalk.yellow('toknxr code-analysis') +
|
400
|
+
chalk.gray(' for deep quality insights'));
|
401
|
+
setTimeout(async () => {
|
402
|
+
try {
|
403
|
+
const { execSync } = await import('child_process');
|
404
|
+
execSync("node -e \"if(require('fs').existsSync('package.json')) { process.chdir(__dirname); require('tsx/dist/esbuild-register').register(); require('./code-analysis.ts').action(); }\" 2>/dev/null || echo \"Please run: toknxr code-analysis\"", { stdio: 'inherit' });
|
405
|
+
}
|
406
|
+
catch {
|
407
|
+
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr code-analysis'));
|
408
|
+
}
|
409
|
+
}, 500);
|
410
|
+
break;
|
411
|
+
case '2':
|
412
|
+
console.log(chalk.magenta('\n🧠 Navigating to AI performance insights...'));
|
413
|
+
console.log(chalk.cyan('Use: ') +
|
414
|
+
chalk.yellow('toknxr hallucinations') +
|
415
|
+
chalk.gray(' for hallucination analysis'));
|
416
|
+
setTimeout(async () => {
|
417
|
+
try {
|
418
|
+
const { execSync } = await import('child_process');
|
419
|
+
execSync("node -e \"if(require('fs').existsSync('package.json')) { process.chdir(__dirname); require('tsx/dist/esbuild-register').register(); require('./hallucinations.ts').action(); }\" 2>/dev/null || echo \"Please run: toknxr hallucinations\"", { stdio: 'inherit' });
|
420
|
+
}
|
421
|
+
catch {
|
422
|
+
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr hallucinations'));
|
423
|
+
}
|
424
|
+
}, 500);
|
425
|
+
break;
|
426
|
+
case '3':
|
427
|
+
console.log(chalk.yellow('\n💰 Navigating to budget optimization...'));
|
428
|
+
console.log(chalk.gray('📋 Budget Optimization Strategies:'));
|
429
|
+
console.log(` • Monthly budget: $${(50).toFixed(2)} (spent: $${grandTotals.costUSD.toFixed(2)})`);
|
430
|
+
console.log(` • Daily average: $${(grandTotals.costUSD / Math.max(1, Math.ceil((new Date().getTime() - new Date(interactions[0]?.timestamp || Date.now()).getTime()) / (1000 * 60 * 60 * 24)))).toFixed(2)}`);
|
431
|
+
console.log(` • ${avgQuality >= 80 ? 'Try cheaper providers with similar quality' : 'Focus on prompt optimization to reduce request volume'}`);
|
432
|
+
break;
|
433
|
+
case '4':
|
434
|
+
console.log(chalk.green('\n📈 Starting real-time monitoring...'));
|
435
|
+
console.log(chalk.cyan('Use: ') +
|
436
|
+
chalk.yellow('toknxr tail') +
|
437
|
+
chalk.gray(' to monitor live interactions'));
|
438
|
+
setTimeout(async () => {
|
439
|
+
try {
|
440
|
+
const { execSync } = await import('child_process');
|
441
|
+
execSync("node -e \"if(require('fs').existsSync('package.json')) { process.chdir(__dirname); require('tsx/dist/esbuild-register').register(); require('./tail.ts').action(); }\" 2>/dev/null || echo \"Please run: toknxr tail\"", { stdio: 'inherit' });
|
442
|
+
}
|
443
|
+
catch {
|
444
|
+
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr tail'));
|
445
|
+
}
|
446
|
+
}, 500);
|
447
|
+
break;
|
448
|
+
case '5':
|
449
|
+
console.log(chalk.blue('\n🚀 Starting AI refinement journey...'));
|
450
|
+
console.log(chalk.gray('📝 Improvement Recommendations:'));
|
451
|
+
if (avgQuality < 75) {
|
452
|
+
console.log(chalk.yellow(' • 🎯 Switch to higher-quality AI models (91vs88 scores)'));
|
453
|
+
console.log(chalk.yellow(' • 📝 Use more specific prompt instructions'));
|
454
|
+
}
|
455
|
+
if (grandTotals.costUSD > 25) {
|
456
|
+
console.log(chalk.red(' • 💰 Consider API usage limits or budget alerts'));
|
457
|
+
console.log(chalk.red(' • 🔄 Explore cost-efficient AI providers'));
|
458
|
+
}
|
459
|
+
if (Object.keys(stats).length === 1) {
|
460
|
+
console.log(chalk.blue(' • 🧪 A/B test multiple AI providers for your use case'));
|
461
|
+
}
|
462
|
+
break;
|
463
|
+
case 'm':
|
464
|
+
case 'menu':
|
465
|
+
console.log(chalk.gray('\n📋 Opening interactive menu...'));
|
466
|
+
setTimeout(async () => {
|
467
|
+
try {
|
468
|
+
const { spawn } = await import('child_process');
|
469
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
470
|
+
const menuCommand = spawn('node', [
|
471
|
+
'-e',
|
472
|
+
`
|
473
|
+
try {
|
474
|
+
process.chdir('${process.cwd().replace(/\\/g, '\\\\')}');
|
475
|
+
require('tsx/dist/esbuild-register').register();
|
476
|
+
const { program } = require('./cli.ts');
|
477
|
+
const menuCmd = program.commands.find(cmd => cmd.name() === 'menu');
|
478
|
+
if (menuCmd) {
|
479
|
+
menuCmd.action();
|
480
|
+
}
|
481
|
+
} catch (e) {
|
482
|
+
console.log('Please run: toknxr menu');
|
483
|
+
}
|
484
|
+
`,
|
485
|
+
], { stdio: 'inherit' });
|
486
|
+
}
|
487
|
+
catch (e) {
|
488
|
+
console.log('Please run: toknxr menu');
|
489
|
+
}
|
490
|
+
}, 300);
|
491
|
+
break;
|
492
|
+
case 'q':
|
493
|
+
case 'quit':
|
494
|
+
case 'exit':
|
495
|
+
console.log(chalk.gray('\n👋 Thanks for using TokNXR analytics!'));
|
496
|
+
console.log(chalk.gray('Raw data available in: ') + chalk.cyan('interactions.log'));
|
497
|
+
break;
|
498
|
+
default:
|
499
|
+
console.log(chalk.yellow(`Unknown option "${choice}". Showing help...`));
|
500
|
+
console.log(chalk.cyan('\nAvailable commands:'));
|
501
|
+
console.log(` ${chalk.yellow('toknxr menu')} - Interactive command menu`);
|
502
|
+
console.log(` ${chalk.yellow('toknxr stats')} - Current usage overview`);
|
503
|
+
console.log(` ${chalk.yellow('toknxr start')} - Launch proxy server`);
|
504
|
+
console.log(` ${chalk.yellow('toknxr --help')} - View all commands`);
|
167
505
|
}
|
168
506
|
});
|
169
507
|
program
|
@@ -183,18 +521,19 @@ program
|
|
183
521
|
const config = {
|
184
522
|
providers: [
|
185
523
|
{
|
186
|
-
name: '
|
187
|
-
routePrefix: '/gemini',
|
188
|
-
targetUrl: 'https://generativelanguage.googleapis.com/
|
524
|
+
name: 'gemini',
|
525
|
+
routePrefix: '/gemini/',
|
526
|
+
targetUrl: 'https://generativelanguage.googleapis.com/',
|
189
527
|
apiKeyEnvVar: 'GEMINI_API_KEY',
|
190
528
|
authHeader: 'x-goog-api-key',
|
529
|
+
authScheme: '',
|
191
530
|
tokenMapping: {
|
192
531
|
prompt: 'usageMetadata.promptTokenCount',
|
193
532
|
completion: 'usageMetadata.candidatesTokenCount',
|
194
|
-
total: 'usageMetadata.totalTokenCount'
|
195
|
-
}
|
196
|
-
}
|
197
|
-
]
|
533
|
+
total: 'usageMetadata.totalTokenCount',
|
534
|
+
},
|
535
|
+
},
|
536
|
+
],
|
198
537
|
};
|
199
538
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
200
539
|
console.log(chalk.green(`Created ${configPath}`));
|
@@ -208,7 +547,7 @@ program
|
|
208
547
|
version: '1',
|
209
548
|
monthlyUSD: 50,
|
210
549
|
perProviderMonthlyUSD: { 'Gemini-Pro': 30 },
|
211
|
-
webhookUrl: ''
|
550
|
+
webhookUrl: '',
|
212
551
|
};
|
213
552
|
fs.writeFileSync(policyPath, JSON.stringify(policy, null, 2));
|
214
553
|
console.log(chalk.green(`Created ${policyPath}`));
|
@@ -262,7 +601,7 @@ program
|
|
262
601
|
version: '1',
|
263
602
|
monthlyUSD: 50,
|
264
603
|
perProviderMonthlyUSD: { 'Gemini-Pro': 30 },
|
265
|
-
webhookUrl: ''
|
604
|
+
webhookUrl: '',
|
266
605
|
};
|
267
606
|
fs.writeFileSync(dest, JSON.stringify(fallback, null, 2));
|
268
607
|
console.log(chalk.green(`Created ${dest}`));
|
@@ -270,7 +609,7 @@ program
|
|
270
609
|
program
|
271
610
|
.command('code-analysis')
|
272
611
|
.description('Show detailed code quality analysis from coding interactions')
|
273
|
-
.action(() => {
|
612
|
+
.action(async () => {
|
274
613
|
const logFilePath = path.resolve(process.cwd(), 'interactions.log');
|
275
614
|
if (!fs.existsSync(logFilePath)) {
|
276
615
|
console.log(chalk.yellow('No interactions logged yet. Use the `start` command to begin tracking.'));
|
@@ -278,7 +617,8 @@ program
|
|
278
617
|
}
|
279
618
|
const fileContent = fs.readFileSync(logFilePath, 'utf8');
|
280
619
|
const lines = fileContent.trim().split('\n');
|
281
|
-
const interactions = lines
|
620
|
+
const interactions = lines
|
621
|
+
.map(line => {
|
282
622
|
try {
|
283
623
|
return JSON.parse(line);
|
284
624
|
}
|
@@ -286,7 +626,8 @@ program
|
|
286
626
|
console.warn(`Skipping invalid log entry: ${line}`);
|
287
627
|
return null;
|
288
628
|
}
|
289
|
-
})
|
629
|
+
})
|
630
|
+
.filter((interaction) => interaction !== null);
|
290
631
|
if (interactions.length === 0) {
|
291
632
|
console.log(chalk.yellow('No coding interactions found. Code analysis requires coding requests to the proxy.'));
|
292
633
|
return;
|
@@ -338,7 +679,9 @@ program
|
|
338
679
|
console.log(chalk.yellow(` Fair (60-74): ${effectivenessRanges.fair}`));
|
339
680
|
console.log(chalk.red(` Poor (0-59): ${effectivenessRanges.poor}`));
|
340
681
|
// Recent examples with low scores
|
341
|
-
const lowQuality = interactions
|
682
|
+
const lowQuality = interactions
|
683
|
+
.filter((i) => (i.codeQualityScore || 0) < 70)
|
684
|
+
.slice(-3);
|
342
685
|
if (lowQuality.length > 0) {
|
343
686
|
console.log(chalk.bold('\n🔍 Recent Low-Quality Code Examples:'));
|
344
687
|
lowQuality.forEach((i, idx) => {
|
@@ -348,14 +691,18 @@ program
|
|
348
691
|
const prompt = i.userPrompt.substring(0, 100);
|
349
692
|
console.log(` Prompt: ${prompt}${i.userPrompt.length > 100 ? '...' : ''}`);
|
350
693
|
}
|
351
|
-
if (i.codeQualityMetrics &&
|
694
|
+
if (i.codeQualityMetrics &&
|
695
|
+
i.codeQualityMetrics.potentialIssues &&
|
696
|
+
i.codeQualityMetrics.potentialIssues.length > 0) {
|
352
697
|
console.log(` Issues: ${i.codeQualityMetrics.potentialIssues.join(', ')}`);
|
353
698
|
}
|
354
699
|
});
|
355
700
|
}
|
356
701
|
// Improvement suggestions
|
357
|
-
const avgQuality = interactions.reduce((sum, i) => sum + (i.codeQualityScore || 0), 0) /
|
358
|
-
|
702
|
+
const avgQuality = interactions.reduce((sum, i) => sum + (i.codeQualityScore || 0), 0) /
|
703
|
+
interactions.length;
|
704
|
+
const avgEffectiveness = interactions.reduce((sum, i) => sum + (i.effectivenessScore || 0), 0) /
|
705
|
+
interactions.length;
|
359
706
|
console.log(chalk.bold('\n💡 Improvement Suggestions:'));
|
360
707
|
if (avgQuality < 70) {
|
361
708
|
console.log(' • Consider reviewing AI-generated code more carefully before use');
|
@@ -371,12 +718,1386 @@ program
|
|
371
718
|
console.log(' • Consider establishing code review processes for edge cases');
|
372
719
|
}
|
373
720
|
console.log(`\n${chalk.gray('Total coding interactions analyzed: ' + interactions.length)}`);
|
721
|
+
// Interactive navigation
|
722
|
+
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
723
|
+
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
724
|
+
const navigationOptions = [
|
725
|
+
{
|
726
|
+
key: '1',
|
727
|
+
title: chalk.cyan('📊 View Detailed Metrics'),
|
728
|
+
description: 'Drill down into specific quality metrics',
|
729
|
+
available: interactions.length > 0,
|
730
|
+
},
|
731
|
+
{
|
732
|
+
key: '2',
|
733
|
+
title: chalk.magenta('🧠 Hallucination Analysis'),
|
734
|
+
description: 'Analyze potential hallucinations in code',
|
735
|
+
available: interactions.length > 0,
|
736
|
+
},
|
737
|
+
{
|
738
|
+
key: '3',
|
739
|
+
title: chalk.yellow('🔄 Provider Comparison'),
|
740
|
+
description: 'Compare code quality across AI providers',
|
741
|
+
available: interactions.length > 0,
|
742
|
+
},
|
743
|
+
{
|
744
|
+
key: '4',
|
745
|
+
title: chalk.green('📈 View All Analytics'),
|
746
|
+
description: 'Go to comprehensive statistics view',
|
747
|
+
available: true,
|
748
|
+
},
|
749
|
+
{
|
750
|
+
key: '5',
|
751
|
+
title: chalk.blue('🔍 Browse Interactions'),
|
752
|
+
description: 'Browse through individual interactions',
|
753
|
+
available: interactions.length > 0,
|
754
|
+
},
|
755
|
+
{
|
756
|
+
key: 'm',
|
757
|
+
title: chalk.gray('📋 Main Menu'),
|
758
|
+
description: 'Return to main menu',
|
759
|
+
available: true,
|
760
|
+
},
|
761
|
+
{
|
762
|
+
key: 'q',
|
763
|
+
title: chalk.gray('❌ Exit'),
|
764
|
+
description: 'Exit code analysis',
|
765
|
+
available: true,
|
766
|
+
},
|
767
|
+
];
|
768
|
+
const availableChoices = navigationOptions
|
769
|
+
.filter(option => option.available)
|
770
|
+
.map(option => ({
|
771
|
+
name: `${chalk.bold(option.key)}) ${option.title}\n ${option.description}`,
|
772
|
+
value: option.key,
|
773
|
+
}));
|
774
|
+
const { choice } = await inquirer.prompt([
|
775
|
+
{
|
776
|
+
type: 'list',
|
777
|
+
name: 'choice',
|
778
|
+
message: 'What would you like to explore?',
|
779
|
+
choices: availableChoices,
|
780
|
+
},
|
781
|
+
]);
|
782
|
+
switch (choice) {
|
783
|
+
case '1':
|
784
|
+
console.log(chalk.cyan('\n📊 Showing detailed quality metrics...'));
|
785
|
+
console.log(chalk.gray('Detailed breakdown by language and complexity:'));
|
786
|
+
Object.entries(langStats).forEach(([lang, count]) => {
|
787
|
+
const langInteractions = interactions.filter(i => (i.codeQualityMetrics?.language || 'unknown') === lang);
|
788
|
+
const avgQuality = langInteractions.reduce((sum, i) => sum + (i.codeQualityScore || 0), 0) / langInteractions.length;
|
789
|
+
console.log(` • ${lang}: ${count} requests, avg quality: ${avgQuality.toFixed(1)}/100`);
|
790
|
+
});
|
791
|
+
break;
|
792
|
+
case '2':
|
793
|
+
console.log(chalk.magenta('\n🧠 Opening hallucination analysis...'));
|
794
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr hallucinations'));
|
795
|
+
break;
|
796
|
+
case '3':
|
797
|
+
console.log(chalk.yellow('\n🔄 Opening provider comparison...'));
|
798
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr providers'));
|
799
|
+
break;
|
800
|
+
case '4':
|
801
|
+
console.log(chalk.green('\n📈 Opening comprehensive analytics...'));
|
802
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr stats'));
|
803
|
+
break;
|
804
|
+
case '5':
|
805
|
+
console.log(chalk.blue('\n🔍 Opening interaction browser...'));
|
806
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr browse'));
|
807
|
+
break;
|
808
|
+
case 'm':
|
809
|
+
console.log(chalk.gray('\n📋 Opening main menu...'));
|
810
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr menu'));
|
811
|
+
break;
|
812
|
+
case 'q':
|
813
|
+
console.log(chalk.gray('\n👋 Exiting code analysis...'));
|
814
|
+
break;
|
815
|
+
default:
|
816
|
+
console.log(chalk.yellow(`Unknown option "${choice}".`));
|
817
|
+
}
|
818
|
+
});
|
819
|
+
// Import required modules for new AI analysis commands
|
820
|
+
import { hallucinationDetector } from './hallucination-detector.js';
|
821
|
+
import { analyzeCodeQuality, scoreEffectiveness, extractCodeFromResponse, } from './code-analysis.js';
|
822
|
+
import { aiAnalytics } from './ai-analytics.js';
|
823
|
+
import { auditLogger, AuditEventType, initializeAuditLogging } from './audit-logger.js';
|
824
|
+
program
|
825
|
+
.command('analyze')
|
826
|
+
.description('Analyze an AI response for hallucinations and quality issues')
|
827
|
+
.argument('<prompt>', 'The original user prompt')
|
828
|
+
.argument('<response>', 'The AI response to analyze')
|
829
|
+
.option('-c, --context <context...>', 'Additional context messages')
|
830
|
+
.action((prompt, response, options) => {
|
831
|
+
console.log(chalk.bold.blue('🔍 AI Response Analysis'));
|
832
|
+
console.log(chalk.gray('━'.repeat(50)));
|
833
|
+
// Hallucination analysis
|
834
|
+
const hallucinationResult = hallucinationDetector.detectHallucination(prompt, response, options.context);
|
835
|
+
console.log(chalk.bold('\n🎯 Hallucination Detection:'));
|
836
|
+
console.log(` Status: ${hallucinationResult.isLikelyHallucination ? chalk.red('⚠️ LIKELY HALLUCINATION') : chalk.green('✅ Clean Response')}`);
|
837
|
+
console.log(` Confidence: ${hallucinationResult.confidence.toFixed(1)}%`);
|
838
|
+
console.log(` Severity: ${hallucinationResult.severity.toUpperCase()}`);
|
839
|
+
if (hallucinationResult.issues.length > 0) {
|
840
|
+
console.log(chalk.yellow('\n⚡ Issues Found:'));
|
841
|
+
hallucinationResult.issues.forEach(issue => {
|
842
|
+
console.log(` • ${issue}`);
|
843
|
+
});
|
844
|
+
}
|
845
|
+
// Extract and analyze code if present
|
846
|
+
const extractedCode = extractCodeFromResponse(response);
|
847
|
+
if (extractedCode) {
|
848
|
+
console.log(chalk.bold('\n💻 Code Quality Analysis:'));
|
849
|
+
const codeMetrics = analyzeCodeQuality(extractedCode.code, extractedCode.language);
|
850
|
+
console.log(` Language: ${codeMetrics.language || 'Unknown'}`);
|
851
|
+
console.log(` Lines of Code: ${codeMetrics.linesOfCode}`);
|
852
|
+
console.log(` Complexity: ${codeMetrics.complexity.toFixed(1)}/10`);
|
853
|
+
console.log(` Readability: ${codeMetrics.estimatedReadability}/10`);
|
854
|
+
console.log(` Syntax Valid: ${codeMetrics.syntaxValid ? chalk.green('✅') : chalk.red('❌')}`);
|
855
|
+
if (codeMetrics.potentialIssues.length > 0) {
|
856
|
+
console.log(chalk.yellow('\n⚠️ Potential Issues:'));
|
857
|
+
codeMetrics.potentialIssues.forEach(issue => {
|
858
|
+
console.log(` • ${issue}`);
|
859
|
+
});
|
860
|
+
}
|
861
|
+
}
|
862
|
+
// Effectiveness scoring
|
863
|
+
const effectiveness = scoreEffectiveness(prompt, response, extractedCode?.code);
|
864
|
+
console.log(chalk.bold('\n⚖️ Effectiveness Score:'));
|
865
|
+
console.log(` Overall: ${chalk.cyan(`${effectiveness.overallEffectiveness}/100`)}`);
|
866
|
+
console.log(` Prompt Match: ${effectiveness.promptClarityMatch.toFixed(1)}%`);
|
867
|
+
console.log(` Completeness: ${effectiveness.codeCompleteness.toFixed(1)}%`);
|
868
|
+
console.log(` Correctness: ${effectiveness.codeCorrectness.toFixed(1)}%`);
|
374
869
|
});
|
375
870
|
program
|
376
|
-
.command('
|
377
|
-
.description('
|
871
|
+
.command('quality')
|
872
|
+
.description('Analyze code quality metrics')
|
873
|
+
.argument('<code>', 'Code to analyze')
|
874
|
+
.option('-l, --language <lang>', 'Programming language')
|
875
|
+
.action((code, options) => {
|
876
|
+
console.log(chalk.bold.blue('💻 Code Quality Analysis'));
|
877
|
+
console.log(chalk.gray('━'.repeat(50)));
|
878
|
+
const metrics = analyzeCodeQuality(code, options.language);
|
879
|
+
console.log(`\n📋 Basic Metrics:`);
|
880
|
+
console.log(` Language: ${metrics.language || 'Unknown'}`);
|
881
|
+
console.log(` Lines of Code: ${metrics.linesOfCode}`);
|
882
|
+
console.log(` Complexity: ${chalk.cyan(`${metrics.complexity.toFixed(1)}/10`)}`);
|
883
|
+
console.log(` Readability: ${chalk.cyan(`${metrics.estimatedReadability}/10`)}`);
|
884
|
+
console.log(` Syntax Valid: ${metrics.syntaxValid ? chalk.green('✅') : chalk.red('❌')}`);
|
885
|
+
console.log(`\n🏗️ Structure:`);
|
886
|
+
console.log(` Has Functions: ${metrics.hasFunctions ? chalk.green('✅') : chalk.red('❌')}`);
|
887
|
+
console.log(` Has Classes: ${metrics.hasClasses ? chalk.green('✅') : chalk.red('❌')}`);
|
888
|
+
console.log(` Has Tests: ${metrics.hasTests ? chalk.green('✅') : chalk.red('❌')}`);
|
889
|
+
if (metrics.potentialIssues.length > 0) {
|
890
|
+
console.log(chalk.yellow('\n⚠️ Issues Found:'));
|
891
|
+
metrics.potentialIssues.forEach(issue => {
|
892
|
+
console.log(` • ${issue}`);
|
893
|
+
});
|
894
|
+
}
|
895
|
+
else {
|
896
|
+
console.log(chalk.green('\n✨ No issues detected!'));
|
897
|
+
}
|
898
|
+
});
|
899
|
+
program
|
900
|
+
.command('effectiveness')
|
901
|
+
.description('Score AI response effectiveness')
|
902
|
+
.argument('<prompt>', 'Original user prompt')
|
903
|
+
.argument('<response>', 'AI response to score')
|
904
|
+
.action((prompt, response) => {
|
905
|
+
console.log(chalk.bold.blue('⚖️ Effectiveness Analysis'));
|
906
|
+
console.log(chalk.gray('━'.repeat(50)));
|
907
|
+
const effectiveness = scoreEffectiveness(prompt, response);
|
908
|
+
console.log(`\n🎯 Overall Score: ${chalk.cyan(`${effectiveness.overallEffectiveness}/100`)}`);
|
909
|
+
console.log(`\n📊 Breakdown:`);
|
910
|
+
console.log(` Prompt Understanding: ${effectiveness.promptClarityMatch.toFixed(1)}%`);
|
911
|
+
console.log(` Code Completeness: ${effectiveness.codeCompleteness.toFixed(1)}%`);
|
912
|
+
console.log(` Code Correctness: ${effectiveness.codeCorrectness.toFixed(1)}%`);
|
913
|
+
console.log(` Code Efficiency: ${effectiveness.codeEfficiency.toFixed(1)}%`);
|
914
|
+
console.log(`\n💡 Interpretation:`);
|
915
|
+
if (effectiveness.overallEffectiveness >= 80) {
|
916
|
+
console.log(chalk.green(' 🌟 Excellent response quality!'));
|
917
|
+
}
|
918
|
+
else if (effectiveness.overallEffectiveness >= 60) {
|
919
|
+
console.log(chalk.blue(' 👍 Good response with minor issues'));
|
920
|
+
}
|
921
|
+
else if (effectiveness.overallEffectiveness >= 40) {
|
922
|
+
console.log(chalk.yellow(' ⚠️ Moderate quality - review needed'));
|
923
|
+
}
|
924
|
+
else {
|
925
|
+
console.log(chalk.red(' ❌ Poor quality - significant issues detected'));
|
926
|
+
}
|
927
|
+
});
|
928
|
+
program
|
929
|
+
.command('hallucinations')
|
930
|
+
.description('Show hallucination statistics and trends')
|
931
|
+
.option('-p, --provider <provider>', 'Filter by AI provider')
|
932
|
+
.option('-l, --last <hours>', 'Show last N hours (default: 24)', '24')
|
933
|
+
.action(async (options) => {
|
934
|
+
const analytics = aiAnalytics.generateAnalytics();
|
935
|
+
console.log(chalk.bold.blue('🧠 Hallucination Analytics'));
|
936
|
+
console.log(chalk.gray('━'.repeat(50)));
|
937
|
+
console.log(`\n📊 Overall Statistics:`);
|
938
|
+
console.log(` Total Interactions: ${analytics.totalInteractions}`);
|
939
|
+
console.log(` Hallucination Rate: ${chalk.red(`${analytics.hallucinationMetrics.hallucinationRate}%`)}`);
|
940
|
+
console.log(` Avg Confidence: ${analytics.hallucinationMetrics.avgConfidence.toFixed(1)}%`);
|
941
|
+
console.log(chalk.bold('\n🏢 Business Impact:'));
|
942
|
+
const impact = analytics.hallucinationMetrics.businessImpact;
|
943
|
+
console.log(` Dev Time Wasted: ${chalk.yellow(`${impact.estimatedDevTimeWasted}h`)}`);
|
944
|
+
console.log(` Quality Degradation: ${impact.qualityDegradationScore}/100`);
|
945
|
+
console.log(` ROI Impact: ${chalk.red(`${impact.roiImpact}% reduction`)}`);
|
946
|
+
console.log(` Extra Cost: ${chalk.red(`$${impact.costOfHallucinations.toFixed(2)}`)}`);
|
947
|
+
if (Object.keys(analytics.providerComparison).length > 0) {
|
948
|
+
console.log(chalk.bold('\n🔄 Provider Comparison:'));
|
949
|
+
Object.entries(analytics.providerComparison).forEach(([provider, stats]) => {
|
950
|
+
const status = stats.hallucinationRate > 15
|
951
|
+
? chalk.red('⚠️ ')
|
952
|
+
: stats.hallucinationRate > 5
|
953
|
+
? chalk.yellow('⚡ ')
|
954
|
+
: chalk.green('✅ ');
|
955
|
+
console.log(`${status}${provider}: ${stats.hallucinationRate}% hallucination rate`);
|
956
|
+
});
|
957
|
+
}
|
958
|
+
if (analytics.recommendations.length > 0) {
|
959
|
+
console.log(chalk.bold('\n💡 Recommendations:'));
|
960
|
+
analytics.recommendations.forEach(rec => {
|
961
|
+
console.log(` • ${rec}`);
|
962
|
+
});
|
963
|
+
}
|
964
|
+
// Interactive navigation
|
965
|
+
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
966
|
+
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
967
|
+
const navigationOptions = [
|
968
|
+
{
|
969
|
+
key: '1',
|
970
|
+
title: chalk.cyan('📊 View Detailed Analysis'),
|
971
|
+
description: 'Deep dive into specific hallucination patterns',
|
972
|
+
available: analytics.totalInteractions > 0,
|
973
|
+
},
|
974
|
+
{
|
975
|
+
key: '2',
|
976
|
+
title: chalk.magenta('🔄 Compare Providers'),
|
977
|
+
description: 'Detailed provider comparison for hallucinations',
|
978
|
+
available: Object.keys(analytics.providerComparison).length > 1,
|
979
|
+
},
|
980
|
+
{
|
981
|
+
key: '3',
|
982
|
+
title: chalk.yellow('💰 Business Impact Analysis'),
|
983
|
+
description: 'Analyze cost and time impact of hallucinations',
|
984
|
+
available: analytics.hallucinationMetrics.businessImpact.estimatedDevTimeWasted > 0,
|
985
|
+
},
|
986
|
+
{
|
987
|
+
key: '4',
|
988
|
+
title: chalk.green('📈 View All Analytics'),
|
989
|
+
description: 'Go to comprehensive statistics view',
|
990
|
+
available: true,
|
991
|
+
},
|
992
|
+
{
|
993
|
+
key: 'm',
|
994
|
+
title: chalk.gray('📋 Main Menu'),
|
995
|
+
description: 'Return to main menu',
|
996
|
+
available: true,
|
997
|
+
},
|
998
|
+
{
|
999
|
+
key: 'q',
|
1000
|
+
title: chalk.gray('❌ Exit'),
|
1001
|
+
description: 'Exit hallucination analysis',
|
1002
|
+
available: true,
|
1003
|
+
},
|
1004
|
+
];
|
1005
|
+
const availableChoices = navigationOptions
|
1006
|
+
.filter(option => option.available)
|
1007
|
+
.map(option => ({
|
1008
|
+
name: `${chalk.bold(option.key)}) ${option.title}\n ${option.description}`,
|
1009
|
+
value: option.key,
|
1010
|
+
}));
|
1011
|
+
const { choice } = await inquirer.prompt([
|
1012
|
+
{
|
1013
|
+
type: 'list',
|
1014
|
+
name: 'choice',
|
1015
|
+
message: 'What would you like to explore?',
|
1016
|
+
choices: availableChoices,
|
1017
|
+
},
|
1018
|
+
]);
|
1019
|
+
switch (choice) {
|
1020
|
+
case '1':
|
1021
|
+
console.log(chalk.cyan('\n📊 Opening detailed hallucination analysis...'));
|
1022
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr code-analysis'));
|
1023
|
+
break;
|
1024
|
+
case '2':
|
1025
|
+
console.log(chalk.magenta('\n🔄 Opening provider comparison...'));
|
1026
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr providers'));
|
1027
|
+
break;
|
1028
|
+
case '3':
|
1029
|
+
console.log(chalk.yellow('\n💰 Analyzing business impact...'));
|
1030
|
+
console.log(chalk.gray('Business impact details:'));
|
1031
|
+
const impact = analytics.hallucinationMetrics.businessImpact;
|
1032
|
+
console.log(` • Estimated dev time wasted: ${impact.estimatedDevTimeWasted}h`);
|
1033
|
+
console.log(` • Quality degradation score: ${impact.qualityDegradationScore}/100`);
|
1034
|
+
console.log(` • ROI impact: ${impact.roiImpact}% reduction`);
|
1035
|
+
console.log(` • Extra cost from hallucinations: ${impact.costOfHallucinations.toFixed(2)}`);
|
1036
|
+
break;
|
1037
|
+
case '4':
|
1038
|
+
console.log(chalk.green('\n📈 Opening comprehensive analytics...'));
|
1039
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr stats'));
|
1040
|
+
break;
|
1041
|
+
case 'm':
|
1042
|
+
console.log(chalk.gray('\n📋 Opening main menu...'));
|
1043
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr menu'));
|
1044
|
+
break;
|
1045
|
+
case 'q':
|
1046
|
+
console.log(chalk.gray('\n👋 Exiting hallucination analysis...'));
|
1047
|
+
break;
|
1048
|
+
default:
|
1049
|
+
console.log(chalk.yellow(`Unknown option "${choice}".`));
|
1050
|
+
}
|
1051
|
+
});
|
1052
|
+
program
|
1053
|
+
.command('providers')
|
1054
|
+
.description('Compare AI provider performance')
|
378
1055
|
.action(async () => {
|
379
|
-
|
380
|
-
|
1056
|
+
const analytics = aiAnalytics.generateAnalytics();
|
1057
|
+
console.log(chalk.bold.blue('🔄 AI Provider Comparison'));
|
1058
|
+
console.log(chalk.gray('━'.repeat(50)));
|
1059
|
+
if (Object.keys(analytics.providerComparison).length === 0) {
|
1060
|
+
console.log(chalk.yellow('No provider data available yet. Use your AI tools to generate some data!'));
|
1061
|
+
return;
|
1062
|
+
}
|
1063
|
+
console.log(`\n📊 Provider Statistics:`);
|
1064
|
+
Object.entries(analytics.providerComparison).forEach(([provider, stats]) => {
|
1065
|
+
const qualityColor = stats.avgQualityScore >= 80
|
1066
|
+
? chalk.green
|
1067
|
+
: stats.avgQualityScore >= 60
|
1068
|
+
? chalk.blue
|
1069
|
+
: chalk.red;
|
1070
|
+
const effectivenessColor = stats.avgEffectivenessScore >= 80
|
1071
|
+
? chalk.green
|
1072
|
+
: stats.avgEffectivenessScore >= 60
|
1073
|
+
? chalk.blue
|
1074
|
+
: chalk.red;
|
1075
|
+
const hallucinationColor = stats.hallucinationRate <= 5
|
1076
|
+
? chalk.green
|
1077
|
+
: stats.hallucinationRate <= 15
|
1078
|
+
? chalk.yellow
|
1079
|
+
: chalk.red;
|
1080
|
+
console.log(`\n🏢 ${chalk.bold(provider)}:`);
|
1081
|
+
console.log(` Total Interactions: ${stats.totalInteractions}`);
|
1082
|
+
console.log(` Hallucination Rate: ${hallucinationColor(`${stats.hallucinationRate}%`)}`);
|
1083
|
+
console.log(` Avg Quality Score: ${qualityColor(`${stats.avgQualityScore}/100`)}`);
|
1084
|
+
console.log(` Avg Effectiveness: ${effectivenessColor(`${stats.avgEffectivenessScore}/100`)}`);
|
1085
|
+
if (stats.businessImpact.estimatedDevTimeWasted > 0) {
|
1086
|
+
console.log(` Dev Time Wasted: ${chalk.yellow(`${stats.businessImpact.estimatedDevTimeWasted}h`)}`);
|
1087
|
+
}
|
1088
|
+
});
|
1089
|
+
// Find best and worst performers
|
1090
|
+
const providers = Object.entries(analytics.providerComparison);
|
1091
|
+
if (providers.length > 1) {
|
1092
|
+
const bestProvider = providers.sort(([, a], [, b]) => b.avgQualityScore +
|
1093
|
+
b.avgEffectivenessScore -
|
1094
|
+
(a.avgQualityScore + a.avgEffectivenessScore))[0];
|
1095
|
+
const worstProvider = providers.sort(([, a], [, b]) => a.avgQualityScore +
|
1096
|
+
a.avgEffectivenessScore -
|
1097
|
+
(b.avgQualityScore + b.avgEffectivenessScore))[0];
|
1098
|
+
console.log(chalk.bold('\n🏆 Performance Summary:'));
|
1099
|
+
console.log(` Best Provider: ${chalk.green(bestProvider[0])} (${bestProvider[1].avgQualityScore}/100 quality)`);
|
1100
|
+
console.log(` Needs Attention: ${chalk.red(worstProvider[0])} (${worstProvider[1].avgQualityScore}/100 quality)`);
|
1101
|
+
}
|
1102
|
+
// Interactive navigation
|
1103
|
+
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
1104
|
+
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
1105
|
+
const providerList = Object.keys(analytics.providerComparison);
|
1106
|
+
const navigationOptions = [
|
1107
|
+
{
|
1108
|
+
key: '1',
|
1109
|
+
title: chalk.cyan('📊 Detailed Provider Analysis'),
|
1110
|
+
description: 'Deep dive into individual provider metrics',
|
1111
|
+
available: providerList.length > 0,
|
1112
|
+
},
|
1113
|
+
{
|
1114
|
+
key: '2',
|
1115
|
+
title: chalk.magenta('🧠 Hallucination Comparison'),
|
1116
|
+
description: 'Compare hallucination rates across providers',
|
1117
|
+
available: providerList.length > 1,
|
1118
|
+
},
|
1119
|
+
{
|
1120
|
+
key: '3',
|
1121
|
+
title: chalk.yellow('💰 Cost Optimization'),
|
1122
|
+
description: 'Analyze cost-effectiveness by provider',
|
1123
|
+
available: true,
|
1124
|
+
},
|
1125
|
+
{
|
1126
|
+
key: '4',
|
1127
|
+
title: chalk.green('📈 View All Analytics'),
|
1128
|
+
description: 'Go to comprehensive statistics view',
|
1129
|
+
available: true,
|
1130
|
+
},
|
1131
|
+
{
|
1132
|
+
key: 'm',
|
1133
|
+
title: chalk.gray('📋 Main Menu'),
|
1134
|
+
description: 'Return to main menu',
|
1135
|
+
available: true,
|
1136
|
+
},
|
1137
|
+
{
|
1138
|
+
key: 'q',
|
1139
|
+
title: chalk.gray('❌ Exit'),
|
1140
|
+
description: 'Exit provider comparison',
|
1141
|
+
available: true,
|
1142
|
+
},
|
1143
|
+
];
|
1144
|
+
const availableChoices = navigationOptions
|
1145
|
+
.filter(option => option.available)
|
1146
|
+
.map(option => ({
|
1147
|
+
name: `${chalk.bold(option.key)}) ${option.title}\n ${option.description}`,
|
1148
|
+
value: option.key,
|
1149
|
+
}));
|
1150
|
+
const { choice } = await inquirer.prompt([
|
1151
|
+
{
|
1152
|
+
type: 'list',
|
1153
|
+
name: 'choice',
|
1154
|
+
message: 'What would you like to explore?',
|
1155
|
+
choices: availableChoices,
|
1156
|
+
},
|
1157
|
+
]);
|
1158
|
+
switch (choice) {
|
1159
|
+
case '1':
|
1160
|
+
console.log(chalk.cyan('\n📊 Opening detailed provider analysis...'));
|
1161
|
+
if (providerList.length > 1) {
|
1162
|
+
const { selectedProvider } = await inquirer.prompt([
|
1163
|
+
{
|
1164
|
+
type: 'list',
|
1165
|
+
name: 'selectedProvider',
|
1166
|
+
message: 'Select a provider to analyze:',
|
1167
|
+
choices: providerList.map(provider => ({
|
1168
|
+
name: `${provider} (${analytics.providerComparison[provider].avgQualityScore}/100 quality)`,
|
1169
|
+
value: provider,
|
1170
|
+
})),
|
1171
|
+
},
|
1172
|
+
]);
|
1173
|
+
const providerStats = analytics.providerComparison[selectedProvider];
|
1174
|
+
console.log(chalk.cyan(`\n📊 Detailed Analysis for ${selectedProvider}:`));
|
1175
|
+
console.log(` Total Interactions: ${providerStats.totalInteractions}`);
|
1176
|
+
console.log(` Avg Quality Score: ${providerStats.avgQualityScore}/100`);
|
1177
|
+
console.log(` Avg Effectiveness: ${providerStats.avgEffectivenessScore}/100`);
|
1178
|
+
console.log(` Hallucination Rate: ${providerStats.hallucinationRate}%`);
|
1179
|
+
console.log(` Dev Time Wasted: ${providerStats.businessImpact.estimatedDevTimeWasted}h`);
|
1180
|
+
}
|
1181
|
+
else {
|
1182
|
+
console.log(chalk.gray('Only one provider available. Run: ') + chalk.yellow('toknxr code-analysis'));
|
1183
|
+
}
|
1184
|
+
break;
|
1185
|
+
case '2':
|
1186
|
+
console.log(chalk.magenta('\n🧠 Opening hallucination comparison...'));
|
1187
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr hallucinations'));
|
1188
|
+
break;
|
1189
|
+
case '3':
|
1190
|
+
console.log(chalk.yellow('\n💰 Analyzing cost optimization...'));
|
1191
|
+
console.log(chalk.gray('Cost optimization recommendations:'));
|
1192
|
+
providerList.forEach(provider => {
|
1193
|
+
const stats = analytics.providerComparison[provider];
|
1194
|
+
const efficiency = stats.avgQualityScore / (stats.businessImpact.costOfHallucinations || 1);
|
1195
|
+
console.log(` • ${provider}: Quality/Cost ratio = ${efficiency.toFixed(2)}`);
|
1196
|
+
});
|
1197
|
+
break;
|
1198
|
+
case '4':
|
1199
|
+
console.log(chalk.green('\n📈 Opening comprehensive analytics...'));
|
1200
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr stats'));
|
1201
|
+
break;
|
1202
|
+
case 'm':
|
1203
|
+
console.log(chalk.gray('\n📋 Opening main menu...'));
|
1204
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr menu'));
|
1205
|
+
break;
|
1206
|
+
case 'q':
|
1207
|
+
console.log(chalk.gray('\n👋 Exiting provider comparison...'));
|
1208
|
+
break;
|
1209
|
+
default:
|
1210
|
+
console.log(chalk.yellow(`Unknown option "${choice}".`));
|
1211
|
+
}
|
1212
|
+
});
|
1213
|
+
program
|
1214
|
+
.command('export')
|
1215
|
+
.description('Export analytics data to JSON file')
|
1216
|
+
.option('-o, --output <file>', 'Output file path')
|
1217
|
+
.action(async (options) => {
|
1218
|
+
try {
|
1219
|
+
let outputPath = options.output;
|
1220
|
+
if (!outputPath) { // If -o option is not provided, use interactive mode
|
1221
|
+
const { locationType } = await inquirer.prompt([
|
1222
|
+
{
|
1223
|
+
type: 'list',
|
1224
|
+
name: 'locationType',
|
1225
|
+
message: 'Where would you like to save the export file?',
|
1226
|
+
choices: [
|
1227
|
+
{ name: 'Current Directory', value: 'current' },
|
1228
|
+
{ name: 'Desktop', value: 'desktop' },
|
1229
|
+
{ name: 'Custom Path', value: 'custom' },
|
1230
|
+
],
|
1231
|
+
},
|
1232
|
+
]);
|
1233
|
+
let chosenDirectory = process.cwd();
|
1234
|
+
if (locationType === 'desktop') {
|
1235
|
+
chosenDirectory = path.join(os.homedir(), 'Desktop');
|
1236
|
+
}
|
1237
|
+
else if (locationType === 'custom') {
|
1238
|
+
const { customPath } = await inquirer.prompt([
|
1239
|
+
{
|
1240
|
+
type: 'input',
|
1241
|
+
name: 'customPath',
|
1242
|
+
message: 'Enter the custom directory path:',
|
1243
|
+
default: process.cwd(),
|
1244
|
+
},
|
1245
|
+
]);
|
1246
|
+
chosenDirectory = customPath;
|
1247
|
+
}
|
1248
|
+
const { filename } = await inquirer.prompt([
|
1249
|
+
{
|
1250
|
+
type: 'input',
|
1251
|
+
name: 'filename',
|
1252
|
+
message: 'Enter the filename for the export:',
|
1253
|
+
default: 'ai-analytics-export.json',
|
1254
|
+
},
|
1255
|
+
]);
|
1256
|
+
// Ensure the chosen directory exists
|
1257
|
+
if (!fs.existsSync(chosenDirectory)) {
|
1258
|
+
fs.mkdirSync(chosenDirectory, { recursive: true });
|
1259
|
+
console.log(chalk.green(`Created directory: ${chosenDirectory}`));
|
1260
|
+
}
|
1261
|
+
outputPath = path.join(chosenDirectory, filename);
|
1262
|
+
}
|
1263
|
+
aiAnalytics.exportAnalytics(outputPath);
|
1264
|
+
console.log(chalk.green(`✅ Analytics exported to ${outputPath}`));
|
1265
|
+
}
|
1266
|
+
catch (error) {
|
1267
|
+
console.error(chalk.red('❌ Export failed'), error);
|
1268
|
+
}
|
1269
|
+
});
|
1270
|
+
// Enhanced Interactive Commands - Phase 3+
|
1271
|
+
program
|
1272
|
+
.command('browse')
|
1273
|
+
.description('Interactive paginated browsing of all AI interactions with filtering')
|
1274
|
+
.option('-p, --page <page>', 'Start page number', '1')
|
1275
|
+
.option('-s, --size <size>', 'Page size (10/25/50)', '10')
|
1276
|
+
.action(async (options) => {
|
1277
|
+
const logFilePath = path.resolve(process.cwd(), 'interactions.log');
|
1278
|
+
if (!fs.existsSync(logFilePath)) {
|
1279
|
+
console.log(chalk.yellow('No interactions logged yet. Start tracking with: ') +
|
1280
|
+
chalk.cyan('toknxr start'));
|
1281
|
+
return;
|
1282
|
+
}
|
1283
|
+
const fileContent = fs.readFileSync(logFilePath, 'utf8');
|
1284
|
+
const lines = fileContent.trim().split('\n');
|
1285
|
+
const interactions = lines
|
1286
|
+
.map(line => {
|
1287
|
+
try {
|
1288
|
+
return JSON.parse(line);
|
1289
|
+
}
|
1290
|
+
catch {
|
1291
|
+
return null;
|
1292
|
+
}
|
1293
|
+
})
|
1294
|
+
.filter((interaction) => interaction !== null);
|
1295
|
+
if (interactions.length === 0) {
|
1296
|
+
console.log(chalk.yellow('No valid interactions found.'));
|
1297
|
+
return;
|
1298
|
+
}
|
1299
|
+
const explorer = new InteractiveDataExplorer(interactions);
|
1300
|
+
const currentPage = parseInt(options.page) || 1;
|
1301
|
+
const pageSize = parseInt(options.size) || 10;
|
1302
|
+
explorer.setPageSize(pageSize);
|
1303
|
+
explorer.setPage(currentPage);
|
1304
|
+
const renderInteraction = (interaction, index) => {
|
1305
|
+
const num = index + 1;
|
1306
|
+
const costColor = interaction.costUSD > 0.1
|
1307
|
+
? chalk.red
|
1308
|
+
: interaction.costUSD > 0.05
|
1309
|
+
? chalk.yellow
|
1310
|
+
: chalk.green;
|
1311
|
+
const qualityColor = (interaction.codeQualityScore || 0) >= 80
|
1312
|
+
? chalk.green
|
1313
|
+
: (interaction.codeQualityScore || 0) >= 60
|
1314
|
+
? chalk.blue
|
1315
|
+
: chalk.red;
|
1316
|
+
return createBox(`#${num} ${interaction.provider}`, [
|
1317
|
+
`📅 ${chalk.gray(new Date(interaction.timestamp || Date.now()).toLocaleDateString())}`,
|
1318
|
+
`💰 Cost: ${costColor(`$${interaction.costUSD?.toFixed(4) || '0.0000'}`)}`,
|
1319
|
+
`🎯 Effectiveness: ${interaction.effectivenessScore || 'N/A'}/100`,
|
1320
|
+
`⭐ Quality: ${qualityColor(`${interaction.codeQualityScore || 'N/A'}/100`)}`,
|
1321
|
+
`🔤 ${interaction.totalTokens || 0} tokens • ${interaction.model || 'Unknown model'}`,
|
1322
|
+
...(interaction.userPrompt
|
1323
|
+
? [
|
1324
|
+
`"${interaction.userPrompt.substring(0, 60)}${interaction.userPrompt.length > 60 ? '...' : ''}"`,
|
1325
|
+
]
|
1326
|
+
: []),
|
1327
|
+
], {
|
1328
|
+
borderColor: 'gray',
|
1329
|
+
titleColor: 'cyan',
|
1330
|
+
width: 80,
|
1331
|
+
});
|
1332
|
+
};
|
1333
|
+
console.log(createPaginatedDisplay(explorer.getCurrentPageData(), pageSize, currentPage, renderInteraction, `🐙 AI Interactions Browser (${explorer.getPaginationInfo().totalItems} total)`));
|
1334
|
+
// Interactive navigation
|
1335
|
+
const pagination = explorer.getPaginationInfo();
|
1336
|
+
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
1337
|
+
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
1338
|
+
const navigationOptions = [
|
1339
|
+
{
|
1340
|
+
key: 'n',
|
1341
|
+
title: chalk.cyan('📄 Next Page'),
|
1342
|
+
description: `Go to page ${currentPage + 1}`,
|
1343
|
+
available: currentPage < pagination.totalPages,
|
1344
|
+
},
|
1345
|
+
{
|
1346
|
+
key: 'p',
|
1347
|
+
title: chalk.cyan('📄 Previous Page'),
|
1348
|
+
description: `Go to page ${currentPage - 1}`,
|
1349
|
+
available: currentPage > 1,
|
1350
|
+
},
|
1351
|
+
{
|
1352
|
+
key: 'f',
|
1353
|
+
title: chalk.magenta('🔍 Filter Results'),
|
1354
|
+
description: 'Apply filters to narrow down interactions',
|
1355
|
+
available: true,
|
1356
|
+
},
|
1357
|
+
{
|
1358
|
+
key: 's',
|
1359
|
+
title: chalk.yellow('🔍 Search Interactions'),
|
1360
|
+
description: 'Search through all interactions',
|
1361
|
+
available: true,
|
1362
|
+
},
|
1363
|
+
{
|
1364
|
+
key: 'a',
|
1365
|
+
title: chalk.green('📊 View Analytics'),
|
1366
|
+
description: 'Go to detailed statistics view',
|
1367
|
+
available: true,
|
1368
|
+
},
|
1369
|
+
{
|
1370
|
+
key: 'm',
|
1371
|
+
title: chalk.gray('📋 Main Menu'),
|
1372
|
+
description: 'Return to main menu',
|
1373
|
+
available: true,
|
1374
|
+
},
|
1375
|
+
{
|
1376
|
+
key: 'q',
|
1377
|
+
title: chalk.gray('❌ Exit'),
|
1378
|
+
description: 'Exit browser',
|
1379
|
+
available: true,
|
1380
|
+
},
|
1381
|
+
];
|
1382
|
+
const availableChoices = navigationOptions
|
1383
|
+
.filter(option => option.available)
|
1384
|
+
.map(option => ({
|
1385
|
+
name: `${chalk.bold(option.key)}) ${option.title}\n ${option.description}`,
|
1386
|
+
value: option.key,
|
1387
|
+
}));
|
1388
|
+
const { choice } = await inquirer.prompt([
|
1389
|
+
{
|
1390
|
+
type: 'list',
|
1391
|
+
name: 'choice',
|
1392
|
+
message: 'What would you like to do?',
|
1393
|
+
choices: availableChoices,
|
1394
|
+
},
|
1395
|
+
]);
|
1396
|
+
switch (choice) {
|
1397
|
+
case 'n':
|
1398
|
+
console.log(chalk.cyan(`\n📄 Going to page ${currentPage + 1}...`));
|
1399
|
+
console.log(chalk.gray('Run: ') + chalk.yellow(`toknxr browse --page ${currentPage + 1}`));
|
1400
|
+
break;
|
1401
|
+
case 'p':
|
1402
|
+
console.log(chalk.cyan(`\n📄 Going to page ${currentPage - 1}...`));
|
1403
|
+
console.log(chalk.gray('Run: ') + chalk.yellow(`toknxr browse --page ${currentPage - 1}`));
|
1404
|
+
break;
|
1405
|
+
case 'f':
|
1406
|
+
console.log(chalk.magenta('\n🔍 Opening filter interface...'));
|
1407
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr filter'));
|
1408
|
+
break;
|
1409
|
+
case 's':
|
1410
|
+
console.log(chalk.yellow('\n🔍 Opening search interface...'));
|
1411
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr search --query "your terms"'));
|
1412
|
+
break;
|
1413
|
+
case 'a':
|
1414
|
+
console.log(chalk.green('\n📊 Opening analytics view...'));
|
1415
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr stats'));
|
1416
|
+
break;
|
1417
|
+
case 'm':
|
1418
|
+
console.log(chalk.gray('\n📋 Opening main menu...'));
|
1419
|
+
console.log(chalk.gray('Run: ') + chalk.yellow('toknxr menu'));
|
1420
|
+
break;
|
1421
|
+
case 'q':
|
1422
|
+
console.log(chalk.gray('\n👋 Exiting browser...'));
|
1423
|
+
break;
|
1424
|
+
default:
|
1425
|
+
console.log(chalk.yellow(`Unknown option "${choice}".`));
|
1426
|
+
}
|
381
1427
|
});
|
382
|
-
program
|
1428
|
+
program
|
1429
|
+
.command('filter')
|
1430
|
+
.description('Interactive filtering interface for AI interactions')
|
1431
|
+
.action(async () => {
|
1432
|
+
try {
|
1433
|
+
console.log(chalk.blue.bold('\n🔍 Advanced Filtering'));
|
1434
|
+
console.log(chalk.gray('Configure filters that will persist across sessions\n'));
|
1435
|
+
const currentFilters = (CliStateManager.getPreferences().filters || {});
|
1436
|
+
const newFilters = await createFilterInterface(currentFilters);
|
1437
|
+
console.log(chalk.green('\n✅ Filters applied and saved!'));
|
1438
|
+
console.log(chalk.gray('Use ') + chalk.cyan('toknxr browse') + chalk.gray(' to see filtered results'));
|
1439
|
+
console.log(chalk.gray('Use ') + chalk.cyan('toknxr filter') + chalk.gray(' again to modify filters'));
|
1440
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
1441
|
+
}
|
1442
|
+
catch (error) {
|
1443
|
+
console.log(chalk.red('❌ Failed to apply filters'));
|
1444
|
+
console.log(chalk.gray('Check your terminal supports interactive prompts'));
|
1445
|
+
}
|
1446
|
+
});
|
1447
|
+
program
|
1448
|
+
.command('search')
|
1449
|
+
.description('Search across all AI interactions with field selection')
|
1450
|
+
.option('-q, --query <query>', 'Search query (minimum 2 characters)')
|
1451
|
+
.action(async (options) => {
|
1452
|
+
const query = options.query;
|
1453
|
+
if (!query || query.trim().length < 2) {
|
1454
|
+
console.log(chalk.yellow('Please provide a search query with at least 2 characters:'));
|
1455
|
+
console.log(chalk.cyan(' toknxr search --query "your search terms"'));
|
1456
|
+
return;
|
1457
|
+
}
|
1458
|
+
try {
|
1459
|
+
console.log(chalk.blue.bold(`\n🔍 Searching for: "${query}"`));
|
1460
|
+
const availableFields = ['provider', 'model', 'userPrompt', 'taskType', 'requestId'];
|
1461
|
+
const searchOptions = await createSearchInterface(availableFields);
|
1462
|
+
if (!searchOptions) {
|
1463
|
+
console.log(chalk.gray('Search cancelled.'));
|
1464
|
+
return;
|
1465
|
+
}
|
1466
|
+
const logFilePath = path.resolve(process.cwd(), 'interactions.log');
|
1467
|
+
if (!fs.existsSync(logFilePath)) {
|
1468
|
+
console.log(chalk.yellow('No interactions logged yet.'));
|
1469
|
+
return;
|
1470
|
+
}
|
1471
|
+
const fileContent = fs.readFileSync(logFilePath, 'utf8');
|
1472
|
+
const lines = fileContent.trim().split('\n');
|
1473
|
+
const interactions = lines
|
1474
|
+
.map(line => {
|
1475
|
+
try {
|
1476
|
+
return JSON.parse(line);
|
1477
|
+
}
|
1478
|
+
catch {
|
1479
|
+
return null;
|
1480
|
+
}
|
1481
|
+
})
|
1482
|
+
.filter((interaction) => interaction !== null);
|
1483
|
+
const filteredResults = filterAndSearchInteractions(interactions, {}, searchOptions);
|
1484
|
+
if (filteredResults.length === 0) {
|
1485
|
+
console.log(chalk.yellow(`\nNo results found for "${query}" in the selected fields.`));
|
1486
|
+
return;
|
1487
|
+
}
|
1488
|
+
const explorer = new InteractiveDataExplorer(filteredResults);
|
1489
|
+
console.log(chalk.green(`\n✅ Found ${filteredResults.length} matching interactions`));
|
1490
|
+
console.log(chalk.gray(`Searching in: ${searchOptions.fields.join(', ')}\n`));
|
1491
|
+
const providerCounts = {};
|
1492
|
+
filteredResults.forEach((i) => {
|
1493
|
+
providerCounts[i.provider] = (providerCounts[i.provider] || 0) + 1;
|
1494
|
+
});
|
1495
|
+
console.log(createBox('📊 Search Results Summary', [
|
1496
|
+
`Total Results: ${filteredResults.length}`,
|
1497
|
+
`Providers: ${Object.entries(providerCounts)
|
1498
|
+
.map(([p, c]) => `${p}(${c})`)
|
1499
|
+
.join(', ')}`,
|
1500
|
+
'',
|
1501
|
+
chalk.gray('Use ↑/↓ or page up/down to navigate results'),
|
1502
|
+
], { borderColor: 'green', titleColor: 'green' }));
|
1503
|
+
const renderSearchResult = (interaction, index) => {
|
1504
|
+
const num = index + 1;
|
1505
|
+
const score = calcRelevanceScore(interaction, searchOptions.query, searchOptions.fields);
|
1506
|
+
let highlightColor;
|
1507
|
+
if (score >= 0.8)
|
1508
|
+
highlightColor = 'green';
|
1509
|
+
else if (score >= 0.6)
|
1510
|
+
highlightColor = 'yellow';
|
1511
|
+
else
|
1512
|
+
highlightColor = 'red';
|
1513
|
+
const highlightedPrompt = highlightMatch(interaction.userPrompt || '', searchOptions.query);
|
1514
|
+
const colorFn = highlightColor === 'green'
|
1515
|
+
? chalk.green
|
1516
|
+
: highlightColor === 'yellow'
|
1517
|
+
? chalk.yellow
|
1518
|
+
: chalk.red;
|
1519
|
+
return createBox(`#${num} ${interaction.provider} (${colorFn('★'.repeat(Math.ceil(score * 5)))})`, [
|
1520
|
+
`📅 ${chalk.gray(new Date(interaction.timestamp || Date.now()).toLocaleDateString())}`,
|
1521
|
+
`🎯 ${highlightedPrompt || 'No prompt available'}`,
|
1522
|
+
`💰 $${interaction.costUSD?.toFixed(4) || '0.0000'} • ⭐ ${interaction.codeQualityScore || 'N/A'}/100`,
|
1523
|
+
], {
|
1524
|
+
borderColor: highlightColor,
|
1525
|
+
titleColor: 'cyan',
|
1526
|
+
width: 90,
|
1527
|
+
});
|
1528
|
+
};
|
1529
|
+
console.log(createPaginatedDisplay(explorer.getCurrentPageData(), 5, // Smaller page size for search results
|
1530
|
+
1, renderSearchResult, undefined));
|
1531
|
+
}
|
1532
|
+
catch (error) {
|
1533
|
+
console.log(chalk.red('❌ Search failed'));
|
1534
|
+
console.log(chalk.gray('Try using: ') + chalk.cyan('toknxr search --query "your terms"'));
|
1535
|
+
}
|
1536
|
+
});
|
1537
|
+
program
|
1538
|
+
.command('budget')
|
1539
|
+
.description('Manage budget settings and view spending analytics')
|
1540
|
+
.option('--set <amount>', 'Set monthly budget amount')
|
1541
|
+
.option('--provider <provider>', 'Set budget for specific provider')
|
1542
|
+
.option('--view', 'View current budget settings')
|
1543
|
+
.action(options => {
|
1544
|
+
if (options.set) {
|
1545
|
+
const amount = parseFloat(options.set);
|
1546
|
+
if (isNaN(amount) || amount <= 0) {
|
1547
|
+
console.log(chalk.red('❌ Invalid budget amount. Must be a positive number.'));
|
1548
|
+
return;
|
1549
|
+
}
|
1550
|
+
CliStateManager.updateSessionBudget(amount, options.provider);
|
1551
|
+
const budgetType = options.provider ? ` for ${options.provider}` : ' (monthly default)';
|
1552
|
+
console.log(chalk.green(`✅ Budget updated to $${amount.toFixed(2)}${budgetType}`));
|
1553
|
+
if (!options.provider) {
|
1554
|
+
console.log(chalk.cyan('\n💡 Tip: Set provider-specific budgets for granular control'));
|
1555
|
+
console.log(chalk.gray('Example: ') + chalk.yellow('toknxr budget --set 25 --provider "Gemini-Pro"'));
|
1556
|
+
}
|
1557
|
+
}
|
1558
|
+
else if (options.view || Object.keys(options).length === 0) {
|
1559
|
+
const preferences = CliStateManager.getPreferences();
|
1560
|
+
const budgets = CliStateManager.loadState().budgets;
|
1561
|
+
console.log(chalk.blue.bold('\n💰 Budget Configuration'));
|
1562
|
+
console.log(chalk.gray('━'.repeat(50)));
|
1563
|
+
console.log(chalk.bold('\nCurrent Settings:'));
|
1564
|
+
console.log(`📊 Monthly Default: ${chalk.cyan(`$${budgets.default || 50.0}`)}`);
|
1565
|
+
console.log(`📏 Page Size: ${preferences.pageSize || 10} items`);
|
1566
|
+
console.log(`🔧 Sort Order: ${preferences.sortOrder || 'date_descends'}`);
|
1567
|
+
if (Object.keys(budgets).length > 1) {
|
1568
|
+
console.log(chalk.bold('\nProvider-Specific Budgets:'));
|
1569
|
+
Object.entries(budgets).forEach(([provider, amount]) => {
|
1570
|
+
if (provider !== 'default') {
|
1571
|
+
console.log(`🏢 ${provider}: ${chalk.cyan(`$${amount}`)}`);
|
1572
|
+
}
|
1573
|
+
});
|
1574
|
+
}
|
1575
|
+
console.log(chalk.bold('\n💡 Budget Commands:'));
|
1576
|
+
console.log(` ${chalk.yellow('toknxr budget --set 75')} - Set monthly budget`);
|
1577
|
+
console.log(` ${chalk.yellow('toknxr budget --set 30 --provider GPT4')} - Set provider budget`);
|
1578
|
+
}
|
1579
|
+
});
|
1580
|
+
// Phase 5: Enterprise Audit Logging Commands
|
1581
|
+
program
|
1582
|
+
.command('audit:init')
|
1583
|
+
.description('Initialize enterprise audit logging system')
|
1584
|
+
.option('--encrypt', 'Enable audit log encryption')
|
1585
|
+
.option('--retention <days>', 'Log retention period in days', '365')
|
1586
|
+
.action(options => {
|
1587
|
+
try {
|
1588
|
+
initializeAuditLogging({
|
1589
|
+
encryptionEnabled: options.encrypt || false,
|
1590
|
+
retentionDays: parseInt(options.retention) || 365,
|
1591
|
+
enabled: true,
|
1592
|
+
});
|
1593
|
+
// Log the initialization event
|
1594
|
+
auditLogger.logAuthEvent(AuditEventType.SYSTEM_MAINTENANCE, 'system', true, {
|
1595
|
+
component: 'audit_system',
|
1596
|
+
action: 'initialization',
|
1597
|
+
settings: {
|
1598
|
+
encryptionEnabled: options.encrypt || false,
|
1599
|
+
retentionDays: parseInt(options.retention) || 365,
|
1600
|
+
},
|
1601
|
+
});
|
1602
|
+
console.log(chalk.green('✅ Enterprise audit logging initialized'));
|
1603
|
+
console.log(chalk.gray('Audit logs will be written to: audit.log'));
|
1604
|
+
if (options.encrypt) {
|
1605
|
+
console.log(chalk.yellow('🛡️ Audit logs are encrypted'));
|
1606
|
+
}
|
1607
|
+
}
|
1608
|
+
catch (error) {
|
1609
|
+
console.log(chalk.red('❌ Failed to initialize audit logging'));
|
1610
|
+
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
1611
|
+
}
|
1612
|
+
});
|
1613
|
+
program
|
1614
|
+
.command('audit:view')
|
1615
|
+
.description('View audit events with filtering options')
|
1616
|
+
.option('-t, --type <eventType>', 'Filter by event type (e.g., ai.request, auth.login)')
|
1617
|
+
.option('-u, --user <userId>', 'Filter by user ID')
|
1618
|
+
.option('-r, --risk <level>', 'Filter by risk level (low/medium/high/critical)')
|
1619
|
+
.option('-f, --from <date>', 'Filter events from date (ISO format)')
|
1620
|
+
.option('--to <date>', 'Filter events to date (ISO format)')
|
1621
|
+
.option('-l, --limit <number>', 'Limit number of results', '50')
|
1622
|
+
.action(options => {
|
1623
|
+
try {
|
1624
|
+
const events = auditLogger.query({
|
1625
|
+
eventType: options.type,
|
1626
|
+
userId: options.user,
|
1627
|
+
riskLevel: options.risk,
|
1628
|
+
dateFrom: options.from,
|
1629
|
+
dateTo: options.to,
|
1630
|
+
limit: parseInt(options.limit) || 50,
|
1631
|
+
});
|
1632
|
+
if (events.length === 0) {
|
1633
|
+
console.log(chalk.yellow('No audit events found matching the criteria.'));
|
1634
|
+
return;
|
1635
|
+
}
|
1636
|
+
console.log(chalk.blue.bold('📋 Audit Events'));
|
1637
|
+
console.log(chalk.gray('━'.repeat(80)));
|
1638
|
+
// Group events by date for better readability
|
1639
|
+
const eventsByDate = {};
|
1640
|
+
events.forEach(event => {
|
1641
|
+
const date = new Date(event.timestamp).toLocaleDateString();
|
1642
|
+
if (!eventsByDate[date])
|
1643
|
+
eventsByDate[date] = [];
|
1644
|
+
eventsByDate[date].push(event);
|
1645
|
+
});
|
1646
|
+
Object.entries(eventsByDate).forEach(([date, dateEvents]) => {
|
1647
|
+
console.log(chalk.cyan(`\n📅 ${date}`));
|
1648
|
+
dateEvents.forEach(event => {
|
1649
|
+
const time = new Date(event.timestamp).toLocaleTimeString();
|
1650
|
+
const riskColor = event.riskLevel === 'critical'
|
1651
|
+
? chalk.red
|
1652
|
+
: event.riskLevel === 'high'
|
1653
|
+
? chalk.yellow
|
1654
|
+
: event.riskLevel === 'medium'
|
1655
|
+
? chalk.blue
|
1656
|
+
: chalk.gray;
|
1657
|
+
const resultIcon = event.result === 'success' ? '✅' : event.result === 'failure' ? '❌' : '⚠️';
|
1658
|
+
console.log(` ${resultIcon} ${chalk.bold(event.eventType)} ${riskColor(`[${event.riskLevel}]`)}`);
|
1659
|
+
console.log(` ${chalk.gray(time)} | ${event.action} | ${event.resource}`);
|
1660
|
+
if (event.userId) {
|
1661
|
+
console.log(` 👤 User: ${event.userId}`);
|
1662
|
+
}
|
1663
|
+
if (event.details && Object.keys(event.details).length > 0) {
|
1664
|
+
const details = Object.entries(event.details)
|
1665
|
+
.filter(([key]) => !['method', 'component'].includes(key))
|
1666
|
+
.slice(0, 3) // Limit to 3 details
|
1667
|
+
.map(([key, value]) => `${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`)
|
1668
|
+
.join(', ');
|
1669
|
+
if (details) {
|
1670
|
+
console.log(` 📋 ${details}`);
|
1671
|
+
}
|
1672
|
+
}
|
1673
|
+
});
|
1674
|
+
});
|
1675
|
+
console.log(chalk.gray(`\nTotal events shown: ${events.length}`));
|
1676
|
+
// Log the audit access itself
|
1677
|
+
auditLogger.log({
|
1678
|
+
eventType: AuditEventType.AUDIT_LOG_ACCESS,
|
1679
|
+
action: 'audit_view',
|
1680
|
+
resource: 'audit_logs',
|
1681
|
+
result: 'success',
|
1682
|
+
riskLevel: 'low',
|
1683
|
+
complianceTags: ['audit', 'access_control'],
|
1684
|
+
details: {
|
1685
|
+
filterCriteria: options,
|
1686
|
+
resultsReturned: events.length,
|
1687
|
+
},
|
1688
|
+
metadata: {
|
1689
|
+
version: '1.0.0',
|
1690
|
+
environment: process.env.NODE_ENV || 'development',
|
1691
|
+
component: 'audit_cli',
|
1692
|
+
},
|
1693
|
+
});
|
1694
|
+
}
|
1695
|
+
catch (error) {
|
1696
|
+
console.log(chalk.red('❌ Failed to retrieve audit events'));
|
1697
|
+
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
1698
|
+
}
|
1699
|
+
});
|
1700
|
+
program
|
1701
|
+
.command('audit:report')
|
1702
|
+
.description('Generate compliance report for specified time period')
|
1703
|
+
.argument('<startDate>', 'Start date in ISO format (YYYY-MM-DD)')
|
1704
|
+
.argument('<endDate>', 'End date in ISO format (YYYY-MM-DD)')
|
1705
|
+
.action((startDate, endDate) => {
|
1706
|
+
try {
|
1707
|
+
console.log(chalk.blue.bold('📊 Compliance Report Generation'));
|
1708
|
+
console.log(chalk.gray('━'.repeat(50)));
|
1709
|
+
const report = auditLogger.generateComplianceReport(startDate, endDate);
|
1710
|
+
console.log(chalk.bold('\n📅 Report Period:'));
|
1711
|
+
console.log(` From: ${new Date(report.period.start).toLocaleDateString()}`);
|
1712
|
+
console.log(` To: ${new Date(report.period.end).toLocaleDateString()}`);
|
1713
|
+
console.log(chalk.bold('\n📈 Summary Statistics:'));
|
1714
|
+
console.log(` Total Events: ${report.totalEvents.toLocaleString()}`);
|
1715
|
+
console.log(chalk.bold('\n🏷️ Risk Distribution:'));
|
1716
|
+
Object.entries(report.riskSummary).forEach(([level, count]) => {
|
1717
|
+
const color = level === 'critical'
|
1718
|
+
? chalk.red
|
1719
|
+
: level === 'high'
|
1720
|
+
? chalk.yellow
|
1721
|
+
: level === 'medium'
|
1722
|
+
? chalk.blue
|
1723
|
+
: chalk.gray;
|
1724
|
+
console.log(` ${color(level.charAt(0).toUpperCase() + level.slice(1))}: ${count}`);
|
1725
|
+
});
|
1726
|
+
if (Object.keys(report.eventsByType).length > 0) {
|
1727
|
+
console.log(chalk.bold('\n📋 Events by Type:'));
|
1728
|
+
Object.entries(report.eventsByType)
|
1729
|
+
.sort(([, a], [, b]) => b - a)
|
1730
|
+
.slice(0, 10) // Top 10
|
1731
|
+
.forEach(([type, count]) => {
|
1732
|
+
console.log(` ${type}: ${count}`);
|
1733
|
+
});
|
1734
|
+
}
|
1735
|
+
if (report.complianceViolations.length > 0) {
|
1736
|
+
console.log(chalk.red.bold('\n🚨 Compliance Violations:'));
|
1737
|
+
report.complianceViolations.slice(0, 5).forEach(violation => {
|
1738
|
+
console.log(` ❌ ${violation.eventType} - ${violation.action}`);
|
1739
|
+
console.log(` ${new Date(violation.timestamp).toLocaleString()}`);
|
1740
|
+
});
|
1741
|
+
if (report.complianceViolations.length > 5) {
|
1742
|
+
console.log(chalk.red(` ... and ${report.complianceViolations.length - 5} more violations`));
|
1743
|
+
}
|
1744
|
+
}
|
1745
|
+
if (report.recommendations.length > 0) {
|
1746
|
+
console.log(chalk.cyan.bold('\n💡 Recommendations:'));
|
1747
|
+
report.recommendations.forEach(rec => {
|
1748
|
+
console.log(` • ${rec}`);
|
1749
|
+
});
|
1750
|
+
}
|
1751
|
+
// Log the compliance report generation
|
1752
|
+
auditLogger.log({
|
1753
|
+
eventType: AuditEventType.COMPLIANCE_REPORT,
|
1754
|
+
action: 'report_generation',
|
1755
|
+
resource: 'audit_system',
|
1756
|
+
result: 'success',
|
1757
|
+
riskLevel: 'low',
|
1758
|
+
complianceTags: ['compliance', 'reporting'],
|
1759
|
+
details: {
|
1760
|
+
reportPeriod: report.period,
|
1761
|
+
totalEvents: report.totalEvents,
|
1762
|
+
violationsFound: report.complianceViolations.length,
|
1763
|
+
},
|
1764
|
+
metadata: {
|
1765
|
+
version: '1.0.0',
|
1766
|
+
environment: process.env.NODE_ENV || 'development',
|
1767
|
+
component: 'audit_cli',
|
1768
|
+
},
|
1769
|
+
});
|
1770
|
+
console.log(chalk.green('\n✅ Compliance report generated successfully'));
|
1771
|
+
}
|
1772
|
+
catch (error) {
|
1773
|
+
console.log(chalk.red('❌ Failed to generate compliance report'));
|
1774
|
+
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
1775
|
+
}
|
1776
|
+
});
|
1777
|
+
program
|
1778
|
+
.command('audit:export')
|
1779
|
+
.description('Export audit data in various formats')
|
1780
|
+
.argument('<format>', 'Export format: json, csv, or xml')
|
1781
|
+
.option('-o, --output <file>', 'Output file name', 'audit-export')
|
1782
|
+
.option('-t, --type <eventType>', 'Filter by event type')
|
1783
|
+
.option('-u, --user <userId>', 'Filter by user ID')
|
1784
|
+
.option('-f, --from <date>', 'Filter events from date (ISO format)')
|
1785
|
+
.option('--to <date>', 'Filter events to date (ISO format)')
|
1786
|
+
.action((format, options) => {
|
1787
|
+
try {
|
1788
|
+
const events = auditLogger.query({
|
1789
|
+
eventType: options.type,
|
1790
|
+
userId: options.user,
|
1791
|
+
dateFrom: options.from,
|
1792
|
+
dateTo: options.to,
|
1793
|
+
});
|
1794
|
+
if (events.length === 0) {
|
1795
|
+
console.log(chalk.yellow('No audit events found to export.'));
|
1796
|
+
return;
|
1797
|
+
}
|
1798
|
+
const fileName = `${options.output}.${format}`;
|
1799
|
+
const exportData = auditLogger.exportAuditData(format);
|
1800
|
+
fs.writeFileSync(fileName, exportData);
|
1801
|
+
console.log(chalk.green(`✅ Exported ${events.length} audit events to ${fileName}`));
|
1802
|
+
console.log(chalk.gray(`Format: ${format.toUpperCase()} | Size: ${exportData.length} bytes`));
|
1803
|
+
// Log the export event
|
1804
|
+
auditLogger.log({
|
1805
|
+
eventType: AuditEventType.AUDIT_LOG_ACCESS,
|
1806
|
+
action: 'audit_export',
|
1807
|
+
resource: 'audit_logs',
|
1808
|
+
resourceId: fileName,
|
1809
|
+
result: 'success',
|
1810
|
+
riskLevel: 'medium', // Exporting sensitive audit data
|
1811
|
+
complianceTags: ['audit', 'data_export'],
|
1812
|
+
details: {
|
1813
|
+
format,
|
1814
|
+
recordsExported: events.length,
|
1815
|
+
fileName,
|
1816
|
+
filterCriteria: {
|
1817
|
+
eventType: options.type,
|
1818
|
+
userId: options.user,
|
1819
|
+
dateFrom: options.from,
|
1820
|
+
dateTo: options.to,
|
1821
|
+
},
|
1822
|
+
},
|
1823
|
+
metadata: {
|
1824
|
+
version: '1.0.0',
|
1825
|
+
environment: process.env.NODE_ENV || 'development',
|
1826
|
+
component: 'audit_cli',
|
1827
|
+
},
|
1828
|
+
});
|
1829
|
+
}
|
1830
|
+
catch (error) {
|
1831
|
+
console.log(chalk.red('❌ Failed to export audit data'));
|
1832
|
+
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
1833
|
+
}
|
1834
|
+
});
|
1835
|
+
program
|
1836
|
+
.command('audit:stats')
|
1837
|
+
.description('Show audit log statistics and health metrics')
|
1838
|
+
.action(() => {
|
1839
|
+
try {
|
1840
|
+
// This is a simple stats command - in real implementation,
|
1841
|
+
// we'd want to expose more audit logger internal stats
|
1842
|
+
console.log(chalk.blue.bold('📊 Audit System Statistics'));
|
1843
|
+
console.log(chalk.gray('━'.repeat(50)));
|
1844
|
+
console.log(chalk.bold('\n🗂️ Log File Information:'));
|
1845
|
+
try {
|
1846
|
+
const auditLogPath = path.resolve(process.cwd(), 'audit.log');
|
1847
|
+
if (fs.existsSync(auditLogPath)) {
|
1848
|
+
const stats = fs.statSync(auditLogPath);
|
1849
|
+
console.log(` Location: ${auditLogPath}`);
|
1850
|
+
console.log(` Size: ${stats.size} bytes (${(stats.size / 1024).toFixed(1)} KB)`);
|
1851
|
+
console.log(` Modified: ${stats.mtime.toLocaleString()}`);
|
1852
|
+
console.log(` Encrypted: ${auditLogger['config'].encryptionEnabled ? 'Yes' : 'No'}`);
|
1853
|
+
}
|
1854
|
+
else {
|
1855
|
+
console.log(chalk.yellow(' No audit log file found'));
|
1856
|
+
}
|
1857
|
+
}
|
1858
|
+
catch (error) {
|
1859
|
+
console.log(chalk.red(' Error reading log file'));
|
1860
|
+
}
|
1861
|
+
console.log(chalk.bold('\n⚙️ System Configuration:'));
|
1862
|
+
console.log(` Enabled: ${auditLogger['config'].enabled ? chalk.green('Yes') : chalk.red('No')}`);
|
1863
|
+
console.log(` Retention: ${auditLogger['config'].retentionDays} days`);
|
1864
|
+
console.log(` Max File Size: ${auditLogger['config'].maxFileSize} MB`);
|
1865
|
+
console.log(` Alert Threshold: ${auditLogger['config'].alertThresholds.riskLevelThreshold}`);
|
1866
|
+
console.log(` Compliance Frameworks: ${auditLogger['config'].complianceFrameworks.join(', ')}`);
|
1867
|
+
console.log(chalk.bold('\n🎯 Quick Commands:'));
|
1868
|
+
console.log(` ${chalk.cyan('toknxr audit:view')} - View recent audit events`);
|
1869
|
+
console.log(` ${chalk.cyan('toknxr audit:report 2025-01-01 2025-01-31')} - Generate compliance report`);
|
1870
|
+
console.log(` ${chalk.cyan('toknxr audit:export json --output my-audit')} - Export audit data`);
|
1871
|
+
// Log the stats access
|
1872
|
+
auditLogger.log({
|
1873
|
+
eventType: AuditEventType.AUDIT_LOG_ACCESS,
|
1874
|
+
action: 'audit_stats_view',
|
1875
|
+
resource: 'audit_system',
|
1876
|
+
result: 'success',
|
1877
|
+
riskLevel: 'low',
|
1878
|
+
complianceTags: ['audit', 'monitoring'],
|
1879
|
+
details: { accessedVia: 'cli_stats_command' },
|
1880
|
+
metadata: {
|
1881
|
+
version: '1.0.0',
|
1882
|
+
environment: process.env.NODE_ENV || 'development',
|
1883
|
+
component: 'audit_cli',
|
1884
|
+
},
|
1885
|
+
});
|
1886
|
+
}
|
1887
|
+
catch (error) {
|
1888
|
+
console.log(chalk.red('❌ Failed to retrieve audit statistics'));
|
1889
|
+
console.log(chalk.gray(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
1890
|
+
}
|
1891
|
+
});
|
1892
|
+
// ---------------------------------------------------------------------------
|
1893
|
+
// Doctor: environment and runtime validator
|
1894
|
+
// ---------------------------------------------------------------------------
|
1895
|
+
program
|
1896
|
+
.command('doctor')
|
1897
|
+
.description('Validate environment, config, and runtime readiness')
|
1898
|
+
.action(async () => {
|
1899
|
+
const results = [];
|
1900
|
+
// Filesystem checks
|
1901
|
+
const configPath = path.resolve(process.cwd(), 'toknxr.config.json');
|
1902
|
+
let providerConfig;
|
1903
|
+
if (fs.existsSync(configPath)) {
|
1904
|
+
results.push({
|
1905
|
+
label: `Provider config at ${configPath}`,
|
1906
|
+
ok: true,
|
1907
|
+
});
|
1908
|
+
try {
|
1909
|
+
const configFile = fs.readFileSync(configPath, 'utf8');
|
1910
|
+
providerConfig = JSON.parse(configFile);
|
1911
|
+
}
|
1912
|
+
catch (error) {
|
1913
|
+
results.push({
|
1914
|
+
label: `Parse toknxr.config.json`,
|
1915
|
+
ok: false,
|
1916
|
+
hint: `Error parsing config file: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
1917
|
+
});
|
1918
|
+
}
|
1919
|
+
}
|
1920
|
+
else {
|
1921
|
+
results.push({
|
1922
|
+
label: `Provider config at ${configPath}`,
|
1923
|
+
ok: false,
|
1924
|
+
hint: 'Run: toknxr init',
|
1925
|
+
});
|
1926
|
+
}
|
1927
|
+
// Environment variable checks for each configured provider
|
1928
|
+
if (providerConfig && providerConfig.providers) {
|
1929
|
+
for (const provider of providerConfig.providers) {
|
1930
|
+
if (provider.apiKeyEnvVar) {
|
1931
|
+
const apiKey = process.env[provider.apiKeyEnvVar];
|
1932
|
+
results.push({
|
1933
|
+
label: `${provider.name} API Key (${provider.apiKeyEnvVar})`,
|
1934
|
+
ok: !!apiKey,
|
1935
|
+
hint: `Set ${provider.apiKeyEnvVar} in your environment or .env file`,
|
1936
|
+
});
|
1937
|
+
}
|
1938
|
+
else {
|
1939
|
+
results.push({
|
1940
|
+
label: `${provider.name} API Key`,
|
1941
|
+
ok: true,
|
1942
|
+
hint: 'No API key required for this provider',
|
1943
|
+
});
|
1944
|
+
}
|
1945
|
+
}
|
1946
|
+
}
|
1947
|
+
else if (providerConfig) {
|
1948
|
+
results.push({
|
1949
|
+
label: 'No providers configured in toknxr.config.json',
|
1950
|
+
ok: false,
|
1951
|
+
hint: 'Edit toknxr.config.json to add AI providers',
|
1952
|
+
});
|
1953
|
+
}
|
1954
|
+
const logPath = path.resolve(process.cwd(), 'interactions.log');
|
1955
|
+
try {
|
1956
|
+
// Touch file if missing (no-op if exists)
|
1957
|
+
if (!fs.existsSync(logPath))
|
1958
|
+
fs.writeFileSync(logPath, '');
|
1959
|
+
results.push({ label: `interactions.log at ${logPath}`, ok: true });
|
1960
|
+
}
|
1961
|
+
catch {
|
1962
|
+
results.push({ label: `interactions.log at ${logPath}`, ok: false, hint: 'Check write permissions' });
|
1963
|
+
}
|
1964
|
+
// Runtime checks (proxy health if running)
|
1965
|
+
let proxyHealthOk = false;
|
1966
|
+
try {
|
1967
|
+
const res = await fetch('http://localhost:8788/health');
|
1968
|
+
proxyHealthOk = res.ok;
|
1969
|
+
}
|
1970
|
+
catch {
|
1971
|
+
proxyHealthOk = false;
|
1972
|
+
}
|
1973
|
+
results.push({
|
1974
|
+
label: 'Proxy server running (http://localhost:8788/health)',
|
1975
|
+
ok: proxyHealthOk,
|
1976
|
+
hint: 'Run: toknxr start (then retry doctor)'
|
1977
|
+
});
|
1978
|
+
// AI Provider Connectivity Tests (only if proxy is running and config is loaded)
|
1979
|
+
let anyProviderConnected = false;
|
1980
|
+
if (proxyHealthOk && providerConfig && providerConfig.providers) {
|
1981
|
+
for (const provider of providerConfig.providers) {
|
1982
|
+
const apiKey = provider.apiKeyEnvVar ? process.env[provider.apiKeyEnvVar] : undefined;
|
1983
|
+
if (provider.apiKeyEnvVar && !apiKey) {
|
1984
|
+
results.push({
|
1985
|
+
label: `${provider.name} connection test`,
|
1986
|
+
ok: false,
|
1987
|
+
hint: `Skipped: API key (${provider.apiKeyEnvVar}) not set.`,
|
1988
|
+
});
|
1989
|
+
continue;
|
1990
|
+
}
|
1991
|
+
const { ok, message } = await testConnection(provider, apiKey);
|
1992
|
+
if (ok) {
|
1993
|
+
anyProviderConnected = true;
|
1994
|
+
}
|
1995
|
+
results.push({
|
1996
|
+
label: `${provider.name} connection test`,
|
1997
|
+
ok: ok,
|
1998
|
+
hint: ok ? undefined : message,
|
1999
|
+
});
|
2000
|
+
}
|
2001
|
+
}
|
2002
|
+
else if (proxyHealthOk && !providerConfig) {
|
2003
|
+
results.push({
|
2004
|
+
label: 'AI Provider connection tests',
|
2005
|
+
ok: false,
|
2006
|
+
hint: 'Skipped: toknxr.config.json not loaded or invalid.',
|
2007
|
+
});
|
2008
|
+
}
|
2009
|
+
else if (!proxyHealthOk) {
|
2010
|
+
results.push({
|
2011
|
+
label: 'AI Provider connection tests',
|
2012
|
+
ok: false,
|
2013
|
+
hint: 'Skipped: Proxy server is not running.',
|
2014
|
+
});
|
2015
|
+
}
|
2016
|
+
// Conditional: add one sample interaction if none exists and at least one provider connected
|
2017
|
+
const logFileExists = fs.existsSync(logPath);
|
2018
|
+
let logFileSize = 0;
|
2019
|
+
if (logFileExists) {
|
2020
|
+
try {
|
2021
|
+
logFileSize = fs.statSync(logPath).size;
|
2022
|
+
}
|
2023
|
+
catch { }
|
2024
|
+
}
|
2025
|
+
if (anyProviderConnected && logFileSize === 0) {
|
2026
|
+
const firstConnectedProvider = providerConfig?.providers.find((p) => {
|
2027
|
+
const apiKey = p.apiKeyEnvVar ? process.env[p.apiKeyEnvVar] : undefined;
|
2028
|
+
return p.apiKeyEnvVar ? !!apiKey : true; // Check if key is set for providers that need it
|
2029
|
+
});
|
2030
|
+
if (firstConnectedProvider) {
|
2031
|
+
const { ok, message } = generateSampleInteraction(firstConnectedProvider.name, logPath);
|
2032
|
+
results.push({
|
2033
|
+
label: 'Generate sample interaction',
|
2034
|
+
ok: ok,
|
2035
|
+
hint: ok ? 'Run: toknxr stats or toknxr code-analysis' : message,
|
2036
|
+
});
|
2037
|
+
}
|
2038
|
+
}
|
2039
|
+
else if (logFileSize > 0) {
|
2040
|
+
results.push({
|
2041
|
+
label: 'Generate sample interaction',
|
2042
|
+
ok: true,
|
2043
|
+
hint: 'interactions.log already contains data.',
|
2044
|
+
});
|
2045
|
+
}
|
2046
|
+
else {
|
2047
|
+
results.push({
|
2048
|
+
label: 'Generate sample interaction',
|
2049
|
+
ok: false,
|
2050
|
+
hint: 'No connected providers or interactions.log already has data.',
|
2051
|
+
});
|
2052
|
+
}
|
2053
|
+
// Print report
|
2054
|
+
console.log(chalk.blue.bold('\nTokNXR Doctor Report'));
|
2055
|
+
console.log(chalk.gray('━'.repeat(60)));
|
2056
|
+
let allOk = true;
|
2057
|
+
for (const r of results) {
|
2058
|
+
allOk && (allOk = r.ok);
|
2059
|
+
const mark = r.ok ? chalk.green('✔') : chalk.red('✖');
|
2060
|
+
const line = `${mark} ${r.label}` + (r.ok ? '' : chalk.gray(` — ${r.hint}`));
|
2061
|
+
console.log(line);
|
2062
|
+
}
|
2063
|
+
console.log(chalk.gray('━'.repeat(60)));
|
2064
|
+
if (allOk) {
|
2065
|
+
console.log(chalk.green('All essential checks passed. You are ready to use TokNXR.'));
|
2066
|
+
}
|
2067
|
+
else {
|
2068
|
+
console.log(chalk.yellow('Some checks failed. Fix the hints above and re-run: toknxr doctor'));
|
2069
|
+
}
|
2070
|
+
});
|
2071
|
+
// Helper functions for search highlighting
|
2072
|
+
function calcRelevanceScore(interaction, query, fields) {
|
2073
|
+
const queryTerms = query.toLowerCase().split(' ');
|
2074
|
+
let totalScore = 0;
|
2075
|
+
const maxPossibleScore = fields.length * queryTerms.length;
|
2076
|
+
fields.forEach(field => {
|
2077
|
+
const fieldValue = (interaction[field] || '').toString().toLowerCase();
|
2078
|
+
queryTerms.forEach(term => {
|
2079
|
+
if (fieldValue.includes(term)) {
|
2080
|
+
totalScore += 1;
|
2081
|
+
}
|
2082
|
+
});
|
2083
|
+
});
|
2084
|
+
return Math.min(totalScore / maxPossibleScore, 1);
|
2085
|
+
}
|
2086
|
+
function highlightMatch(text, query) {
|
2087
|
+
if (!text)
|
2088
|
+
return text;
|
2089
|
+
const queryTerms = query.toLowerCase().split(' ');
|
2090
|
+
let highlighted = text;
|
2091
|
+
queryTerms.forEach(term => {
|
2092
|
+
const regex = new RegExp(`(${term})`, 'gi');
|
2093
|
+
highlighted = highlighted.replace(regex, chalk.bgYellow.black('$1'));
|
2094
|
+
});
|
2095
|
+
return highlighted;
|
2096
|
+
}
|
2097
|
+
program.exitOverride();
|
2098
|
+
try {
|
2099
|
+
program.parse(process.argv);
|
2100
|
+
}
|
2101
|
+
catch (e) {
|
2102
|
+
// This will catch the exit override and prevent the process from exiting
|
2103
|
+
}
|