lemonade-interactive-loader 1.0.1 → 1.0.3
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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/cli/menu.js +26 -4
- package/src/cli/setup-wizard.js +1 -1
- package/src/config/constants.js +3 -3
- package/src/services/download.js +1 -1
- package/src/services/server.js +77 -5
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ The main menu adapts based on whether you have a configuration saved:
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
| Command | Description |
|
|
76
|
-
|
|
76
|
+
|---------|-------------|
|
|
77
77
|
| **▶️ Start Server** | Launch Lemonade Server with saved config (when config exists) |
|
|
78
78
|
| **✏️ Edit Configuration** | Update your saved settings interactively |
|
|
79
79
|
| **👁️ View Configuration** | See your current configuration and installed builds |
|
|
@@ -169,7 +169,7 @@ Point Lemonade Server to your existing model directory (like LM Studio's):
|
|
|
169
169
|
|
|
170
170
|
Configuration is automatically saved and loaded:
|
|
171
171
|
|
|
172
|
-
- **Location**: `~/.lemonade-launcher/config.json` (Linux/macOS) or `%USERPROFILE%\.lemonade-launcher\config.json` (Windows)
|
|
172
|
+
- **Location**: `~/.lemonade-interactive-launcher/config.json` (Linux/macOS) or `%USERPROFILE%\.lemonade-interactive-launcher\config.json` (Windows)
|
|
173
173
|
- **Format**: JSON
|
|
174
174
|
- **Auto-saved**: After every setup or edit
|
|
175
175
|
|
|
@@ -250,7 +250,7 @@ Run Command Prompt or PowerShell as Administrator.
|
|
|
250
250
|
|
|
251
251
|
#### Build download fails
|
|
252
252
|
- Check your internet connection
|
|
253
|
-
- Ensure you have write permissions to `~/.lemonade-launcher/`
|
|
253
|
+
- Ensure you have write permissions to `~/.lemonade-interactive-launcher/`
|
|
254
254
|
- Try downloading the asset manually from GitHub
|
|
255
255
|
|
|
256
256
|
#### Server won't start
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemonade-interactive-loader",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Interactive CLI tool to launch Lemonade Server with custom arguments and download llama.cpp releases - Cross-platform (Windows/Linux)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/src/cli/menu.js
CHANGED
|
@@ -3,7 +3,7 @@ const { loadConfig } = require('../config');
|
|
|
3
3
|
const { getAllInstalledAssets, getLlamaServerPath, downloadAndExtractLlamaCpp, deleteInstalledAsset } = require('../services/asset-manager');
|
|
4
4
|
const { selectLlamaCppRelease, selectAsset, askLaunchServer } = require('./prompts');
|
|
5
5
|
const { runSetupWizard } = require('./setup-wizard');
|
|
6
|
-
const { launchLemonadeServer } = require('../services/server');
|
|
6
|
+
const { launchLemonadeServer, setupShutdownHandlers } = require('../services/server');
|
|
7
7
|
const { inferBackendType, formatBytes } = require('../utils/system');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -263,15 +263,20 @@ async function resetConfiguration() {
|
|
|
263
263
|
/**
|
|
264
264
|
* Handle main menu command
|
|
265
265
|
* @param {string} command - Selected command
|
|
266
|
+
* @returns {Promise<boolean>} True if should exit the app
|
|
266
267
|
*/
|
|
267
268
|
async function handleCommand(command) {
|
|
269
|
+
let shouldExit = false;
|
|
270
|
+
|
|
268
271
|
switch (command) {
|
|
269
272
|
case 'setup':
|
|
270
273
|
await runSetupWizard(false);
|
|
271
274
|
if (await askLaunchServer()) {
|
|
272
275
|
const config = loadConfig();
|
|
273
276
|
if (Object.keys(config).length > 0) {
|
|
277
|
+
setupShutdownHandlers();
|
|
274
278
|
await launchLemonadeServer(config);
|
|
279
|
+
shouldExit = true;
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
break;
|
|
@@ -281,7 +286,9 @@ async function handleCommand(command) {
|
|
|
281
286
|
if (await askLaunchServer()) {
|
|
282
287
|
const config = loadConfig();
|
|
283
288
|
if (Object.keys(config).length > 0) {
|
|
289
|
+
setupShutdownHandlers();
|
|
284
290
|
await launchLemonadeServer(config);
|
|
291
|
+
shouldExit = true;
|
|
285
292
|
}
|
|
286
293
|
}
|
|
287
294
|
break;
|
|
@@ -294,7 +301,7 @@ async function handleCommand(command) {
|
|
|
294
301
|
await resetConfiguration();
|
|
295
302
|
break;
|
|
296
303
|
|
|
297
|
-
|
|
304
|
+
|
|
298
305
|
|
|
299
306
|
case 'manage':
|
|
300
307
|
let manageAction;
|
|
@@ -319,11 +326,17 @@ async function handleCommand(command) {
|
|
|
319
326
|
const config = loadConfig();
|
|
320
327
|
if (Object.keys(config).length === 0) {
|
|
321
328
|
console.log('No configuration found. Please run "setup" first.');
|
|
322
|
-
return;
|
|
329
|
+
return false;
|
|
323
330
|
}
|
|
331
|
+
// Set up shutdown handlers to kill server on exit
|
|
332
|
+
setupShutdownHandlers();
|
|
324
333
|
await launchLemonadeServer(config);
|
|
334
|
+
// After server exits, exit the app completely
|
|
335
|
+
shouldExit = true;
|
|
325
336
|
break;
|
|
326
337
|
}
|
|
338
|
+
|
|
339
|
+
return shouldExit;
|
|
327
340
|
}
|
|
328
341
|
|
|
329
342
|
/**
|
|
@@ -334,7 +347,12 @@ async function runCLI() {
|
|
|
334
347
|
|
|
335
348
|
while (continueRunning) {
|
|
336
349
|
const command = await showMainMenu();
|
|
337
|
-
await handleCommand(command);
|
|
350
|
+
const shouldExit = await handleCommand(command);
|
|
351
|
+
|
|
352
|
+
// Exit the app if we just exited from the server
|
|
353
|
+
if (shouldExit) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
338
356
|
|
|
339
357
|
const { continueRunning: shouldContinue } = await inquirer.prompt([
|
|
340
358
|
{
|
|
@@ -349,6 +367,10 @@ async function runCLI() {
|
|
|
349
367
|
}
|
|
350
368
|
|
|
351
369
|
console.log('\n👋 Goodbye!\n');
|
|
370
|
+
|
|
371
|
+
// Ensure server is shut down when exiting the CLI
|
|
372
|
+
const { shutdownLemonadeServer } = require('../services/server');
|
|
373
|
+
shutdownLemonadeServer();
|
|
352
374
|
}
|
|
353
375
|
|
|
354
376
|
module.exports = {
|
package/src/cli/setup-wizard.js
CHANGED
package/src/config/constants.js
CHANGED
|
@@ -2,14 +2,14 @@ const path = require('path');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
|
|
4
4
|
// Configuration directories
|
|
5
|
-
const USER_CONFIG_DIR = path.join(os.homedir(), '.lemonade-launcher');
|
|
5
|
+
const USER_CONFIG_DIR = path.join(os.homedir(), '.lemonade-interactive-launcher');
|
|
6
6
|
const USER_CONFIG_FILE = path.join(USER_CONFIG_DIR, 'config.json');
|
|
7
|
-
const DEFAULT_LLAMACPP_INSTALL_DIR = path.join(os.homedir(), '.lemonade-launcher', 'llama-cpp');
|
|
7
|
+
const DEFAULT_LLAMACPP_INSTALL_DIR = path.join(os.homedir(), '.lemonade-interactive-launcher', 'llama-cpp');
|
|
8
8
|
|
|
9
9
|
// GitHub API
|
|
10
10
|
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/ggml-org/llama.cpp/releases';
|
|
11
11
|
const GITHUB_API_HEADERS = {
|
|
12
|
-
'User-Agent': 'lemonade-launcher',
|
|
12
|
+
'User-Agent': 'lemonade-interactive-launcher',
|
|
13
13
|
'Accept': 'application/vnd.github.v3+json'
|
|
14
14
|
};
|
|
15
15
|
|
package/src/services/download.js
CHANGED
|
@@ -20,7 +20,7 @@ function downloadFile(url, outputPath) {
|
|
|
20
20
|
|
|
21
21
|
const file = fs.createWriteStream(outputPath);
|
|
22
22
|
|
|
23
|
-
const req = protocol.get(url, { headers: { 'User-Agent': 'lemonade-launcher' } }, (res) => {
|
|
23
|
+
const req = protocol.get(url, { headers: { 'User-Agent': 'lemonade-interactive-launcher' } }, (res) => {
|
|
24
24
|
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
25
25
|
downloadFile(res.headers.location, outputPath)
|
|
26
26
|
.then(resolve)
|
package/src/services/server.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const { execSync } = require('child_process');
|
|
2
|
+
const { execSync, spawn } = require('child_process');
|
|
3
3
|
const { LEMONADE_SERVER_DEFAULT_PATH } = require('../config/constants');
|
|
4
4
|
const { findLlamaServer } = require('../utils/system');
|
|
5
5
|
const { getLlamaServerPath } = require('./asset-manager');
|
|
6
6
|
|
|
7
|
+
// Track the server process for graceful shutdown
|
|
8
|
+
let serverProcess = null;
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* Build server command arguments
|
|
9
12
|
* @param {Object} config - Server configuration
|
|
@@ -154,21 +157,90 @@ async function launchLemonadeServer(config) {
|
|
|
154
157
|
console.log('\nStarting server...\n');
|
|
155
158
|
|
|
156
159
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
// Build the command for spawning
|
|
161
|
+
const commandStr = formatCommand(serverPath, args, {});
|
|
162
|
+
|
|
163
|
+
// Parse the command into executable and arguments
|
|
164
|
+
const parts = commandStr.trim().split(/\s+/);
|
|
165
|
+
const executable = parts[0];
|
|
166
|
+
const execArgs = parts.slice(1);
|
|
167
|
+
|
|
168
|
+
console.log(`Spawning: ${executable} ${execArgs.join(' ')}`);
|
|
169
|
+
|
|
170
|
+
// Spawn the server process
|
|
171
|
+
serverProcess = spawn(executable, execArgs, {
|
|
159
172
|
stdio: 'inherit',
|
|
160
173
|
env: process.env
|
|
161
174
|
});
|
|
175
|
+
|
|
176
|
+
// Wait for the process to exit (blocking)
|
|
177
|
+
await new Promise((resolve, reject) => {
|
|
178
|
+
serverProcess.on('close', (code) => {
|
|
179
|
+
console.log(`\nServer exited with code ${code}`);
|
|
180
|
+
serverProcess = null;
|
|
181
|
+
resolve(code);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
serverProcess.on('error', (err) => {
|
|
185
|
+
console.error(`Server process error: ${err.message}`);
|
|
186
|
+
serverProcess = null;
|
|
187
|
+
reject(err);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
162
191
|
} catch (error) {
|
|
163
|
-
console.error(`Server exited with error
|
|
192
|
+
console.error(`Server exited with error: ${error.message}`);
|
|
164
193
|
if (error.status !== null) {
|
|
165
194
|
process.exit(error.status);
|
|
166
195
|
}
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
|
|
199
|
+
/**
|
|
200
|
+
* Gracefully shutdown the lemonade server
|
|
201
|
+
*/
|
|
202
|
+
function shutdownLemonadeServer() {
|
|
203
|
+
if (serverProcess) {
|
|
204
|
+
console.log('\n\nShutting down Lemonade Server...');
|
|
205
|
+
|
|
206
|
+
// Try graceful shutdown first (SIGTERM)
|
|
207
|
+
serverProcess.on('exit', () => {
|
|
208
|
+
console.log('Server shut down successfully.');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
serverProcess.on('error', (err) => {
|
|
212
|
+
console.error(`Error shutting down server: ${err.message}`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Send SIGTERM signal
|
|
216
|
+
serverProcess.kill('SIGTERM');
|
|
217
|
+
|
|
218
|
+
// Force kill after 5 seconds if still running
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
if (serverProcess && !serverProcess.killed) {
|
|
221
|
+
console.log('Force killing server...');
|
|
222
|
+
serverProcess.kill('SIGKILL');
|
|
223
|
+
}
|
|
224
|
+
}, 5000);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Set up signal handlers for graceful shutdown
|
|
229
|
+
function setupShutdownHandlers() {
|
|
230
|
+
const shutdown = (signal) => {
|
|
231
|
+
console.log(`\n\nReceived ${signal}. Shutting down...`);
|
|
232
|
+
shutdownLemonadeServer();
|
|
233
|
+
setTimeout(() => process.exit(0), 2000);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
process.on('SIGINT', () => shutdown('SIGINT')); // Ctrl+C
|
|
237
|
+
process.on('SIGTERM', () => shutdown('SIGTERM')); // Termination signal
|
|
238
|
+
}
|
|
239
|
+
|
|
170
240
|
module.exports = {
|
|
171
241
|
buildServerArgs,
|
|
172
242
|
formatCommand,
|
|
173
|
-
launchLemonadeServer
|
|
243
|
+
launchLemonadeServer,
|
|
244
|
+
shutdownLemonadeServer,
|
|
245
|
+
setupShutdownHandlers
|
|
174
246
|
};
|