@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.
@@ -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
- const logFilePath = path.resolve(process.cwd(), 'interactions.log');
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. Nothing to sync.'));
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 (error) {
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
- fs.writeFileSync(logFilePath, '');
93
- console.log(chalk.gray('Local interactions.log has been cleared.'));
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,129 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
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 = 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;
58
+ };
59
+ export const createBox = (title, content, options) => {
60
+ const { borderColor, titleColor } = options;
61
+ const color = chalk[borderColor] || chalk.gray;
62
+ const titleColored = chalk[titleColor] || chalk.white;
63
+ let box = color('┌' + '─'.repeat(title.length + 2) + '┐\n');
64
+ box += color('│ ') + titleColored(title) + color(' │\n');
65
+ box += color('├' + '─'.repeat(title.length + 2) + '┤\n');
66
+ content.forEach(line => {
67
+ box +=
68
+ color('│ ') +
69
+ line +
70
+ color(' '.repeat(Math.max(0, title.length - line.length + 2))) +
71
+ color(' │\n');
72
+ });
73
+ box += color('└' + '─'.repeat(title.length + 2) + '┘');
74
+ return box;
75
+ };
76
+ export const createCostChart = (data) => {
77
+ // Basic chart for demonstration
78
+ let chart = `${chalk.bold.blue('Cost Trend (Last 7 Days)')}\n`;
79
+ const max = Math.max(...data);
80
+ data.forEach(value => {
81
+ const bar = '█'.repeat(Math.round((value / max) * 20));
82
+ chart += `${chalk.green(bar)}\n`;
83
+ });
84
+ return chart;
85
+ };
86
+ export const createPaginatedDisplay = (data, pageSize, currentPage, render, title) => {
87
+ let display = title ? `${chalk.bold.blue(title)}\n` : '';
88
+ data.forEach((item, index) => {
89
+ display += render(item, (currentPage - 1) * pageSize + index);
90
+ });
91
+ return display;
92
+ };
93
+ export const createFilterInterface = (currentFilters) => {
94
+ return new Promise(resolve => {
95
+ console.log('Filtering interface is not implemented in this mock.');
96
+ resolve(currentFilters);
97
+ });
98
+ };
99
+ export const createSearchInterface = (fields) => {
100
+ return new Promise(resolve => {
101
+ console.log('Search interface is not implemented in this mock.');
102
+ resolve({ query: 'test', fields });
103
+ });
104
+ };
105
+ export class InteractiveDataExplorer {
106
+ constructor(data) {
107
+ this.data = data;
108
+ }
109
+ setPageSize(_size) { } // Mock implementation
110
+ setPage(_page) { } // Mock implementation
111
+ getCurrentPageData() {
112
+ return this.data;
113
+ }
114
+ getPaginationInfo() {
115
+ return { totalItems: this.data.length, totalPages: 1 };
116
+ }
117
+ }
118
+ export class CliStateManager {
119
+ static getPreferences() {
120
+ return {};
121
+ }
122
+ static updateSessionBudget(_amount, _provider) { } // Mock implementation
123
+ static loadState() {
124
+ return { budgets: {} };
125
+ }
126
+ }
127
+ export const filterAndSearchInteractions = (interactions, _filters, _search) => {
128
+ return interactions;
129
+ };
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
+ }
package/package.json CHANGED
@@ -1,38 +1,71 @@
1
1
  {
2
2
  "name": "@goldensheepai/toknxr-cli",
3
- "version": "0.2.0",
4
- "license": "ISC",
3
+ "version": "0.3.0",
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
- "test": "echo \"Error: no test specified\" && exit 1"
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
- "open": "^10.2.0",
21
- "keytar": "^7.9.0",
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
- "onetime": "^7.0.0",
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
- "@types/keytar": "^4.4.0",
28
- "dotenv": "^16.4.5"
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
- "typescript": "^5.9.3",
65
+ "@vitest/ui": "^3.2.4",
66
+ "esbuild": "^0.25.10",
35
67
  "tsx": "^4.20.6",
36
- "esbuild": "^0.25.10"
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