@goldensheepai/toknxr-cli 0.2.2 → 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/lib/cli.js +737 -213
- package/lib/ui.js +11 -14
- package/lib/utils.js +117 -0
- package/package.json +1 -1
package/lib/cli.js
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
#!/usr/bin/env node
|
1
2
|
import 'dotenv/config';
|
2
3
|
import { Command } from 'commander';
|
3
4
|
import chalk from 'chalk';
|
4
|
-
import
|
5
|
+
import inquirer from 'inquirer';
|
5
6
|
import { startProxyServer } from './proxy.js';
|
7
|
+
import { testConnection, generateSampleInteraction } from './utils.js';
|
6
8
|
import * as fs from 'node:fs';
|
7
9
|
import * as path from 'node:path';
|
10
|
+
import os from 'os';
|
8
11
|
import open from 'open';
|
9
12
|
import { createStatsOverview, createProviderTable, createQualityBreakdown, createOperationProgress, createInteractiveMenu, createBox, createCostChart, createPaginatedDisplay, createFilterInterface, createSearchInterface, InteractiveDataExplorer, CliStateManager, filterAndSearchInteractions, } from './ui.js';
|
10
13
|
// Gracefully handle broken pipe (e.g., piping output to `head`)
|
@@ -18,28 +21,34 @@ process.stderr.on('error', (err) => {
|
|
18
21
|
});
|
19
22
|
const program = new Command();
|
20
23
|
// ASCII Art Welcome Screen with gradient colors
|
21
|
-
const
|
22
|
-
${chalk.
|
23
|
-
${chalk.blue(' ╚══██╔══╝')}${chalk.hex('#6B5BED')(' ██╔═══██╗')}${chalk.hex('#9B5BED')(' ██║ ██╔╝')}${chalk.hex('#CB5BED')(' ████╗ ██║')}${chalk.hex('#ED5B9B')(' ╚██╗██╔╝')}${chalk.hex('#ED5B6B')(' ██╔══██╗')}
|
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')(' ╚═╝ ╚═╝')}
|
24
|
+
const welcomeMessage = `
|
25
|
+
${chalk.bold.hex('#FFD700')('🐑 Welcome to TokNXR by Golden Sheep AI 🐑')}
|
28
26
|
|
29
|
-
${chalk.cyan('
|
30
|
-
${chalk.white('1. Start tracking:')} ${chalk.yellow('toknxr start')} ${chalk.gray('- Launch the proxy server')}
|
31
|
-
${chalk.white('2. View analytics:')} ${chalk.yellow('toknxr stats')} ${chalk.gray('- See token usage and code quality')}
|
32
|
-
${chalk.white('3. Deep dive:')} ${chalk.yellow('toknxr code-analysis')} ${chalk.gray('- Detailed quality insights')}
|
33
|
-
${chalk.white('4. Interactive menu:')} ${chalk.yellow('toknxr menu')} ${chalk.gray('- Guided command interface')}
|
34
|
-
${chalk.white('5. Set limits:')} ${chalk.yellow('toknxr policy:init')} ${chalk.gray('- Configure spending policies')}
|
35
|
-
${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')}
|
36
28
|
|
37
|
-
${chalk.
|
29
|
+
${chalk.bold.underline('Getting Started Guide:')}
|
38
30
|
|
39
|
-
${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.')}
|
40
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('------------------------------------------------------------')}
|
41
50
|
`;
|
42
|
-
console.log(
|
51
|
+
console.log(welcomeMessage);
|
43
52
|
/**
|
44
53
|
* Generate weekly cost trends for the cost chart visualization
|
45
54
|
*/
|
@@ -75,7 +84,7 @@ function generateWeeklyCostTrends(interactions) {
|
|
75
84
|
}
|
76
85
|
return dailyCosts;
|
77
86
|
}
|
78
|
-
program.name('toknxr').description('AI Effectiveness & Code Quality Analysis CLI').version('0.
|
87
|
+
program.name('toknxr').description('AI Effectiveness & Code Quality Analysis CLI').version('0.3.0');
|
79
88
|
program
|
80
89
|
.command('menu')
|
81
90
|
.description('Interactive menu system for TokNXR operations')
|
@@ -100,36 +109,27 @@ program
|
|
100
109
|
},
|
101
110
|
{ name: chalk.red('⚙️ Initialize') + chalk.gray(' - Set up configuration'), value: 'init' },
|
102
111
|
]);
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
'
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
case 'analysis':
|
125
|
-
// This will auto-trigger the code-analysis command
|
126
|
-
break;
|
127
|
-
case 'hallucinations':
|
128
|
-
// This will trigger hallucination analytics
|
129
|
-
break;
|
130
|
-
case 'init':
|
131
|
-
// This will trigger init command
|
132
|
-
break;
|
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
133
|
}
|
134
134
|
}
|
135
135
|
catch (error) {
|
@@ -327,10 +327,6 @@ program
|
|
327
327
|
// Interactive navigation and context-aware guidance
|
328
328
|
console.log('\n' + chalk.blue.bold('🔍 Interactive Navigation'));
|
329
329
|
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
330
|
-
const rl = readline.createInterface({
|
331
|
-
input: process.stdin,
|
332
|
-
output: process.stdout,
|
333
|
-
});
|
334
330
|
// Analyze current situation for intelligent suggestions
|
335
331
|
const currentSituation = {
|
336
332
|
needsBudgetAttention: grandTotals.costUSD > 40, // 80% of monthly budget
|
@@ -383,94 +379,97 @@ program
|
|
383
379
|
recommended: false,
|
384
380
|
},
|
385
381
|
];
|
386
|
-
console.log(chalk.gray('
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
}
|
410
|
-
}, 500);
|
411
|
-
break;
|
412
|
-
case '2':
|
413
|
-
console.log(chalk.magenta('\n🧠 Navigating to AI performance insights...'));
|
414
|
-
console.log(chalk.cyan('Use: ') +
|
415
|
-
chalk.yellow('toknxr hallucinations') +
|
416
|
-
chalk.gray(' for hallucination analysis'));
|
417
|
-
setTimeout(async () => {
|
418
|
-
try {
|
419
|
-
const { execSync } = await import('child_process');
|
420
|
-
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' });
|
421
|
-
}
|
422
|
-
catch {
|
423
|
-
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr hallucinations'));
|
424
|
-
}
|
425
|
-
}, 500);
|
426
|
-
break;
|
427
|
-
case '3':
|
428
|
-
console.log(chalk.yellow('\n💰 Navigating to budget optimization...'));
|
429
|
-
console.log(chalk.gray('📋 Budget Optimization Strategies:'));
|
430
|
-
console.log(` • Monthly budget: $${(50).toFixed(2)} (spent: $${grandTotals.costUSD.toFixed(2)})`);
|
431
|
-
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)}`);
|
432
|
-
console.log(` • ${avgQuality >= 80 ? 'Try cheaper providers with similar quality' : 'Focus on prompt optimization to reduce request volume'}`);
|
433
|
-
break;
|
434
|
-
case '4':
|
435
|
-
console.log(chalk.green('\n📈 Starting real-time monitoring...'));
|
436
|
-
console.log(chalk.cyan('Use: ') +
|
437
|
-
chalk.yellow('toknxr tail') +
|
438
|
-
chalk.gray(' to monitor live interactions'));
|
439
|
-
setTimeout(async () => {
|
440
|
-
try {
|
441
|
-
const { execSync } = await import('child_process');
|
442
|
-
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' });
|
443
|
-
}
|
444
|
-
catch {
|
445
|
-
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr tail'));
|
446
|
-
}
|
447
|
-
}, 500);
|
448
|
-
break;
|
449
|
-
case '5':
|
450
|
-
console.log(chalk.blue('\n🚀 Starting AI refinement journey...'));
|
451
|
-
console.log(chalk.gray('📝 Improvement Recommendations:'));
|
452
|
-
if (avgQuality < 75) {
|
453
|
-
console.log(chalk.yellow(' • 🎯 Switch to higher-quality AI models (91vs88 scores)'));
|
454
|
-
console.log(chalk.yellow(' • 📝 Use more specific prompt instructions'));
|
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' });
|
455
405
|
}
|
456
|
-
|
457
|
-
console.log(chalk.
|
458
|
-
console.log(chalk.red(' • 🔄 Explore cost-efficient AI providers'));
|
406
|
+
catch {
|
407
|
+
console.log(chalk.gray('Please run: ') + chalk.cyan('toknxr code-analysis'));
|
459
408
|
}
|
460
|
-
|
461
|
-
|
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' });
|
462
420
|
}
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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
|
+
`
|
474
473
|
try {
|
475
474
|
process.chdir('${process.cwd().replace(/\\/g, '\\\\')}');
|
476
475
|
require('tsx/dist/esbuild-register').register();
|
@@ -483,29 +482,27 @@ program
|
|
483
482
|
console.log('Please run: toknxr menu');
|
484
483
|
}
|
485
484
|
`,
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
rl.close();
|
508
|
-
});
|
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`);
|
505
|
+
}
|
509
506
|
});
|
510
507
|
program
|
511
508
|
.command('init')
|
@@ -612,7 +609,7 @@ program
|
|
612
609
|
program
|
613
610
|
.command('code-analysis')
|
614
611
|
.description('Show detailed code quality analysis from coding interactions')
|
615
|
-
.action(() => {
|
612
|
+
.action(async () => {
|
616
613
|
const logFilePath = path.resolve(process.cwd(), 'interactions.log');
|
617
614
|
if (!fs.existsSync(logFilePath)) {
|
618
615
|
console.log(chalk.yellow('No interactions logged yet. Use the `start` command to begin tracking.'));
|
@@ -721,6 +718,103 @@ program
|
|
721
718
|
console.log(' • Consider establishing code review processes for edge cases');
|
722
719
|
}
|
723
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
|
+
}
|
724
818
|
});
|
725
819
|
// Import required modules for new AI analysis commands
|
726
820
|
import { hallucinationDetector } from './hallucination-detector.js';
|
@@ -836,7 +930,7 @@ program
|
|
836
930
|
.description('Show hallucination statistics and trends')
|
837
931
|
.option('-p, --provider <provider>', 'Filter by AI provider')
|
838
932
|
.option('-l, --last <hours>', 'Show last N hours (default: 24)', '24')
|
839
|
-
.action(options => {
|
933
|
+
.action(async (options) => {
|
840
934
|
const analytics = aiAnalytics.generateAnalytics();
|
841
935
|
console.log(chalk.bold.blue('🧠 Hallucination Analytics'));
|
842
936
|
console.log(chalk.gray('━'.repeat(50)));
|
@@ -867,11 +961,98 @@ program
|
|
867
961
|
console.log(` • ${rec}`);
|
868
962
|
});
|
869
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
|
+
}
|
870
1051
|
});
|
871
1052
|
program
|
872
1053
|
.command('providers')
|
873
1054
|
.description('Compare AI provider performance')
|
874
|
-
.action(() => {
|
1055
|
+
.action(async () => {
|
875
1056
|
const analytics = aiAnalytics.generateAnalytics();
|
876
1057
|
console.log(chalk.bold.blue('🔄 AI Provider Comparison'));
|
877
1058
|
console.log(chalk.gray('━'.repeat(50)));
|
@@ -918,18 +1099,172 @@ program
|
|
918
1099
|
console.log(` Best Provider: ${chalk.green(bestProvider[0])} (${bestProvider[1].avgQualityScore}/100 quality)`);
|
919
1100
|
console.log(` Needs Attention: ${chalk.red(worstProvider[0])} (${worstProvider[1].avgQualityScore}/100 quality)`);
|
920
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
|
+
}
|
921
1212
|
});
|
922
1213
|
program
|
923
1214
|
.command('export')
|
924
1215
|
.description('Export analytics data to JSON file')
|
925
|
-
.option('-o, --output <file>', 'Output file path'
|
926
|
-
.action(options => {
|
1216
|
+
.option('-o, --output <file>', 'Output file path')
|
1217
|
+
.action(async (options) => {
|
927
1218
|
try {
|
928
|
-
|
929
|
-
|
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}`));
|
930
1265
|
}
|
931
|
-
catch {
|
932
|
-
console.error(chalk.red('❌ Export failed'));
|
1266
|
+
catch (error) {
|
1267
|
+
console.error(chalk.red('❌ Export failed'), error);
|
933
1268
|
}
|
934
1269
|
});
|
935
1270
|
// Enhanced Interactive Commands - Phase 3+
|
@@ -998,15 +1333,96 @@ program
|
|
998
1333
|
console.log(createPaginatedDisplay(explorer.getCurrentPageData(), pageSize, currentPage, renderInteraction, `🐙 AI Interactions Browser (${explorer.getPaginationInfo().totalItems} total)`));
|
999
1334
|
// Interactive navigation
|
1000
1335
|
const pagination = explorer.getPaginationInfo();
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
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}".`));
|
1010
1426
|
}
|
1011
1427
|
});
|
1012
1428
|
program
|
@@ -1427,10 +1843,10 @@ program
|
|
1427
1843
|
console.log(chalk.gray('━'.repeat(50)));
|
1428
1844
|
console.log(chalk.bold('\n🗂️ Log File Information:'));
|
1429
1845
|
try {
|
1430
|
-
const
|
1431
|
-
if (fs.existsSync(
|
1432
|
-
const stats = fs.statSync(
|
1433
|
-
console.log(` Location: ${
|
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}`);
|
1434
1850
|
console.log(` Size: ${stats.size} bytes (${(stats.size / 1024).toFixed(1)} KB)`);
|
1435
1851
|
console.log(` Modified: ${stats.mtime.toLocaleString()}`);
|
1436
1852
|
console.log(` Encrypted: ${auditLogger['config'].encryptionEnabled ? 'Yes' : 'No'}`);
|
@@ -1481,22 +1897,60 @@ program
|
|
1481
1897
|
.description('Validate environment, config, and runtime readiness')
|
1482
1898
|
.action(async () => {
|
1483
1899
|
const results = [];
|
1484
|
-
// Environment checks
|
1485
|
-
const geminiKey = process.env.GEMINI_API_KEY;
|
1486
|
-
const openaiKey = process.env.OPENAI_API_KEY;
|
1487
|
-
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
1488
|
-
results.push({
|
1489
|
-
label: 'AI Provider API key',
|
1490
|
-
ok: !!(geminiKey || openaiKey || anthropicKey),
|
1491
|
-
hint: 'Set GEMINI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY in your environment',
|
1492
|
-
});
|
1493
1900
|
// Filesystem checks
|
1494
1901
|
const configPath = path.resolve(process.cwd(), 'toknxr.config.json');
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
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
|
+
}
|
1500
1954
|
const logPath = path.resolve(process.cwd(), 'interactions.log');
|
1501
1955
|
try {
|
1502
1956
|
// Touch file if missing (no-op if exists)
|
@@ -1508,30 +1962,94 @@ program
|
|
1508
1962
|
results.push({ label: `interactions.log at ${logPath}`, ok: false, hint: 'Check write permissions' });
|
1509
1963
|
}
|
1510
1964
|
// Runtime checks (proxy health if running)
|
1511
|
-
let
|
1965
|
+
let proxyHealthOk = false;
|
1512
1966
|
try {
|
1513
1967
|
const res = await fetch('http://localhost:8788/health');
|
1514
|
-
|
1968
|
+
proxyHealthOk = res.ok;
|
1515
1969
|
}
|
1516
1970
|
catch {
|
1517
|
-
|
1971
|
+
proxyHealthOk = false;
|
1518
1972
|
}
|
1519
1973
|
results.push({
|
1520
|
-
label: 'Proxy
|
1521
|
-
ok:
|
1974
|
+
label: 'Proxy server running (http://localhost:8788/health)',
|
1975
|
+
ok: proxyHealthOk,
|
1522
1976
|
hint: 'Run: toknxr start (then retry doctor)'
|
1523
1977
|
});
|
1524
|
-
//
|
1525
|
-
|
1526
|
-
|
1527
|
-
const
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
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;
|
1532
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
|
+
});
|
1533
2052
|
}
|
1534
|
-
catch { }
|
1535
2053
|
// Print report
|
1536
2054
|
console.log(chalk.blue.bold('\nTokNXR Doctor Report'));
|
1537
2055
|
console.log(chalk.gray('━'.repeat(60)));
|
@@ -1544,7 +2062,7 @@ program
|
|
1544
2062
|
}
|
1545
2063
|
console.log(chalk.gray('━'.repeat(60)));
|
1546
2064
|
if (allOk) {
|
1547
|
-
console.log(chalk.green('All checks passed. You are ready to use TokNXR.'));
|
2065
|
+
console.log(chalk.green('All essential checks passed. You are ready to use TokNXR.'));
|
1548
2066
|
}
|
1549
2067
|
else {
|
1550
2068
|
console.log(chalk.yellow('Some checks failed. Fix the hints above and re-run: toknxr doctor'));
|
@@ -1576,4 +2094,10 @@ function highlightMatch(text, query) {
|
|
1576
2094
|
});
|
1577
2095
|
return highlighted;
|
1578
2096
|
}
|
1579
|
-
program.
|
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
|
+
}
|
package/lib/ui.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import chalk from 'chalk';
|
2
|
-
import
|
2
|
+
import inquirer from 'inquirer';
|
3
3
|
export const createStatsOverview = (cost, requests, waste, hallucinations) => `
|
4
4
|
${chalk.bold.blue('📊 TokNXR Analytics Overview')}
|
5
5
|
${chalk.gray('------------------------------------')}
|
@@ -45,19 +45,16 @@ export const createOperationProgress = (title, steps) => {
|
|
45
45
|
console.log(chalk.bold.blue(title));
|
46
46
|
return spinner;
|
47
47
|
};
|
48
|
-
export const createInteractiveMenu = (options) => {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
}
|
56
|
-
|
57
|
-
|
58
|
-
resolve(answer);
|
59
|
-
});
|
60
|
-
});
|
48
|
+
export const createInteractiveMenu = async (options) => {
|
49
|
+
const { choice } = await inquirer.prompt([
|
50
|
+
{
|
51
|
+
type: 'list',
|
52
|
+
name: 'choice',
|
53
|
+
message: 'Select an operation:',
|
54
|
+
choices: options,
|
55
|
+
},
|
56
|
+
]);
|
57
|
+
return choice;
|
61
58
|
};
|
62
59
|
export const createBox = (title, content, options) => {
|
63
60
|
const { borderColor, titleColor } = options;
|
package/lib/utils.js
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
import axios from 'axios';
|
2
|
+
import * as fs from 'node:fs';
|
3
|
+
// Helper to resolve dot notation paths (copied from proxy.ts for consistency)
|
4
|
+
const getValueFromPath = (obj, path) => {
|
5
|
+
if (!path || !obj)
|
6
|
+
return 0;
|
7
|
+
try {
|
8
|
+
const result = path.split('.').reduce((res, prop) => res && typeof res === 'object' && prop in res ? res[prop] : undefined, obj);
|
9
|
+
return Number(result) || 0;
|
10
|
+
}
|
11
|
+
catch {
|
12
|
+
return 0;
|
13
|
+
}
|
14
|
+
};
|
15
|
+
// Minimal payload for testing various providers
|
16
|
+
const TEST_PAYLOADS = {
|
17
|
+
'gemini': {
|
18
|
+
contents: [{ parts: [{ text: "Hello" }] }]
|
19
|
+
},
|
20
|
+
'openai': {
|
21
|
+
model: "gpt-3.5-turbo",
|
22
|
+
messages: [{ role: "user", content: "Hello" }]
|
23
|
+
},
|
24
|
+
'anthropic': {
|
25
|
+
model: "claude-3-opus-20240229",
|
26
|
+
messages: [{ role: "user", content: "Hello" }],
|
27
|
+
max_tokens: 10
|
28
|
+
},
|
29
|
+
'ollama': {
|
30
|
+
model: "llama3",
|
31
|
+
prompt: "Hello",
|
32
|
+
stream: false
|
33
|
+
}
|
34
|
+
};
|
35
|
+
export async function testConnection(provider, apiKey) {
|
36
|
+
const proxyUrl = `http://localhost:8788${provider.routePrefix}`;
|
37
|
+
const testPayload = TEST_PAYLOADS[provider.name.toLowerCase().split('-')[0]] || TEST_PAYLOADS['gemini']; // Default to gemini payload
|
38
|
+
if (!testPayload) {
|
39
|
+
return { ok: false, message: `No test payload defined for provider: ${provider.name}` };
|
40
|
+
}
|
41
|
+
try {
|
42
|
+
const headers = { 'Content-Type': 'application/json' };
|
43
|
+
// The proxy handles adding the actual API key to the upstream request
|
44
|
+
// We just need to ensure the proxy itself is reachable and responds
|
45
|
+
// For the test, we don't need to pass the API key here, as the proxy will add it.
|
46
|
+
// However, if the proxy itself fails due to missing key, that's what we want to catch.
|
47
|
+
const res = await axios.post(proxyUrl, testPayload, {
|
48
|
+
headers,
|
49
|
+
timeout: 5000, // 5 second timeout
|
50
|
+
validateStatus: () => true, // Don't throw on non-2xx status codes
|
51
|
+
});
|
52
|
+
if (res.status === 200) {
|
53
|
+
// Basic check for a valid response structure
|
54
|
+
if (res.data && (res.data.candidates || res.data.choices || res.data.response)) {
|
55
|
+
return { ok: true };
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
return { ok: false, message: `Unexpected response format from ${provider.name}` };
|
59
|
+
}
|
60
|
+
}
|
61
|
+
else if (res.status === 401 || res.status === 403) {
|
62
|
+
return { ok: false, message: `Authentication failed (Status: ${res.status}). Check API key.` };
|
63
|
+
}
|
64
|
+
else if (res.status === 429) {
|
65
|
+
return { ok: false, message: `Rate limit exceeded (Status: ${res.status}).` };
|
66
|
+
}
|
67
|
+
else if (res.status === 500 && res.data && res.data.error) {
|
68
|
+
// Catch specific proxy errors, e.g., "API key not set"
|
69
|
+
return { ok: false, message: `Proxy error: ${res.data.error}` };
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
return { ok: false, message: `Proxy returned status ${res.status}: ${JSON.stringify(res.data)}` };
|
73
|
+
}
|
74
|
+
}
|
75
|
+
catch (error) {
|
76
|
+
if (axios.isAxiosError(error)) {
|
77
|
+
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
|
78
|
+
return { ok: false, message: `Proxy not running or unreachable at ${proxyUrl}.` };
|
79
|
+
}
|
80
|
+
else if (error.response) {
|
81
|
+
return { ok: false, message: `Proxy returned status ${error.response.status}: ${JSON.stringify(error.response.data)}` };
|
82
|
+
}
|
83
|
+
return { ok: false, message: `Network error: ${error.message}` };
|
84
|
+
}
|
85
|
+
return { ok: false, message: `Unknown error during connection test: ${error.message}` };
|
86
|
+
}
|
87
|
+
}
|
88
|
+
export function generateSampleInteraction(providerName, logFilePath) {
|
89
|
+
const sampleInteraction = {
|
90
|
+
requestId: `sample-${Date.now()}`,
|
91
|
+
timestamp: new Date().toISOString(),
|
92
|
+
provider: providerName,
|
93
|
+
model: `${providerName}-test-model`,
|
94
|
+
promptTokens: 10,
|
95
|
+
completionTokens: 20,
|
96
|
+
totalTokens: 30,
|
97
|
+
costUSD: 0.0005,
|
98
|
+
taskType: 'chat',
|
99
|
+
userPrompt: 'Generate a sample interaction for testing.',
|
100
|
+
aiResponse: 'This is a sample AI response generated by the doctor command.',
|
101
|
+
codeQualityScore: 85,
|
102
|
+
effectivenessScore: 90,
|
103
|
+
hallucinationDetection: {
|
104
|
+
isLikelyHallucination: false,
|
105
|
+
confidence: 5,
|
106
|
+
severity: 'low',
|
107
|
+
issues: []
|
108
|
+
}
|
109
|
+
};
|
110
|
+
try {
|
111
|
+
fs.appendFileSync(logFilePath, JSON.stringify(sampleInteraction) + '\n');
|
112
|
+
return { ok: true, message: `Generated sample interaction for ${providerName}.` };
|
113
|
+
}
|
114
|
+
catch (error) {
|
115
|
+
return { ok: false, message: `Failed to write sample interaction: ${error.message}` };
|
116
|
+
}
|
117
|
+
}
|