@goldensheepai/toknxr-cli 0.2.0 → 0.2.2
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 +1309 -112
- 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 +132 -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
@@ -0,0 +1,266 @@
|
|
1
|
+
/**
|
2
|
+
* Plugin System for TokNxr Code Analysis
|
3
|
+
* Allows extensible analysis capabilities for custom analyzers
|
4
|
+
*/
|
5
|
+
/**
|
6
|
+
* Plugin registry and management
|
7
|
+
*/
|
8
|
+
export class PluginManager {
|
9
|
+
constructor() {
|
10
|
+
this.plugins = new Map();
|
11
|
+
}
|
12
|
+
/**
|
13
|
+
* Register a new plugin
|
14
|
+
*/
|
15
|
+
register(plugin) {
|
16
|
+
this.plugins.set(plugin.name, plugin);
|
17
|
+
}
|
18
|
+
/**
|
19
|
+
* Unregister a plugin
|
20
|
+
*/
|
21
|
+
unregister(name) {
|
22
|
+
return this.plugins.delete(name);
|
23
|
+
}
|
24
|
+
/**
|
25
|
+
* Get all registered plugins
|
26
|
+
*/
|
27
|
+
getPlugins() {
|
28
|
+
return Array.from(this.plugins.values());
|
29
|
+
}
|
30
|
+
/**
|
31
|
+
* Get plugins that support a specific language
|
32
|
+
*/
|
33
|
+
getPluginsForLanguage(language) {
|
34
|
+
return Array.from(this.plugins.values())
|
35
|
+
.filter(plugin => plugin.supportedLanguages.includes(language));
|
36
|
+
}
|
37
|
+
/**
|
38
|
+
* Analyze code using all applicable plugins
|
39
|
+
*/
|
40
|
+
analyzeWithPlugins(code, baseMetrics, plugins) {
|
41
|
+
const applicablePlugins = plugins || this.getPluginsForLanguage(baseMetrics.language || 'unknown');
|
42
|
+
// Apply plugin analyses
|
43
|
+
for (const plugin of applicablePlugins) {
|
44
|
+
if (plugin.analyzeQuality && plugin.validateSyntax && plugin.calculateReadability) {
|
45
|
+
try {
|
46
|
+
const pluginResults = plugin.analyzeQuality(code, baseMetrics.language || 'unknown');
|
47
|
+
// Merge plugin results
|
48
|
+
if (pluginResults.pluginMetrics) {
|
49
|
+
baseMetrics.pluginMetrics = {
|
50
|
+
...baseMetrics.pluginMetrics,
|
51
|
+
[plugin.name]: pluginResults.pluginMetrics
|
52
|
+
};
|
53
|
+
}
|
54
|
+
// Override base metrics if plugin provides better validation
|
55
|
+
if (pluginResults.syntaxValid !== undefined) {
|
56
|
+
const pluginValid = plugin.validateSyntax(code, baseMetrics.language || 'unknown');
|
57
|
+
if (!pluginValid && baseMetrics.syntaxValid) {
|
58
|
+
// Plugin detected issues that base validation missed
|
59
|
+
baseMetrics.syntaxValid = false;
|
60
|
+
baseMetrics.potentialIssues.push(`${plugin.name}: syntax validation failed`);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
// Enhance readability if plugin provides better calculation
|
64
|
+
if (pluginResults.estimatedReadability !== undefined) {
|
65
|
+
const pluginReadability = plugin.calculateReadability(code, baseMetrics.language || 'unknown');
|
66
|
+
// Use the better (higher) readability score
|
67
|
+
if (pluginReadability > baseMetrics.estimatedReadability) {
|
68
|
+
baseMetrics.estimatedReadability = pluginReadability;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
// Add potential issues from plugin
|
72
|
+
if (pluginResults.potentialIssues && pluginResults.potentialIssues.length > 0) {
|
73
|
+
baseMetrics.potentialIssues.push(...pluginResults.potentialIssues.map(issue => `${plugin.name}: ${issue}`));
|
74
|
+
}
|
75
|
+
}
|
76
|
+
catch (error) {
|
77
|
+
console.warn(`Plugin ${plugin.name} failed during analysis:`, error);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
return baseMetrics;
|
82
|
+
}
|
83
|
+
/**
|
84
|
+
* Score effectiveness using plugins
|
85
|
+
*/
|
86
|
+
scoreEffectivenessWithPlugins(userPrompt, aiResponse, baseScore, extractedCode, plugins) {
|
87
|
+
const code = extractedCode || aiResponse;
|
88
|
+
const codeMetrics = { language: this.detectLanguageFromCode(code) };
|
89
|
+
const applicablePlugins = plugins || this.getPluginsForLanguage(codeMetrics.language || 'unknown');
|
90
|
+
// Apply plugin effectiveness analysis
|
91
|
+
for (const plugin of applicablePlugins) {
|
92
|
+
if (plugin.analyzeEffectiveness) {
|
93
|
+
try {
|
94
|
+
const pluginResults = plugin.analyzeEffectiveness(userPrompt, aiResponse, extractedCode);
|
95
|
+
// Apply hallucination detection
|
96
|
+
if (plugin.detectHallucinations) {
|
97
|
+
const hallucinations = plugin.detectHallucinations(code, codeMetrics.language || 'unknown');
|
98
|
+
if (hallucinations.length > 0) {
|
99
|
+
const avgConfidence = hallucinations.reduce((sum, h) => sum + h.confidence, 0) / hallucinations.length;
|
100
|
+
baseScore.hallucinationRisk = (baseScore.hallucinationRisk || 0) + (avgConfidence * 0.3);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
// Merge other effectiveness metrics
|
104
|
+
Object.assign(baseScore, pluginResults);
|
105
|
+
}
|
106
|
+
catch (error) {
|
107
|
+
console.warn(`Plugin ${plugin.name} failed during effectiveness scoring:`, error);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
return baseScore;
|
112
|
+
}
|
113
|
+
/**
|
114
|
+
* Detect language from code (helper method)
|
115
|
+
*/
|
116
|
+
detectLanguageFromCode(code) {
|
117
|
+
if (/(?:import|export|function|const|let|var)\s+/.test(code)) {
|
118
|
+
return code.includes('interface') || code.includes(': string') ? 'typescript' : 'javascript';
|
119
|
+
}
|
120
|
+
if (/package\s+\w+|func\s+\w+\(/.test(code))
|
121
|
+
return 'go';
|
122
|
+
if (/fn\s+\w+|use\s+std::/.test(code))
|
123
|
+
return 'rust';
|
124
|
+
if (/public\s+class|import\s+java\./.test(code))
|
125
|
+
return 'java';
|
126
|
+
if (/#include\s+<|std::|cout\s+<<|int\s+main\(/.test(code))
|
127
|
+
return 'cpp';
|
128
|
+
return undefined;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
/**
|
132
|
+
* Built-in security analyzer plugin
|
133
|
+
*/
|
134
|
+
export const SecurityAnalyzerPlugin = {
|
135
|
+
name: 'security-analyzer',
|
136
|
+
version: '1.0.0',
|
137
|
+
description: 'Analyzes code for security vulnerabilities',
|
138
|
+
supportedLanguages: ['javascript', 'typescript', 'python', 'go', 'rust', 'java', 'cpp'],
|
139
|
+
analyzeQuality(code, language) {
|
140
|
+
const issues = [];
|
141
|
+
let securityScore = 100;
|
142
|
+
// Common security issues across languages
|
143
|
+
if (code.includes('eval(') || code.includes('Function(')) {
|
144
|
+
issues.push('Use of eval() or Function() constructor - potential code injection');
|
145
|
+
securityScore -= 30;
|
146
|
+
}
|
147
|
+
if (code.includes('innerHTML') || code.includes('outerHTML')) {
|
148
|
+
issues.push('Direct HTML manipulation - potential XSS vulnerability');
|
149
|
+
securityScore -= 20;
|
150
|
+
}
|
151
|
+
if (/(password|secret|key).*=\s*["'][^"']*["']/.test(code)) {
|
152
|
+
issues.push('Hardcoded credentials detected');
|
153
|
+
securityScore -= 40;
|
154
|
+
}
|
155
|
+
// Language-specific checks
|
156
|
+
switch (language) {
|
157
|
+
case 'javascript':
|
158
|
+
case 'typescript':
|
159
|
+
if (code.includes('document.cookie')) {
|
160
|
+
issues.push('Direct cookie manipulation without secure flags');
|
161
|
+
securityScore -= 15;
|
162
|
+
}
|
163
|
+
break;
|
164
|
+
case 'python':
|
165
|
+
if (code.includes('exec(') || code.includes('eval(')) {
|
166
|
+
issues.push('Use of exec() or eval() in Python - code injection risk');
|
167
|
+
securityScore -= 30;
|
168
|
+
}
|
169
|
+
break;
|
170
|
+
}
|
171
|
+
return {
|
172
|
+
potentialIssues: issues,
|
173
|
+
securityScore: Math.max(0, securityScore)
|
174
|
+
};
|
175
|
+
},
|
176
|
+
detectHallucinations(code, language) {
|
177
|
+
const detections = [];
|
178
|
+
// Check for obviously incorrect API usage patterns
|
179
|
+
if (language === 'javascript' && code.includes('fs.readFile') && !code.includes('import fs')) {
|
180
|
+
detections.push({
|
181
|
+
confidence: 85,
|
182
|
+
type: 'missing_import',
|
183
|
+
message: 'Using fs.readFile without importing fs module'
|
184
|
+
});
|
185
|
+
}
|
186
|
+
if (language === 'python' && code.includes('import requests') && code.includes('requests.get') && !code.includes('response.raise_for_status()')) {
|
187
|
+
detections.push({
|
188
|
+
confidence: 60,
|
189
|
+
type: 'missing_error_handling',
|
190
|
+
message: 'HTTP request without proper error handling'
|
191
|
+
});
|
192
|
+
}
|
193
|
+
return detections;
|
194
|
+
}
|
195
|
+
};
|
196
|
+
/**
|
197
|
+
* Performance analyzer plugin
|
198
|
+
*/
|
199
|
+
export const PerformanceAnalyzerPlugin = {
|
200
|
+
name: 'performance-analyzer',
|
201
|
+
version: '1.0.0',
|
202
|
+
description: 'Analyzes code for performance bottlenecks',
|
203
|
+
supportedLanguages: ['javascript', 'typescript', 'python', 'go', 'rust', 'java', 'cpp'],
|
204
|
+
analyzeQuality(code, language) {
|
205
|
+
const issues = [];
|
206
|
+
let performanceScore = 100;
|
207
|
+
// Detect performance anti-patterns
|
208
|
+
if (/(for.*for|while.*while)/.test(code)) {
|
209
|
+
issues.push('Nested loops detected - potential O(n²) complexity');
|
210
|
+
performanceScore -= 15;
|
211
|
+
}
|
212
|
+
// Memory leaks in JavaScript
|
213
|
+
if (/(setInterval|setTimeout)/.test(code) && !code.includes('clearInterval') && !code.includes('clearTimeout')) {
|
214
|
+
issues.push('Timer without cleanup - potential memory leak');
|
215
|
+
performanceScore -= 20;
|
216
|
+
}
|
217
|
+
// Inefficient string concatenation
|
218
|
+
if (/(\w+\s*\+\s*\w+.*){3,}/.test(code) && !code.includes('join(')) {
|
219
|
+
issues.push('Inefficient string concatenation - consider using array join');
|
220
|
+
performanceScore -= 10;
|
221
|
+
}
|
222
|
+
return {
|
223
|
+
potentialIssues: issues,
|
224
|
+
performanceScore: Math.max(0, performanceScore)
|
225
|
+
};
|
226
|
+
}
|
227
|
+
};
|
228
|
+
/**
|
229
|
+
* Framework detector plugin
|
230
|
+
*/
|
231
|
+
export const FrameworkDetectorPlugin = {
|
232
|
+
name: 'framework-detector',
|
233
|
+
version: '1.0.0',
|
234
|
+
description: 'Detects and analyzes framework-specific code',
|
235
|
+
supportedLanguages: ['javascript', 'typescript', 'python'],
|
236
|
+
analyzeQuality(code, language) {
|
237
|
+
let framework;
|
238
|
+
if (code.includes('import React') || code.includes('from "react"')) {
|
239
|
+
framework = 'react';
|
240
|
+
}
|
241
|
+
else if (code.includes('import Vue') || code.includes('from "vue"')) {
|
242
|
+
framework = 'vue';
|
243
|
+
}
|
244
|
+
else if (code.includes('import { Component }') && code.includes('@Component')) {
|
245
|
+
framework = 'angular';
|
246
|
+
}
|
247
|
+
else if (code.includes('from flask import') || code.includes('import flask')) {
|
248
|
+
framework = 'flask';
|
249
|
+
}
|
250
|
+
else if (code.includes('from django') || code.includes('import django')) {
|
251
|
+
framework = 'django';
|
252
|
+
}
|
253
|
+
else if (code.includes('from fastapi import') || code.includes('import fastapi')) {
|
254
|
+
framework = 'fastapi';
|
255
|
+
}
|
256
|
+
return {
|
257
|
+
framework
|
258
|
+
};
|
259
|
+
}
|
260
|
+
};
|
261
|
+
// Export default plugin manager instance
|
262
|
+
export const pluginManager = new PluginManager();
|
263
|
+
// Register built-in plugins
|
264
|
+
pluginManager.register(SecurityAnalyzerPlugin);
|
265
|
+
pluginManager.register(PerformanceAnalyzerPlugin);
|
266
|
+
pluginManager.register(FrameworkDetectorPlugin);
|
package/lib/sync.js
CHANGED
@@ -13,9 +13,16 @@ export async function syncInteractions(supabase, options) {
|
|
13
13
|
}
|
14
14
|
// Set auth in Supabase
|
15
15
|
setAuthToken(token);
|
16
|
-
|
16
|
+
// Resolve interactions.log robustly across run contexts (repo root vs toknxr-cli)
|
17
|
+
const candidatePaths = [
|
18
|
+
path.resolve(process.cwd(), 'interactions.log'),
|
19
|
+
path.resolve(process.cwd(), 'toknxr-cli', 'interactions.log'),
|
20
|
+
// When executed from built JS in lib/, __dirname points to lib; go up one
|
21
|
+
path.resolve(__dirname, '../interactions.log'),
|
22
|
+
];
|
23
|
+
const logFilePath = candidatePaths.find(p => fs.existsSync(p)) || candidatePaths[0];
|
17
24
|
if (!fs.existsSync(logFilePath)) {
|
18
|
-
console.log(chalk.yellow('No interactions.log file found
|
25
|
+
console.log(chalk.yellow('No interactions.log file found in current directory or toknxr-cli/. Nothing to sync.'));
|
19
26
|
return;
|
20
27
|
}
|
21
28
|
const fileContent = fs.readFileSync(logFilePath, 'utf8');
|
@@ -30,7 +37,7 @@ export async function syncInteractions(supabase, options) {
|
|
30
37
|
const log = JSON.parse(line);
|
31
38
|
logs.push(log);
|
32
39
|
}
|
33
|
-
catch
|
40
|
+
catch {
|
34
41
|
console.warn(`Skipping invalid log entry: ${line}`);
|
35
42
|
}
|
36
43
|
}
|
@@ -71,6 +78,16 @@ export async function syncInteractions(supabase, options) {
|
|
71
78
|
timestamp: new Date(log.timestamp).toISOString(),
|
72
79
|
request_details: log.userPrompt || '',
|
73
80
|
response_details: log.aiResponse || log.extractedCode || '',
|
81
|
+
// Rich metrics from CLI analysis
|
82
|
+
code_quality_score: log.codeQualityScore,
|
83
|
+
effectiveness_score: log.effectivenessScore,
|
84
|
+
language: log.codeQualityMetrics?.language,
|
85
|
+
syntax_valid: log.codeQualityMetrics?.syntaxValid,
|
86
|
+
readability_score: log.codeQualityMetrics?.estimatedReadability,
|
87
|
+
hallucination_detected: log.hallucinationDetection?.isLikelyHallucination,
|
88
|
+
hallucination_confidence: log.hallucinationDetection?.confidence,
|
89
|
+
prompt_clarity_match: log.hallucinationDetection?.promptClarityMatch,
|
90
|
+
task_type: log.taskType,
|
74
91
|
};
|
75
92
|
interactions.push(interaction);
|
76
93
|
}
|
@@ -89,7 +106,12 @@ export async function syncInteractions(supabase, options) {
|
|
89
106
|
console.log(`Successfully synced ${interactions.length} interactions.`);
|
90
107
|
}
|
91
108
|
if (options.clear) {
|
92
|
-
|
93
|
-
|
109
|
+
try {
|
110
|
+
fs.writeFileSync(logFilePath, '');
|
111
|
+
console.log(chalk.gray('Local interactions.log has been cleared.'));
|
112
|
+
}
|
113
|
+
catch {
|
114
|
+
console.log(chalk.yellow('Could not clear interactions.log. Proceeding without clearing.'));
|
115
|
+
}
|
94
116
|
}
|
95
117
|
}
|
package/lib/ui.js
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
import chalk from 'chalk';
|
2
|
+
import readline from 'readline';
|
3
|
+
export const createStatsOverview = (cost, requests, waste, hallucinations) => `
|
4
|
+
${chalk.bold.blue('📊 TokNXR Analytics Overview')}
|
5
|
+
${chalk.gray('------------------------------------')}
|
6
|
+
${chalk.cyan('Total Cost:')} ${chalk.green(`$${cost.toFixed(2)}`)}
|
7
|
+
${chalk.cyan('Total Requests:')} ${chalk.green(requests)}
|
8
|
+
${chalk.cyan('Estimated Waste:')} ${chalk.yellow(`${waste.toFixed(2)}%`)}
|
9
|
+
${chalk.cyan('Hallucination Rate:')} ${chalk.red(`${hallucinations.toFixed(2)}%`)}
|
10
|
+
`;
|
11
|
+
export const createProviderTable = async (stats) => {
|
12
|
+
let table = `${chalk.bold.blue('🤖 Provider Performance')}\n`;
|
13
|
+
table += `${chalk.gray('----------------------------------------------------------------')}\n`;
|
14
|
+
table += `${chalk.bold('Provider')} | ${chalk.bold('Tokens')} | ${chalk.bold('Cost')} | ${chalk.bold('Quality')} | ${chalk.bold('Effectiveness')}\n`;
|
15
|
+
table += `${chalk.gray('----------------------------------------------------------------')}\n`;
|
16
|
+
for (const provider in stats) {
|
17
|
+
const { totalTokens, costUSD, avgQualityScore, avgEffectivenessScore } = stats[provider];
|
18
|
+
table += `${provider.padEnd(15)} | ${totalTokens.toLocaleString().padEnd(10)} | ${`$${costUSD.toFixed(2)}`.padEnd(8)} | ${`${avgQualityScore}/100`.padEnd(10)} | ${`${avgEffectivenessScore}/100`}\n`;
|
19
|
+
}
|
20
|
+
return table;
|
21
|
+
};
|
22
|
+
export const createQualityBreakdown = (interactions) => {
|
23
|
+
const qualityScores = interactions.map(i => i.codeQualityScore || 0);
|
24
|
+
const averageQuality = qualityScores.reduce((a, b) => a + b, 0) / qualityScores.length;
|
25
|
+
return `
|
26
|
+
${chalk.bold.magenta('🔍 Code Quality Breakdown')}
|
27
|
+
${chalk.gray('------------------------------------')}
|
28
|
+
${chalk.cyan('Average Quality Score:')} ${chalk.green(`${averageQuality.toFixed(2)}/100`)}
|
29
|
+
`;
|
30
|
+
};
|
31
|
+
export const createOperationProgress = (title, steps) => {
|
32
|
+
let currentStep = 0;
|
33
|
+
const spinner = {
|
34
|
+
updateProgress: (step) => {
|
35
|
+
currentStep = step;
|
36
|
+
console.log(`${chalk.blue(`[${currentStep + 1}/${steps.length}]`)} ${steps[currentStep]}`);
|
37
|
+
},
|
38
|
+
succeed: (message) => {
|
39
|
+
console.log(chalk.green(message));
|
40
|
+
},
|
41
|
+
fail: (message) => {
|
42
|
+
console.log(chalk.red(message));
|
43
|
+
},
|
44
|
+
};
|
45
|
+
console.log(chalk.bold.blue(title));
|
46
|
+
return spinner;
|
47
|
+
};
|
48
|
+
export const createInteractiveMenu = (options) => {
|
49
|
+
return new Promise(resolve => {
|
50
|
+
console.log(chalk.bold.blue('MENU'));
|
51
|
+
options.forEach(option => console.log(option.name));
|
52
|
+
const rl = readline.createInterface({
|
53
|
+
input: process.stdin,
|
54
|
+
output: process.stdout,
|
55
|
+
});
|
56
|
+
rl.question('Choose an option: ', answer => {
|
57
|
+
rl.close();
|
58
|
+
resolve(answer);
|
59
|
+
});
|
60
|
+
});
|
61
|
+
};
|
62
|
+
export const createBox = (title, content, options) => {
|
63
|
+
const { borderColor, titleColor } = options;
|
64
|
+
const color = chalk[borderColor] || chalk.gray;
|
65
|
+
const titleColored = chalk[titleColor] || chalk.white;
|
66
|
+
let box = color('┌' + '─'.repeat(title.length + 2) + '┐\n');
|
67
|
+
box += color('│ ') + titleColored(title) + color(' │\n');
|
68
|
+
box += color('├' + '─'.repeat(title.length + 2) + '┤\n');
|
69
|
+
content.forEach(line => {
|
70
|
+
box +=
|
71
|
+
color('│ ') +
|
72
|
+
line +
|
73
|
+
color(' '.repeat(Math.max(0, title.length - line.length + 2))) +
|
74
|
+
color(' │\n');
|
75
|
+
});
|
76
|
+
box += color('└' + '─'.repeat(title.length + 2) + '┘');
|
77
|
+
return box;
|
78
|
+
};
|
79
|
+
export const createCostChart = (data) => {
|
80
|
+
// Basic chart for demonstration
|
81
|
+
let chart = `${chalk.bold.blue('Cost Trend (Last 7 Days)')}\n`;
|
82
|
+
const max = Math.max(...data);
|
83
|
+
data.forEach(value => {
|
84
|
+
const bar = '█'.repeat(Math.round((value / max) * 20));
|
85
|
+
chart += `${chalk.green(bar)}\n`;
|
86
|
+
});
|
87
|
+
return chart;
|
88
|
+
};
|
89
|
+
export const createPaginatedDisplay = (data, pageSize, currentPage, render, title) => {
|
90
|
+
let display = title ? `${chalk.bold.blue(title)}\n` : '';
|
91
|
+
data.forEach((item, index) => {
|
92
|
+
display += render(item, (currentPage - 1) * pageSize + index);
|
93
|
+
});
|
94
|
+
return display;
|
95
|
+
};
|
96
|
+
export const createFilterInterface = (currentFilters) => {
|
97
|
+
return new Promise(resolve => {
|
98
|
+
console.log('Filtering interface is not implemented in this mock.');
|
99
|
+
resolve(currentFilters);
|
100
|
+
});
|
101
|
+
};
|
102
|
+
export const createSearchInterface = (fields) => {
|
103
|
+
return new Promise(resolve => {
|
104
|
+
console.log('Search interface is not implemented in this mock.');
|
105
|
+
resolve({ query: 'test', fields });
|
106
|
+
});
|
107
|
+
};
|
108
|
+
export class InteractiveDataExplorer {
|
109
|
+
constructor(data) {
|
110
|
+
this.data = data;
|
111
|
+
}
|
112
|
+
setPageSize(_size) { } // Mock implementation
|
113
|
+
setPage(_page) { } // Mock implementation
|
114
|
+
getCurrentPageData() {
|
115
|
+
return this.data;
|
116
|
+
}
|
117
|
+
getPaginationInfo() {
|
118
|
+
return { totalItems: this.data.length, totalPages: 1 };
|
119
|
+
}
|
120
|
+
}
|
121
|
+
export class CliStateManager {
|
122
|
+
static getPreferences() {
|
123
|
+
return {};
|
124
|
+
}
|
125
|
+
static updateSessionBudget(_amount, _provider) { } // Mock implementation
|
126
|
+
static loadState() {
|
127
|
+
return { budgets: {} };
|
128
|
+
}
|
129
|
+
}
|
130
|
+
export const filterAndSearchInteractions = (interactions, _filters, _search) => {
|
131
|
+
return interactions;
|
132
|
+
};
|
package/package.json
CHANGED
@@ -1,38 +1,71 @@
|
|
1
1
|
{
|
2
2
|
"name": "@goldensheepai/toknxr-cli",
|
3
|
-
"version": "0.2.
|
4
|
-
"
|
3
|
+
"version": "0.2.2",
|
4
|
+
"description": "CLI-powered AI effectiveness & code quality analysis tool - 100% Local",
|
5
|
+
"license": "MIT",
|
5
6
|
"type": "module",
|
7
|
+
"private": false,
|
6
8
|
"bin": {
|
7
9
|
"toknxr": "lib/cli.js"
|
8
10
|
},
|
11
|
+
"main": "lib/cli.js",
|
12
|
+
"files": [
|
13
|
+
"lib/**/*",
|
14
|
+
"package.json",
|
15
|
+
"README.md",
|
16
|
+
"LICENSE"
|
17
|
+
],
|
18
|
+
"publishConfig": {
|
19
|
+
"access": "public",
|
20
|
+
"registry": "https://registry.npmjs.org/"
|
21
|
+
},
|
22
|
+
"keywords": [
|
23
|
+
"ai",
|
24
|
+
"cli",
|
25
|
+
"code-quality",
|
26
|
+
"analytics",
|
27
|
+
"token-tracking",
|
28
|
+
"developer-tools",
|
29
|
+
"local-first",
|
30
|
+
"proxy",
|
31
|
+
"gemini",
|
32
|
+
"openai",
|
33
|
+
"anthropic"
|
34
|
+
],
|
9
35
|
"scripts": {
|
10
|
-
"start": "tsx src/cli.ts start",
|
11
|
-
"cli": "tsx src/cli.ts",
|
12
36
|
"build": "tsc",
|
13
|
-
"
|
37
|
+
"dev": "tsx watch src/cli.ts",
|
38
|
+
"lint": "echo 'Lint CLI code with root ESLint config'",
|
39
|
+
"typecheck": "tsc --noEmit",
|
40
|
+
"cli": "tsx src/cli.ts",
|
41
|
+
"start": "tsx src/cli.ts start",
|
42
|
+
"test": "tsx --test --test-reporter=verbose src/**/*.test.ts",
|
43
|
+
"test:watch": "tsx --test --watch src/**/*.test.ts"
|
14
44
|
},
|
15
45
|
"dependencies": {
|
16
|
-
"@supabase/supabase-js": "^2.47.1",
|
17
|
-
"commander": "^14.0.1",
|
18
|
-
"chalk": "^5.6.2",
|
19
46
|
"axios": "^1.12.2",
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"ora": "^9.0.0",
|
23
|
-
"cli-spinners": "^3.3.0",
|
47
|
+
"boxen": "^7.1.1",
|
48
|
+
"chalk": "^5.6.2",
|
24
49
|
"cli-cursor": "^5.0.0",
|
25
|
-
"
|
50
|
+
"cli-progress": "^3.12.0",
|
51
|
+
"cli-spinners": "^3.3.0",
|
52
|
+
"cli-table3": "^0.6.4",
|
53
|
+
"commander": "^14.0.1",
|
54
|
+
"dotenv": "^16.4.5",
|
55
|
+
"inquirer": "^12.0.0",
|
26
56
|
"is-interactive": "^2.0.0",
|
27
|
-
"
|
28
|
-
"
|
57
|
+
"onetime": "^7.0.0",
|
58
|
+
"open": "^10.2.0",
|
59
|
+
"ora": "^9.0.0"
|
29
60
|
},
|
30
61
|
"devDependencies": {
|
31
|
-
"@types/node": "^24.6.2",
|
32
62
|
"@types/chalk": "^2.2.4",
|
63
|
+
"@types/node": "^24.6.2",
|
33
64
|
"@types/open": "^6.2.1",
|
34
|
-
"
|
65
|
+
"@vitest/ui": "^3.2.4",
|
66
|
+
"esbuild": "^0.25.10",
|
35
67
|
"tsx": "^4.20.6",
|
36
|
-
"
|
68
|
+
"typescript": "^5.9.3",
|
69
|
+
"vitest": "^3.2.4"
|
37
70
|
}
|
38
71
|
}
|
package/.env
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# TokNXR Environment Variables
|
2
|
-
# Copy this file to .env and fill in your actual API keys
|
3
|
-
|
4
|
-
# Google AI API Key (used for both Gemini-Pro and Gemini-Free)
|
5
|
-
GEMINI_API_KEY=AIzaSyCtrQ8e5H66vbrAozWlWUmCcObJmrI2ovg
|
6
|
-
|
7
|
-
# OpenAI API Key
|
8
|
-
OPENAI_API_KEY=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
|
9
|
-
|
10
|
-
# Anthropic Claude API Key
|
11
|
-
ANTHROPIC_API_KEY=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
|
12
|
-
|
13
|
-
# Supabase Configuration
|
14
|
-
SUPABASE_URL=https://your-project.supabase.co
|
15
|
-
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
|
16
|
-
# Optional: Webhook URL for budget alerts
|
17
|
-
# WEBHOOK_URL=https://your-webhook-url.com/alerts
|
18
|
-
SUPABASE_URL=https://pkdytotoptkknghtsomn.supabase.co
|
19
|
-
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBrZHl0b3RvcHRra25naHRzb21uIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTk3MjI1OTgsImV4cCI6MjA3NTI5ODU5OH0.Y3RVJvx7w-eGD4Nv2aVv8jnkUKhmA2vpkBs7_rFzEoQ
|
20
|
-
# Optional: Custom port for the proxy server (default: 8788)
|
21
|
-
# PORT=8788
|
package/.env.example
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# TokNXR Environment Variables
|
2
|
-
# Copy this file to .env and fill in your actual API keys
|
3
|
-
|
4
|
-
# Google AI API Key (used for both Gemini-Pro and Gemini-Free)
|
5
|
-
GEMINI_API_KEY=AIzaSyCtrQ8e5H66vbrAozWlWUmCcObJmrI2ovg
|
6
|
-
|
7
|
-
# OpenAI API Key
|
8
|
-
OPENAI_API_KEY=your-openai-api-key-here
|
9
|
-
|
10
|
-
# Anthropic Claude API Key
|
11
|
-
ANTHROPIC_API_KEY=your-anthropic-api-key-here
|
12
|
-
|
13
|
-
# Optional: Webhook URL for budget alerts
|
14
|
-
# WEBHOOK_URL=https://your-webhook-url.com/alerts
|
15
|
-
SUPABASE_URL=your-supabase-url
|
16
|
-
SUPABASE_ANON_KEY=your-anon-key
|
17
|
-
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
18
|
-
SUPABASE_JWT_SECRET=your-jwt-secret
|
19
|
-
|
20
|
-
# Optional: Custom port for the proxy server (default: 8787)
|
21
|
-
PORT=8787
|
package/interactions.log
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
{"timestamp":"2025-10-05T20:14:35.420Z","provider":"Gemini-Pro","model":"unknown","promptTokens":9,"completionTokens":2056,"totalTokens":3506,"costUSD":0,"taskType":"chat"}
|
2
|
-
{"timestamp":"2025-10-05T20:18:24.093Z","provider":"Gemini-Pro","model":"unknown","promptTokens":9,"completionTokens":2236,"totalTokens":3564,"costUSD":0,"taskType":"chat"}
|
3
|
-
{"timestamp":"2025-10-05T20:21:58.826Z","provider":"Gemini-Pro","model":"unknown","promptTokens":9,"completionTokens":2054,"totalTokens":3322,"costUSD":0,"taskType":"chat"}
|
4
|
-
{"requestId":"84870610-70c2-4b31-834f-6c55f92c8495","timestamp":"2025-10-05T20:49:46.035Z","provider":"Gemini-Pro","model":"gemini-2.5-flash","promptTokens":2,"completionTokens":519,"totalTokens":1930,"costUSD":0.3117,"taskType":"chat"}
|
5
|
-
{"timestamp":"2025-10-05T21:44:57.972Z","provider":"Gemini-Pro","model":"gemini-2.5-flash","promptTokens":150,"completionTokens":200,"totalTokens":350,"costUSD":0.05,"taskType":"coding","userPrompt":"Create a React component for a todo list","aiResponse":"Here's a React todo component...","extractedCode":"function TodoList() {\n const [todos, setTodos] = useState([]);\n return (\n <div>\n <h1>My Todos</h1>\n {/* Todo implementation */}\n </div>\n );\n}","codeQualityScore":85,"codeQualityMetrics":{"syntaxValid":true,"estimatedReadability":0.8,"hasFunctions":true,"hasClasses":false,"linesOfCode":12,"potentialIssues":[]},"effectivenessScore":88}
|
6
|
-
{"timestamp":"2025-10-05T21:59:57.974Z","provider":"OpenAI-GPT4","model":"gpt-4","promptTokens":80,"completionTokens":120,"totalTokens":200,"costUSD":0.02,"taskType":"coding","userPrompt":"Write a Python function to calculate fibonacci","aiResponse":"def fibonacci(n):...","extractedCode":"def fibonacci(n):\n if n <= 1:\n return n\n return fibonacci(n-1) + fibonacci(n-2)","codeQualityScore":92,"codeQualityMetrics":{"syntaxValid":true,"estimatedReadability":0.9,"hasFunctions":true,"hasClasses":false,"linesOfCode":4,"potentialIssues":[]},"effectivenessScore":95}
|
7
|
-
{"timestamp":"2025-10-05T22:09:57.974Z","provider":"Gemini-Pro","model":"gemini-2.5-flash","promptTokens":200,"completionTokens":300,"totalTokens":500,"costUSD":0.08,"taskType":"coding","userPrompt":"Create a TypeScript API endpoint","aiResponse":"Here's a TypeScript API endpoint...","extractedCode":"import express from 'express';\n\nconst app = express();\n\napp.get('/api/users', async (req, res) => {\n // Implementation here\n});\n\nexport default app;","codeQualityScore":78,"codeQualityMetrics":{"syntaxValid":true,"estimatedReadability":0.7,"hasFunctions":true,"hasClasses":false,"linesOfCode":8,"potentialIssues":["Missing error handling"]},"effectivenessScore":82}
|
8
|
-
{"requestId":"b8d3c1cc-c3c5-427a-b9f3-0af12b7e8e8d","timestamp":"2025-10-05T22:19:07.944Z","provider":"Gemini-Pro","model":"gemini-2.5-flash","promptTokens":13,"completionTokens":1133,"totalTokens":2206,"costUSD":0.68175,"taskType":"coding","extractedCode":"/**\n * Calculates the factorial of a non-negative integer using recursion.\n *\n * @param {number} n The non-negative integer for which to calculate the factorial.\n * @returns {number} The factorial of n.\n * @throws {Error} If n is a negative number.\n * @throws {Error} If n is not an integer.\n */\nfunction factorial(n) {\n // Input validation: Factorial is traditionally defined for non-negative integers.\n if (n < 0) {\n throw new Error(\"Factorial is not defined for negative numbers.\");\n }\n if (!Number.isInteger(n)) {\n throw new Error(\"Factorial is only defined for integer numbers.\");\n }\n\n // Base Case: This is where the recursion stops.\n // The factorial of 0 is 1, and the factorial of 1 is 1.\n if (n === 0 || n === 1) {\n return 1;\n }\n\n // Recursive Step: The function calls itself with a smaller input.\n // n! = n * (n-1)!\n return n * factorial(n - 1);\n}\n\n// --- Examples ---\nconsole.log(`Factorial of 0: ${factorial(0)}`); // Expected: 1\nconsole.log(`Factorial of 1: ${factorial(1)}`); // Expected: 1\nconsole.log(`Factorial of 5: ${factorial(5)}`); // Expected: 120 (5 * 4 * 3 * 2 * 1)\nconsole.log(`Factorial of 7: ${factorial(7)}`); // Expected: 5040 (7 * 6 * 5 * 4 * 3 * 2 * 1)\nconsole.log(`Factorial of 10: ${factorial(10)}`); // Expected: 3628800\n\n// --- Error Handling Examples ---\ntry {\n console.log(`Factorial of -3: ${factorial(-3)}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is not defined for negative numbers.\n}\n\ntry {\n console.log(`Factorial of 3.5: ${factorial(3.5)}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is only defined for integer numbers.\n}\n\ntry {\n console.log(`Factorial of \"abc\": ${factorial(\"abc\")}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is only defined for integer numbers.\n}","codeQualityMetrics":{"syntaxValid":true,"linesOfCode":47,"complexity":10,"hasFunctions":true,"hasClasses":false,"hasTests":false,"estimatedReadability":6,"potentialIssues":[],"language":"javascript"},"codeQualityScore":100,"effectivenessScore":68,"userPrompt":"Write a JavaScript function to calculate the factorial of a number using recursion","aiResponse":"Here's a JavaScript function to calculate the factorial of a number using recursion, along with explanations and examples:\n\n```javascript\n/**\n * Calculates the factorial of a non-negative integer using recursion.\n *\n * @param {number} n The non-negative integer for which to calculate the factorial.\n * @returns {number} The factorial of n.\n * @throws {Error} If n is a negative number.\n * @throws {Error} If n is not an integer.\n */\nfunction factorial(n) {\n // Input validation: Factorial is traditionally defined for non-negative integers.\n if (n < 0) {\n throw new Error(\"Factorial is not defined for negative numbers.\");\n }\n if (!Number.isInteger(n)) {\n throw new Error(\"Factorial is only defined for integer numbers.\");\n }\n\n // Base Case: This is where the recursion stops.\n // The factorial of 0 is 1, and the factorial of 1 is 1.\n if (n === 0 || n === 1) {\n return 1;\n }\n\n // Recursive Step: The function calls itself with a smaller input.\n // n! = n * (n-1)!\n return n * factorial(n - 1);\n}\n\n// --- Examples ---\nconsole.log(`Factorial of 0: ${factorial(0)}`); // Expected: 1\nconsole.log(`Factorial of 1: ${factorial(1)}`); // Expected: 1\nconsole.log(`Factorial of 5: ${factorial(5)}`); // Expected: 120 (5 * 4 * 3 * 2 * 1)\nconsole.log(`Factorial of 7: ${factorial(7)}`); // Expected: 5040 (7 * 6 * 5 * 4 * 3 * 2 * 1)\nconsole.log(`Factorial of 10: ${factorial(10)}`); // Expected: 3628800\n\n// --- Error Handling Examples ---\ntry {\n console.log(`Factorial of -3: ${factorial(-3)}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is not defined for negative numbers.\n}\n\ntry {\n console.log(`Factorial of 3.5: ${factorial(3.5)}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is only defined for integer numbers.\n}\n\ntry {\n console.log(`Factorial of \"abc\": ${factorial(\"abc\")}`);\n} catch (error) {\n console.error(`Error: ${error.message}`); // Expected: Error: Factorial is only defined for integer numbers.\n}\n```\n\n### How it works (Recursive Breakdown):\n\nLet's trace `factorial(3)`:\n\n1. `factorial(3)` is called.\n * `n` is 3. It's not 0 or 1.\n * It returns `3 * factorial(2)`.\n\n2. `factorial(2)` is called (from inside the first call).\n * `n` is 2. It's not 0 or 1.\n * It returns `2 * factorial(1)`.\n\n3. `factorial(1)` is called (from inside the second call).\n * `n` is 1. **This hits the base case!**\n * It returns `1`.\n\n4. Now, the execution unwinds:\n * The `factorial(2)` call receives `1` from `factorial(1)`.\n * It calculates `2 * 1 = 2`.\n * It returns `2`.\n\n5. Finally, the `factorial(3)` call receives `2` from `factorial(2)`.\n * It calculates `3 * 2 = 6`.\n * It returns `6`.\n\nThe result `6` is then printed.\n\n### Key Concepts:\n\n1. **Base Case:** Every recursive function *must* have one or more base cases. This is a condition where the function stops calling itself and returns a direct value. Without a base case, the function would call itself indefinitely, leading to a \"Stack Overflow\" error. For factorial, `factorial(0) = 1` and `factorial(1) = 1` are the base cases.\n2. **Recursive Step:** This is where the function calls itself with a modified (usually smaller or simpler) input. The idea is to break down the problem into a smaller instance of the *same* problem. For factorial, `n! = n * (n-1)!` is the recursive step. The problem `n!` is solved by using the solution to `(n-1)!`.\n3. **Input Validation:** It's good practice to add checks for invalid inputs (like negative numbers or non-integers for factorial) to make the function more robust."}
|