diff --git a/src/main/managers/OpenWebUIManager.ts b/src/main/managers/OpenWebUIManager.ts index 2d28812..2a5138e 100644 --- a/src/main/managers/OpenWebUIManager.ts +++ b/src/main/managers/OpenWebUIManager.ts @@ -1,6 +1,8 @@ import { spawn } from 'child_process'; import type { ChildProcess } from 'child_process'; import { join } from 'path'; +import { homedir } from 'os'; +import { access } from 'fs/promises'; import { LogManager } from './LogManager'; import { WindowManager } from './WindowManager'; @@ -53,6 +55,26 @@ export class OpenWebUIManager { > { const env = { ...process.env }; + const uvPaths = [ + join(homedir(), '.cargo', 'bin'), + join(homedir(), '.local', 'bin'), + ]; + + const existingPaths: string[] = []; + for (const path of uvPaths) { + try { + await access(path); + existingPaths.push(path); + } catch { + void 0; + } + } + + if (existingPaths.length > 0) { + const pathSeparator = process.platform === 'win32' ? ';' : ':'; + env.PATH = `${existingPaths.join(pathSeparator)}${pathSeparator}${env.PATH}`; + } + if (process.platform === 'win32') { env.PYTHONIOENCODING = 'utf-8'; env.PYTHONLEGACYWINDOWSSTDIO = '1'; @@ -66,11 +88,7 @@ export class OpenWebUIManager { async isUvAvailable(): Promise { try { const env = await this.getUvEnvironment(); - const testProcess = spawn('uv', ['--version'], { - stdio: 'pipe', - env, - shell: true, - }); + const testProcess = spawn('uv', ['--version'], { stdio: 'pipe', env }); return new Promise((resolve) => { const timeout = setTimeout(() => { @@ -109,7 +127,6 @@ export class OpenWebUIManager { stdio: ['pipe', 'pipe', 'pipe'], detached: false, env: mergedEnv, - shell: true, }); } @@ -118,24 +135,14 @@ export class OpenWebUIManager { return new Promise((resolve, reject) => { const checkForOutput = (data: Buffer) => { - try { - const output = data.toString('utf8'); - if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) { - this.windowManager.sendKoboldOutput('Open WebUI is now running!'); - resolve(); + const output = data.toString(); + if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) { + this.windowManager.sendKoboldOutput('Open WebUI is now running!'); + resolve(); - if (this.openWebUIProcess?.stdout) { - this.openWebUIProcess.stdout.removeListener( - 'data', - checkForOutput - ); - } + if (this.openWebUIProcess?.stdout) { + this.openWebUIProcess.stdout.removeListener('data', checkForOutput); } - } catch (error) { - this.logManager.logError( - 'Error checking OpenWebUI output:', - error as Error - ); } }; diff --git a/src/main/managers/SillyTavernManager.ts b/src/main/managers/SillyTavernManager.ts index c9b2e63..39b2bf8 100644 --- a/src/main/managers/SillyTavernManager.ts +++ b/src/main/managers/SillyTavernManager.ts @@ -2,6 +2,7 @@ import { spawn } from 'child_process'; import { createServer, request, type Server } from 'http'; import { homedir } from 'os'; import { join } from 'path'; +import { access, readdir } from 'fs/promises'; import type { ChildProcess } from 'child_process'; import { LogManager } from './LogManager'; @@ -86,12 +87,94 @@ export class SillyTavernManager { return join(dataRoot, 'default-user', 'settings.json'); } + private async tryAddPathToEnv( + env: Record, + path: string + ): Promise { + const pathSeparator = process.platform === 'win32' ? ';' : ':'; + if (!env.PATH?.includes(path)) { + env.PATH = `${path}${pathSeparator}${env.PATH}`; + return true; + } + return false; + } + + private async tryVersionManagerPath( + basePath: string, + env: Record + ): Promise { + try { + await access(basePath); + const versions = await readdir(basePath); + if (versions.length > 0) { + const latestVersion = versions.sort().pop(); + if (latestVersion) { + const binSubPath = basePath.includes('fnm') + ? join('installation', 'bin') + : 'bin'; + const nodeBinPath = join(basePath, latestVersion, binSubPath); + + try { + await access(nodeBinPath); + return this.tryAddPathToEnv(env, nodeBinPath); + } catch { + return false; + } + } + } + } catch { + return false; + } + return false; + } + + private async getNodeEnvironment(): Promise< + Record + > { + const env = { ...process.env }; + + const versionManagerPaths = [ + join(homedir(), '.local', 'share', 'fnm', 'node-versions'), + join(homedir(), '.nvm', 'versions', 'node'), + join(homedir(), '.volta', 'tools', 'image', 'node'), + join(homedir(), '.asdf', 'installs', 'nodejs'), + ]; + + const systemPaths: string[] = []; + if (process.platform === 'darwin') { + systemPaths.push('/opt/homebrew/bin', '/usr/local/bin'); + } + if (process.platform === 'win32') { + versionManagerPaths.push( + join(homedir(), 'AppData', 'Local', 'fnm', 'node-versions'), + join(homedir(), 'AppData', 'Roaming', 'nvm') + ); + } + + for (const systemPath of systemPaths) { + try { + await access(systemPath); + if (await this.tryAddPathToEnv(env, systemPath)) { + return env; + } + } catch { + continue; + } + } + + for (const versionPath of versionManagerPaths) { + if (await this.tryVersionManagerPath(versionPath, env)) { + return env; + } + } + + return env; + } + async isNpxAvailable(): Promise { try { - const testProcess = spawn('npx', ['--version'], { - stdio: 'pipe', - shell: true, - }); + const env = await this.getNodeEnvironment(); + const testProcess = spawn('npx', ['--version'], { stdio: 'pipe', env }); return new Promise((resolve) => { const timeout = setTimeout(() => { @@ -115,10 +198,11 @@ export class SillyTavernManager { } private async createNpxProcess(args: string[]): Promise { + const env = await this.getNodeEnvironment(); return spawn('npx', args, { stdio: ['pipe', 'pipe', 'pipe'], detached: false, - shell: true, + env, }); }