Comprehensive Windows crash fix - handle ALL spawn ollama calls

Fixed remaining unprotected spawn('ollama') calls across multiple files:
- src/models/deterministic-selector.js: Added ENOENT error handling in runOllamaCommand and getModelDetails
- ml-model/js/cli.js: Protected getAvailableModels and runOllamaModel functions
- Added proper try-catch blocks around all spawn calls
- Added graceful fallbacks when Ollama executable is not found
- Enhanced error messages to guide users to install Ollama

This should completely resolve the Windows ENOENT crash when Ollama is not installed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pavelevich 2025-08-13 12:17:39 +02:00
parent 9754f0d2bd
commit a163ccba48
3 changed files with 99 additions and 49 deletions

View file

@ -10,33 +10,45 @@ class OllamaAISelector {
async getAvailableModels() {
return new Promise((resolve, reject) => {
const ollama = spawn('ollama', ['list']);
let output = '';
let error = '';
try {
const ollama = spawn('ollama', ['list']);
let output = '';
let error = '';
ollama.stdout.on('data', (data) => {
output += data.toString();
});
ollama.stdout.on('data', (data) => {
output += data.toString();
});
ollama.stderr.on('data', (data) => {
error += data.toString();
});
ollama.stderr.on('data', (data) => {
error += data.toString();
});
ollama.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Ollama list failed: ${error}`));
return;
}
ollama.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Ollama list failed: ${error}`));
return;
}
// Parse ollama list output
const lines = output.trim().split('\n').slice(1); // Skip header
const models = lines
.filter(line => line.trim())
.map(line => line.split(/\s+/)[0])
.filter(model => model && !model.includes('NAME'));
// Parse ollama list output
const lines = output.trim().split('\n').slice(1); // Skip header
const models = lines
.filter(line => line.trim())
.map(line => line.split(/\s+/)[0])
.filter(model => model && !model.includes('NAME'));
resolve(models);
});
resolve(models);
});
ollama.on('error', (err) => {
if (err.code === 'ENOENT') {
reject(new Error('Ollama not found. Please install Ollama from https://ollama.ai'));
} else {
reject(new Error(`Ollama spawn error: ${err.message}`));
}
});
} catch (spawnError) {
reject(new Error(`Failed to start Ollama: ${spawnError.message}`));
}
});
}
@ -44,14 +56,15 @@ class OllamaAISelector {
console.log(`🚀 Running Ollama with ${modelName}...`);
return new Promise((resolve, reject) => {
const args = ['run', modelName];
if (prompt) {
args.push(prompt);
}
try {
const args = ['run', modelName];
if (prompt) {
args.push(prompt);
}
const ollama = spawn('ollama', args, {
stdio: 'inherit' // Stream output directly to terminal
});
const ollama = spawn('ollama', args, {
stdio: 'inherit' // Stream output directly to terminal
});
ollama.on('close', (code) => {
if (code === 0) {
@ -61,9 +74,16 @@ class OllamaAISelector {
}
});
ollama.on('error', (error) => {
reject(new Error(`Failed to start Ollama: ${error.message}`));
});
ollama.on('error', (error) => {
if (error.code === 'ENOENT') {
reject(new Error('Ollama not found. Please install Ollama from https://ollama.ai'));
} else {
reject(new Error(`Failed to start Ollama: ${error.message}`));
}
});
} catch (spawnError) {
reject(new Error(`Failed to start Ollama: ${spawnError.message}`));
}
});
}

View file

@ -1,6 +1,6 @@
{
"name": "llm-checker",
"version": "2.3.2",
"version": "2.3.3",
"description": "Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system",
"bin": {
"llm-checker": "bin/enhanced_cli.js"

View file

@ -221,7 +221,8 @@ class DeterministicModelSelector {
}
async getModelDetails(modelName) {
const details = await this.runOllamaCommand(['show', modelName]);
try {
const details = await this.runOllamaCommand(['show', modelName]);
// Parse model details from ollama show output
const meta = {
@ -236,7 +237,22 @@ class DeterministicModelSelector {
model_identifier: modelName
};
return meta;
return meta;
} catch (error) {
// If Ollama is not available or model details can't be fetched, return minimal info
return {
name: modelName,
family: 'unknown',
paramsB: 0,
ctxMax: 2048,
quant: 'unknown',
sizeGB: 0,
modalities: ['text'],
tags: [],
model_identifier: modelName,
error: error.message
};
}
}
/**
@ -404,20 +420,34 @@ class DeterministicModelSelector {
async runOllamaCommand(args) {
return new Promise((resolve, reject) => {
const proc = spawn('ollama', args, { stdio: 'pipe' });
let output = '';
let error = '';
proc.stdout.on('data', (data) => output += data);
proc.stderr.on('data', (data) => error += data);
proc.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`Ollama command failed: ${error}`));
}
});
try {
const proc = spawn('ollama', args, { stdio: 'pipe' });
let output = '';
let error = '';
proc.stdout.on('data', (data) => output += data);
proc.stderr.on('data', (data) => error += data);
proc.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`Ollama command failed: ${error}`));
}
});
proc.on('error', (err) => {
// Handle ENOENT and other spawn errors gracefully
if (err.code === 'ENOENT') {
reject(new Error('Ollama not found. Please install Ollama from https://ollama.ai'));
} else {
reject(new Error(`Ollama spawn error: ${err.message}`));
}
});
} catch (spawnError) {
// Handle synchronous spawn errors
reject(new Error(`Failed to start Ollama: ${spawnError.message}`));
}
});
}