windows needs special handling for CLI mode

This commit is contained in:
lone-cloud 2025-08-24 17:05:59 -07:00
parent 31a1b2485f
commit a2695109a6
4 changed files with 48 additions and 18 deletions

View file

@ -22,9 +22,10 @@ A desktop app for running Large Language Models locally. <!-- markdownlint-disab
Download the latest release for your platform from the [GitHub Releases page](https://github.com/lone-cloud/friendly-kobold/releases/latest): Download the latest release for your platform from the [GitHub Releases page](https://github.com/lone-cloud/friendly-kobold/releases/latest):
- **Windows**: `Friendly-Kobold-X.X.X.exe` (portable executable) - **Windows**: `Friendly Kobold Portable X.X.X.exe` (portable executable)
- **macOS**: `Friendly-Kobold-X.X.X.dmg` (disk image) - **Windows**: `Friendly Kobold Setup X.X.X.exe` (installer executable - **recommended for CLI mode**)
- **Linux**: `Friendly-Kobold-X.X.X.AppImage` (portable application) - **macOS**: `Friendly Kobold-X.X.X.dmg` (disk image)
- **Linux**: `Friendly Kobold-X.X.X.AppImage` (portable application)
#### Linux - AUR (Arch Linux) #### Linux - AUR (Arch Linux)
@ -93,6 +94,8 @@ You might want to run CLI Mode if you're looking to use a different frontend, su
### Usage ### Usage
**Linux/macOS:**
```bash ```bash
# Basic usage - launch KoboldCpp with no arguments # Basic usage - launch KoboldCpp with no arguments
./friendly-kobold --cli ./friendly-kobold --cli
@ -105,6 +108,10 @@ You might want to run CLI Mode if you're looking to use a different frontend, su
./friendly-kobold --cli --model /path/to/model.gguf --port 5001 --host 0.0.0.0 --multiuser 2 ./friendly-kobold --cli --model /path/to/model.gguf --port 5001 --host 0.0.0.0 --multiuser 2
``` ```
**Windows:**
CLI mode will only work correctly on Windows if you install Friendly Kobold using the Setup.exe from the github releases. Otherwise there is currently a technical limitation with the Windows portable .exe which will cause it to not display the terminal output correctly nor will it be killable through the standard terminal (Ctrl+C) commands.
## For Local Dev ## For Local Dev
### Prerequisites ### Prerequisites

View file

@ -153,6 +153,12 @@
"win": { "win": {
"compression": "normal", "compression": "normal",
"target": [ "target": [
{
"target": "nsis",
"arch": [
"x64"
]
},
{ {
"target": "portable", "target": "portable",
"arch": [ "arch": [
@ -161,6 +167,17 @@
} }
] ]
}, },
"nsis": {
"artifactName": "${productName} Setup ${version}.${ext}",
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Friendly Kobold"
},
"portable": {
"artifactName": "${productName} Portable ${version}.${ext}"
},
"linux": { "linux": {
"compression": "store", "compression": "store",
"category": "Development", "category": "Development",

View file

@ -160,7 +160,6 @@ if (isCliMode) {
process.exit(1); process.exit(1);
} }
} }
runCliMode(); runCliMode();
} else { } else {
const friendlyKoboldApp = new FriendlyKoboldApp(); const friendlyKoboldApp = new FriendlyKoboldApp();

View file

@ -1,3 +1,4 @@
/* eslint-disable no-console */
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { app } from 'electron'; import { app } from 'electron';
@ -27,7 +28,6 @@ export class CliHandler {
const currentBinary = this.configManager.getCurrentKoboldBinary(); const currentBinary = this.configManager.getCurrentKoboldBinary();
if (!currentBinary) { if (!currentBinary) {
// eslint-disable-next-line no-console
console.error( console.error(
'Error: No KoboldCpp binary found. Please run the GUI first to download KoboldCpp.' 'Error: No KoboldCpp binary found. Please run the GUI first to download KoboldCpp.'
); );
@ -35,29 +35,38 @@ export class CliHandler {
} }
if (!existsSync(currentBinary)) { if (!existsSync(currentBinary)) {
// eslint-disable-next-line no-console
console.error(`Error: KoboldCpp binary not found at: ${currentBinary}`); console.error(`Error: KoboldCpp binary not found at: ${currentBinary}`);
// eslint-disable-next-line no-console
console.error('Please run the GUI to download or reconfigure KoboldCpp.'); console.error('Please run the GUI to download or reconfigure KoboldCpp.');
process.exit(1); process.exit(1);
} }
// eslint-disable-next-line no-console
console.log(`Launching KoboldCpp: ${currentBinary}`);
// eslint-disable-next-line no-console
console.log(`Arguments: ${args.join(' ')}`);
// eslint-disable-next-line no-console
console.log('─'.repeat(60));
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const isWindows = process.platform === 'win32';
const child = spawn(currentBinary, args, { const child = spawn(currentBinary, args, {
stdio: 'inherit', stdio: isWindows ? 'pipe' : 'inherit',
detached: false, detached: false,
}); });
if (isWindows) {
child.stdout?.setEncoding('utf8');
child.stderr?.setEncoding('utf8');
child.stdout?.on('data', (data) => {
process.stdout.write(data.toString());
});
child.stderr?.on('data', (data) => {
process.stderr.write(data.toString());
});
if (child.stdin && process.stdin.readable) {
process.stdin.pipe(child.stdin);
}
}
child.on('exit', (code, signal) => { child.on('exit', (code, signal) => {
if (signal) { if (signal) {
// eslint-disable-next-line no-console
console.log(`\nProcess terminated with signal: ${signal}`); console.log(`\nProcess terminated with signal: ${signal}`);
process.exit(128 + (signal === 'SIGTERM' ? 15 : 2)); process.exit(128 + (signal === 'SIGTERM' ? 15 : 2));
} else if (code !== null) { } else if (code !== null) {
@ -68,13 +77,11 @@ export class CliHandler {
}); });
child.on('error', (error) => { child.on('error', (error) => {
// eslint-disable-next-line no-console
console.error(`Failed to start KoboldCpp: ${error.message}`); console.error(`Failed to start KoboldCpp: ${error.message}`);
reject(error); reject(error);
}); });
const handleSignal = () => { const handleSignal = () => {
// eslint-disable-next-line no-console
console.log('\nReceived termination signal, terminating KoboldCpp...'); console.log('\nReceived termination signal, terminating KoboldCpp...');
if (!child.killed) { if (!child.killed) {
child.kill('SIGTERM'); child.kill('SIGTERM');