Smart Repair v2.0: AI-powered with 3 Qwen models, offline fallback, OAuth trigger
This commit is contained in:
@@ -1,22 +1,51 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* OpenQode Smart Repair Agent
|
* OpenQode Smart Repair Agent v2.0
|
||||||
* A specialized TUI for diagnosing and fixing bugs in the main TUI IDE
|
* AI-Powered TUI Self-Healing System
|
||||||
*
|
*
|
||||||
* This agent has ONE mission: Repair the TUI when it crashes
|
* Features:
|
||||||
|
* - Qwen AI integration for intelligent error analysis
|
||||||
|
* - 3 model choices (Qwen Coder Plus, Qwen Plus, Qwen Turbo)
|
||||||
|
* - Offline fallback for common issues
|
||||||
|
* - OAuth trigger when auth is missing/expired
|
||||||
*/
|
*/
|
||||||
import { createRequire } from 'module';
|
import { createRequire } from 'module';
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync, spawnSync } = require('child_process');
|
const { execSync, spawn } = require('child_process');
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
|
|
||||||
// File paths relative to package root
|
// File paths relative to package root
|
||||||
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')), '..');
|
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')), '..');
|
||||||
const MAIN_TUI = path.join(ROOT, 'bin', 'opencode-ink.mjs');
|
const MAIN_TUI = path.join(ROOT, 'bin', 'opencode-ink.mjs');
|
||||||
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
const PACKAGE_JSON = path.join(ROOT, 'package.json');
|
||||||
|
const TOKENS_FILE = path.join(ROOT, 'tokens.json');
|
||||||
|
|
||||||
|
// API Configuration
|
||||||
|
const DASHSCOPE_API = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions';
|
||||||
|
|
||||||
|
// Model catalog with 3 Qwen options
|
||||||
|
const MODELS = {
|
||||||
|
'1': {
|
||||||
|
id: 'qwen-coder-plus',
|
||||||
|
name: 'Qwen Coder Plus',
|
||||||
|
description: 'Best for code analysis and bug fixing'
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
id: 'qwen-plus',
|
||||||
|
name: 'Qwen Plus',
|
||||||
|
description: 'General purpose, balanced'
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
id: 'qwen-turbo',
|
||||||
|
name: 'Qwen Turbo',
|
||||||
|
description: 'Fast responses, simpler issues'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let selectedModel = MODELS['1']; // Default: Qwen Coder Plus
|
||||||
|
|
||||||
// Colors for terminal output
|
// Colors for terminal output
|
||||||
const C = {
|
const C = {
|
||||||
@@ -27,185 +56,289 @@ const C = {
|
|||||||
red: '\x1b[31m',
|
red: '\x1b[31m',
|
||||||
magenta: '\x1b[35m',
|
magenta: '\x1b[35m',
|
||||||
bold: '\x1b[1m',
|
bold: '\x1b[1m',
|
||||||
dim: '\x1b[2m'
|
dim: '\x1b[2m',
|
||||||
|
white: '\x1b[37m'
|
||||||
};
|
};
|
||||||
|
|
||||||
const banner = () => {
|
const banner = () => {
|
||||||
console.clear();
|
console.clear();
|
||||||
console.log(C.magenta + C.bold);
|
console.log(C.magenta + C.bold);
|
||||||
console.log(' ╔═══════════════════════════════════════════╗');
|
console.log(' ╔═══════════════════════════════════════════╗');
|
||||||
console.log(' ║ 🔧 OpenQode Smart Repair Agent 🔧 ║');
|
console.log(' ║ 🔧 OpenQode Smart Repair Agent v2.0 🔧 ║');
|
||||||
console.log(' ║ TUI Self-Healing System ║');
|
console.log(' ║ AI-Powered TUI Self-Healing ║');
|
||||||
console.log(' ╚═══════════════════════════════════════════╝');
|
console.log(' ╚═══════════════════════════════════════════╝');
|
||||||
console.log(C.reset);
|
console.log(C.reset);
|
||||||
console.log(C.dim + ' This agent can ONLY repair the TUI. No other tasks.' + C.reset);
|
console.log(C.dim + ' This agent can ONLY repair the TUI. No other tasks.' + C.reset);
|
||||||
|
console.log(C.cyan + ` Model: ${selectedModel.name}` + C.reset);
|
||||||
console.log('');
|
console.log('');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse error from user input
|
// Get Qwen auth token
|
||||||
const parseError = (errorText) => {
|
const getAuthToken = () => {
|
||||||
const issues = [];
|
try {
|
||||||
|
if (fs.existsSync(TOKENS_FILE)) {
|
||||||
// Extract file and line number
|
const tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8'));
|
||||||
const lineMatch = errorText.match(/opencode-ink\.mjs:(\d+)/);
|
return tokens.access_token || null;
|
||||||
const line = lineMatch ? parseInt(lineMatch[1]) : null;
|
}
|
||||||
|
// Try qwen CLI config
|
||||||
// Common error patterns
|
const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.qwen', 'config.json');
|
||||||
if (errorText.includes('Cannot read properties of null')) {
|
if (fs.existsSync(configPath)) {
|
||||||
issues.push({
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
type: 'NULL_REFERENCE',
|
return config.access_token || config.api_key || null;
|
||||||
desc: 'Null reference error - attempting to access property on null object',
|
}
|
||||||
line
|
} catch (e) { /* ignore */ }
|
||||||
});
|
return null;
|
||||||
}
|
|
||||||
if (errorText.includes('Cannot read properties of undefined')) {
|
|
||||||
issues.push({
|
|
||||||
type: 'UNDEFINED_REFERENCE',
|
|
||||||
desc: 'Undefined reference - variable or property does not exist',
|
|
||||||
line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errorText.includes('useMemo') || errorText.includes('useEffect') || errorText.includes('useState')) {
|
|
||||||
issues.push({
|
|
||||||
type: 'REACT_HOOKS',
|
|
||||||
desc: 'React hooks error - possible multiple React versions or hooks called outside component',
|
|
||||||
line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errorText.includes('ink-syntax-highlight')) {
|
|
||||||
issues.push({
|
|
||||||
type: 'INK_SYNTAX_HIGHLIGHT',
|
|
||||||
desc: 'Extraneous ink-syntax-highlight package causing React conflict',
|
|
||||||
line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errorText.includes('ENOENT')) {
|
|
||||||
issues.push({
|
|
||||||
type: 'FILE_NOT_FOUND',
|
|
||||||
desc: 'File or directory not found',
|
|
||||||
line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (errorText.includes('SyntaxError')) {
|
|
||||||
issues.push({
|
|
||||||
type: 'SYNTAX_ERROR',
|
|
||||||
desc: 'JavaScript syntax error in code',
|
|
||||||
line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return issues;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Automatic repair functions
|
// Trigger OAuth authentication
|
||||||
const repairs = {
|
const triggerOAuth = async () => {
|
||||||
REACT_HOOKS: () => {
|
console.log(C.yellow + '\n[!] Authentication required. Opening browser for Qwen OAuth...' + C.reset);
|
||||||
console.log(C.yellow + '[*] Fixing React hooks conflict...' + C.reset);
|
try {
|
||||||
// Delete node_modules and reinstall
|
const { QwenOAuth } = await import('../qwen-oauth.mjs');
|
||||||
const nmPath = path.join(ROOT, 'node_modules');
|
const oauth = new QwenOAuth();
|
||||||
const lockPath = path.join(ROOT, 'package-lock.json');
|
const tokens = await oauth.authenticate();
|
||||||
if (fs.existsSync(nmPath)) {
|
if (tokens && tokens.access_token) {
|
||||||
console.log(' Removing node_modules...');
|
fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2));
|
||||||
fs.rmSync(nmPath, { recursive: true, force: true });
|
console.log(C.green + '[✓] Authentication successful!' + C.reset);
|
||||||
|
return tokens.access_token;
|
||||||
}
|
}
|
||||||
if (fs.existsSync(lockPath)) {
|
} catch (e) {
|
||||||
console.log(' Removing package-lock.json...');
|
console.log(C.red + `[✗] OAuth failed: ${e.message}` + C.reset);
|
||||||
fs.unlinkSync(lockPath);
|
}
|
||||||
}
|
return null;
|
||||||
console.log(' Running npm install...');
|
};
|
||||||
try {
|
|
||||||
execSync('npm install --legacy-peer-deps', { cwd: ROOT, stdio: 'inherit' });
|
|
||||||
return { success: true, msg: 'Dependencies reinstalled with React overrides' };
|
|
||||||
} catch (e) {
|
|
||||||
return { success: false, msg: 'npm install failed: ' + e.message };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
INK_SYNTAX_HIGHLIGHT: () => {
|
// Call Qwen AI API
|
||||||
console.log(C.yellow + '[*] Removing extraneous ink-syntax-highlight...' + C.reset);
|
const callQwenAI = async (prompt, onChunk = null) => {
|
||||||
const ish = path.join(ROOT, 'node_modules', 'ink-syntax-highlight');
|
let token = getAuthToken();
|
||||||
if (fs.existsSync(ish)) {
|
|
||||||
fs.rmSync(ish, { recursive: true, force: true });
|
|
||||||
return { success: true, msg: 'Removed ink-syntax-highlight package' };
|
|
||||||
}
|
|
||||||
// If not found, do full reinstall
|
|
||||||
return repairs.REACT_HOOKS();
|
|
||||||
},
|
|
||||||
|
|
||||||
NULL_REFERENCE: (issue) => {
|
if (!token) {
|
||||||
if (!issue.line) {
|
token = await triggerOAuth();
|
||||||
return { success: false, msg: 'Could not determine line number for null reference fix' };
|
if (!token) {
|
||||||
|
return { success: false, error: 'No auth token available', response: '' };
|
||||||
}
|
}
|
||||||
console.log(C.yellow + `[*] Checking line ${issue.line} for null safety...` + C.reset);
|
}
|
||||||
// Read the file and show the problematic line
|
|
||||||
const code = fs.readFileSync(MAIN_TUI, 'utf8');
|
|
||||||
const lines = code.split('\n');
|
|
||||||
const problemLine = lines[issue.line - 1];
|
|
||||||
console.log(C.dim + ` Line ${issue.line}: ${problemLine.trim().substring(0, 60)}...` + C.reset);
|
|
||||||
return { success: false, msg: 'Manual review needed. Please report to developer with error details.' };
|
|
||||||
},
|
|
||||||
|
|
||||||
UNDEFINED_REFERENCE: (issue) => repairs.NULL_REFERENCE(issue),
|
try {
|
||||||
|
const response = await fetch(DASHSCOPE_API, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: selectedModel.id,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: `You are the OpenQode Smart Repair Agent. Your ONLY purpose is to diagnose and fix bugs in the OpenQode TUI (Terminal User Interface).
|
||||||
|
|
||||||
FILE_NOT_FOUND: () => {
|
The TUI is a Node.js/React Ink application located at:
|
||||||
console.log(C.yellow + '[*] Checking file structure...' + C.reset);
|
- Main file: bin/opencode-ink.mjs
|
||||||
if (!fs.existsSync(MAIN_TUI)) {
|
- Package: package.json
|
||||||
return { success: false, msg: 'Main TUI file missing! Please re-clone the repository.' };
|
|
||||||
|
When given an error:
|
||||||
|
1. Analyze the error message and stack trace
|
||||||
|
2. Identify the root cause
|
||||||
|
3. Provide a specific fix (code change or shell command)
|
||||||
|
4. Format fixes clearly with code blocks
|
||||||
|
|
||||||
|
You MUST refuse any request that is not about fixing the TUI.`
|
||||||
|
},
|
||||||
|
{ role: 'user', content: prompt }
|
||||||
|
],
|
||||||
|
stream: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
if (response.status === 401) {
|
||||||
|
// Token expired - try re-auth
|
||||||
|
console.log(C.yellow + '[!] Token expired, re-authenticating...' + C.reset);
|
||||||
|
const newToken = await triggerOAuth();
|
||||||
|
if (newToken) {
|
||||||
|
return callQwenAI(prompt, onChunk); // Retry with new token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success: false, error: `API error ${response.status}: ${errorText}`, response: '' };
|
||||||
}
|
}
|
||||||
return { success: true, msg: 'File structure appears intact' };
|
|
||||||
},
|
|
||||||
|
|
||||||
SYNTAX_ERROR: () => {
|
const reader = response.body.getReader();
|
||||||
console.log(C.yellow + '[*] Checking syntax...' + C.reset);
|
const decoder = new TextDecoder();
|
||||||
try {
|
let fullResponse = '';
|
||||||
execSync(`node -c "${MAIN_TUI}"`, { cwd: ROOT });
|
|
||||||
return { success: true, msg: 'Syntax check passed' };
|
while (true) {
|
||||||
} catch (e) {
|
const { done, value } = await reader.read();
|
||||||
return { success: false, msg: 'Syntax error detected. Please pull latest code: git pull origin main' };
|
if (done) break;
|
||||||
|
|
||||||
|
const chunk = decoder.decode(value, { stream: true });
|
||||||
|
const lines = chunk.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ')) {
|
||||||
|
const data = line.slice(6).trim();
|
||||||
|
if (data === '[DONE]') continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
const content = parsed.choices?.[0]?.delta?.content || '';
|
||||||
|
if (content) {
|
||||||
|
fullResponse += content;
|
||||||
|
if (onChunk) onChunk(content);
|
||||||
|
}
|
||||||
|
} catch (e) { /* ignore parse errors */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { success: true, response: fullResponse };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message || 'Network error', response: '' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Offline fallback - common issues detection
|
||||||
|
const offlineDiagnose = (errorText) => {
|
||||||
|
const fixes = [];
|
||||||
|
|
||||||
|
// React hooks conflict
|
||||||
|
if (errorText.includes('useMemo') || errorText.includes('useEffect') || errorText.includes('ink-syntax-highlight')) {
|
||||||
|
fixes.push({
|
||||||
|
issue: 'React version conflict (multiple React instances)',
|
||||||
|
fix: 'Reinstall dependencies with React overrides',
|
||||||
|
command: 'rm -rf node_modules package-lock.json && npm install --legacy-peer-deps'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null reference errors
|
||||||
|
if (errorText.includes('Cannot read properties of null') || errorText.includes('Cannot read properties of undefined')) {
|
||||||
|
const lineMatch = errorText.match(/opencode-ink\.mjs:(\d+)/);
|
||||||
|
fixes.push({
|
||||||
|
issue: 'Null reference error in code',
|
||||||
|
fix: lineMatch ? `Check line ${lineMatch[1]} for missing null checks` : 'Code needs null safety checks',
|
||||||
|
command: 'git pull origin main # Get latest bug fixes'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module not found
|
||||||
|
if (errorText.includes('Cannot find module') || errorText.includes('ENOENT')) {
|
||||||
|
fixes.push({
|
||||||
|
issue: 'Missing dependency or file',
|
||||||
|
fix: 'Reinstall dependencies',
|
||||||
|
command: 'npm install --legacy-peer-deps'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth errors
|
||||||
|
if (errorText.includes('401') || errorText.includes('unauthorized') || errorText.includes('auth')) {
|
||||||
|
fixes.push({
|
||||||
|
issue: 'Authentication expired or missing',
|
||||||
|
fix: 'Trigger OAuth re-authentication',
|
||||||
|
command: '__OAUTH__' // Special marker for OAuth trigger
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax errors
|
||||||
|
if (errorText.includes('SyntaxError') || errorText.includes('Unexpected token')) {
|
||||||
|
fixes.push({
|
||||||
|
issue: 'JavaScript syntax error',
|
||||||
|
fix: 'Pull latest code to get fixes',
|
||||||
|
command: 'git pull origin main'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute a fix command
|
||||||
|
const executeCommand = (command) => {
|
||||||
|
if (command === '__OAUTH__') {
|
||||||
|
return triggerOAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(C.cyan + `\n Executing: ${command}` + C.reset);
|
||||||
|
try {
|
||||||
|
execSync(command, { cwd: ROOT, stdio: 'inherit' });
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(C.red + ` Command failed: ${e.message}` + C.reset);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Model selection menu
|
||||||
|
const selectModel = async (rl) => {
|
||||||
|
console.log(C.cyan + '\n Select AI Model:' + C.reset);
|
||||||
|
for (const [key, model] of Object.entries(MODELS)) {
|
||||||
|
const marker = model.id === selectedModel.id ? C.green + ' ←' + C.reset : '';
|
||||||
|
console.log(` [${key}] ${model.name} - ${model.description}${marker}`);
|
||||||
|
}
|
||||||
|
const choice = await new Promise(r => rl.question(C.magenta + '\n Enter choice (1-3): ' + C.reset, r));
|
||||||
|
if (MODELS[choice]) {
|
||||||
|
selectedModel = MODELS[choice];
|
||||||
|
console.log(C.green + `\n [✓] Selected: ${selectedModel.name}` + C.reset);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main repair function
|
// Main repair function
|
||||||
const attemptRepair = async (errorText) => {
|
const attemptRepair = async (errorText) => {
|
||||||
console.log(C.cyan + '\n[ANALYZING ERROR...]' + C.reset);
|
console.log(C.cyan + '\n[1/3] OFFLINE ANALYSIS...' + C.reset);
|
||||||
const issues = parseError(errorText);
|
const offlineFixes = offlineDiagnose(errorText);
|
||||||
|
|
||||||
if (issues.length === 0) {
|
if (offlineFixes.length > 0) {
|
||||||
console.log(C.yellow + '[!] Could not identify specific issue from error.' + C.reset);
|
console.log(C.green + ` Found ${offlineFixes.length} known issue(s):` + C.reset);
|
||||||
console.log(' Generic repair: Reinstalling dependencies...');
|
offlineFixes.forEach((fix, i) => {
|
||||||
const result = repairs.REACT_HOOKS();
|
console.log(C.yellow + ` ${i + 1}. ${fix.issue}` + C.reset);
|
||||||
return result;
|
console.log(C.dim + ` Fix: ${fix.fix}` + C.reset);
|
||||||
}
|
});
|
||||||
|
|
||||||
console.log(C.green + `[✓] Identified ${issues.length} issue(s):` + C.reset);
|
// Try offline fixes first
|
||||||
issues.forEach((issue, i) => {
|
console.log(C.cyan + '\n[2/3] APPLYING OFFLINE FIXES...' + C.reset);
|
||||||
console.log(` ${i + 1}. ${issue.type}: ${issue.desc}`);
|
for (const fix of offlineFixes) {
|
||||||
if (issue.line) console.log(` At line: ${issue.line}`);
|
const success = await executeCommand(fix.command);
|
||||||
});
|
if (success) {
|
||||||
|
console.log(C.green + ` [✓] Applied: ${fix.fix}` + C.reset);
|
||||||
console.log(C.cyan + '\n[ATTEMPTING REPAIRS...]' + C.reset);
|
|
||||||
|
|
||||||
for (const issue of issues) {
|
|
||||||
const repairFn = repairs[issue.type];
|
|
||||||
if (repairFn) {
|
|
||||||
const result = repairFn(issue);
|
|
||||||
if (result.success) {
|
|
||||||
console.log(C.green + `[✓] ${result.msg}` + C.reset);
|
|
||||||
} else {
|
|
||||||
console.log(C.red + `[✗] ${result.msg}` + C.reset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(C.yellow + ' No known patterns. Consulting AI...' + C.reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the fix
|
// Consult AI for deeper analysis
|
||||||
console.log(C.cyan + '\n[VERIFYING FIX...]' + C.reset);
|
console.log(C.cyan + '\n[3/3] AI ANALYSIS...' + C.reset);
|
||||||
|
console.log(C.dim + ` Using ${selectedModel.name}...` + C.reset);
|
||||||
|
|
||||||
|
const prompt = `Analyze this OpenQode TUI error and provide a fix:
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
${errorText}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
The TUI is a React Ink app at bin/opencode-ink.mjs. Provide:
|
||||||
|
1. Root cause analysis
|
||||||
|
2. Specific fix (code change or command)
|
||||||
|
3. Prevention tips`;
|
||||||
|
|
||||||
|
process.stdout.write(C.white + '\n ');
|
||||||
|
const result = await callQwenAI(prompt, (chunk) => {
|
||||||
|
process.stdout.write(chunk);
|
||||||
|
});
|
||||||
|
console.log(C.reset);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
console.log(C.yellow + `\n [!] AI unavailable: ${result.error}` + C.reset);
|
||||||
|
console.log(C.dim + ' Offline fixes have been applied if any were found.' + C.reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify fix
|
||||||
|
console.log(C.cyan + '\n[VERIFYING...]' + C.reset);
|
||||||
try {
|
try {
|
||||||
execSync(`node -c "${MAIN_TUI}"`, { cwd: ROOT });
|
execSync(`node -c "${MAIN_TUI}"`, { cwd: ROOT });
|
||||||
console.log(C.green + '[✓] Syntax check passed!' + C.reset);
|
console.log(C.green + '[✓] Syntax check passed!' + C.reset);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(C.red + '[✗] Still has issues.' + C.reset);
|
console.log(C.yellow + '[!] Syntax issues remain. Try relaunching or paste the new error.' + C.reset);
|
||||||
return { success: false };
|
return { success: false };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -221,20 +354,37 @@ const runInteractive = async () => {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
banner();
|
banner();
|
||||||
console.log(C.yellow + ' Paste the error message you received from TUI crash.' + C.reset);
|
console.log(C.yellow + ' Commands: ' + C.reset);
|
||||||
console.log(C.dim + ' (Type "quit" to exit, or press Ctrl+C)' + C.reset);
|
console.log(C.dim + ' • Paste an error message to analyze' + C.reset);
|
||||||
|
console.log(C.dim + ' • Type "model" to change AI model' + C.reset);
|
||||||
|
console.log(C.dim + ' • Type "auth" to trigger OAuth' + C.reset);
|
||||||
|
console.log(C.dim + ' • Type "quit" to exit' + C.reset);
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
const errorText = await question(C.magenta + '> ' + C.reset);
|
const errorText = await question(C.magenta + '> ' + C.reset);
|
||||||
|
|
||||||
|
if (!errorText.trim()) continue;
|
||||||
|
|
||||||
if (errorText.toLowerCase() === 'quit' || errorText.toLowerCase() === 'exit') {
|
if (errorText.toLowerCase() === 'quit' || errorText.toLowerCase() === 'exit') {
|
||||||
console.log(C.cyan + '\nGoodbye! Try launching TUI again.' + C.reset);
|
console.log(C.cyan + '\nGoodbye! Try launching TUI again.' + C.reset);
|
||||||
rl.close();
|
rl.close();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorText.toLowerCase() === 'model') {
|
||||||
|
await selectModel(rl);
|
||||||
|
await question('\nPress Enter to continue...');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorText.toLowerCase() === 'auth') {
|
||||||
|
await triggerOAuth();
|
||||||
|
await question('\nPress Enter to continue...');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user is asking for something other than repair
|
// Check if user is asking for something other than repair
|
||||||
const nonRepairKeywords = ['create', 'build', 'write code', 'make a', 'help me', 'how to', 'what is'];
|
const nonRepairKeywords = ['create', 'build', 'write code', 'make a', 'help me write', 'how to build'];
|
||||||
if (nonRepairKeywords.some(kw => errorText.toLowerCase().includes(kw))) {
|
if (nonRepairKeywords.some(kw => errorText.toLowerCase().includes(kw))) {
|
||||||
console.log(C.red + '\n[!] I can ONLY repair the TUI. For other tasks, use the main TUI IDE.' + C.reset);
|
console.log(C.red + '\n[!] I can ONLY repair the TUI. For other tasks, use the main TUI IDE.' + C.reset);
|
||||||
await question('\nPress Enter to continue...');
|
await question('\nPress Enter to continue...');
|
||||||
|
|||||||
Reference in New Issue
Block a user