lemonade-interactive-loader 1.0.3 → 1.0.4
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 +7 -6
- package/lemonade-interactive-loader-1.0.4.tgz +0 -0
- package/package.json +2 -1
- package/src/README.md +1 -1
- package/src/cli/menu.js +1 -0
- package/src/cli/prompts.js +1 -0
- package/src/cli/setup-wizard.js +34 -7
- package/src/config/constants.js +14 -1
- package/src/services/server.js +96 -26
package/README.md
CHANGED
|
@@ -83,16 +83,17 @@ The main menu adapts based on whether you have a configuration saved:
|
|
|
83
83
|
|
|
84
84
|
### The Setup Wizard
|
|
85
85
|
|
|
86
|
-
Just answer
|
|
86
|
+
Just answer 9 simple questions:
|
|
87
87
|
|
|
88
88
|
1. **Network access?** Should the server be accessible from other devices?
|
|
89
89
|
2. **Port number?** Which port should it run on? (default: 8080)
|
|
90
90
|
3. **Logging level?** Choose from info, debug, warning, or error
|
|
91
|
-
4. **
|
|
92
|
-
5. **
|
|
93
|
-
6. **
|
|
94
|
-
7. **Custom
|
|
95
|
-
8. **
|
|
91
|
+
4. **Context window size?** Choose from 4K, 8K, 16K, 32K, 64K, 128K, or 256K tokens (default: 4K)
|
|
92
|
+
5. **Model directory?** Point to existing models (like LM Studio) if needed
|
|
93
|
+
6. **Interface type?** System tray or headless mode
|
|
94
|
+
7. **Custom arguments?** Any additional llama.cpp parameters?
|
|
95
|
+
8. **Custom build?** Use a specific llama.cpp build from GitHub?
|
|
96
|
+
9. **Backend?** Choose auto, vulkan, rocm, or cpu
|
|
96
97
|
|
|
97
98
|
## ✨ Key Features
|
|
98
99
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lemonade-interactive-loader",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"adm-zip": "^0.5.16",
|
|
36
36
|
"inquirer": "^8.2.6",
|
|
37
37
|
"tar": "^7.5.9",
|
|
38
|
+
"tree-kill": "^1.2.2",
|
|
38
39
|
"unzipper": "^0.12.3"
|
|
39
40
|
}
|
|
40
41
|
}
|
package/src/README.md
CHANGED
|
@@ -43,7 +43,7 @@ src/
|
|
|
43
43
|
### CLI (`cli/`)
|
|
44
44
|
- **menu.js**: Main menu system and command routing
|
|
45
45
|
- **prompts.js**: Interactive prompts for release/asset selection
|
|
46
|
-
- **setup-wizard.js**:
|
|
46
|
+
- **setup-wizard.js**: 9-question setup wizard
|
|
47
47
|
|
|
48
48
|
## Architecture Principles
|
|
49
49
|
|
package/src/cli/menu.js
CHANGED
|
@@ -213,6 +213,7 @@ async function viewConfiguration() {
|
|
|
213
213
|
console.log(`Host: ${config.host}`);
|
|
214
214
|
console.log(`Port: ${config.port}`);
|
|
215
215
|
console.log(`Log Level: ${config.logLevel}`);
|
|
216
|
+
console.log(`Context Size: ${config.contextSize}`);
|
|
216
217
|
console.log(`Backend: ${config.backend}`);
|
|
217
218
|
console.log(`Model Directory: ${config.modelDir}`);
|
|
218
219
|
console.log(`Run Mode: ${config.runMode}`);
|
package/src/cli/prompts.js
CHANGED
|
@@ -223,6 +223,7 @@ function displayConfigSummary(config) {
|
|
|
223
223
|
console.log(`Host: ${config.host}`);
|
|
224
224
|
console.log(`Port: ${config.port}`);
|
|
225
225
|
console.log(`Log Level: ${config.logLevel}`);
|
|
226
|
+
console.log(`Context Size: ${config.contextSize}`);
|
|
226
227
|
console.log(`Backend: ${config.backend}`);
|
|
227
228
|
console.log(`Model Directory: ${config.modelDir}`);
|
|
228
229
|
console.log(`Run Mode: ${config.runMode}`);
|
package/src/cli/setup-wizard.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const inquirer = require('inquirer');
|
|
2
|
-
const { DEFAULTS, BACKEND_TYPES, LOG_LEVELS, RUN_MODES, HOSTS } = require('../config/constants');
|
|
2
|
+
const { DEFAULTS, BACKEND_TYPES, LOG_LEVELS, RUN_MODES, HOSTS, CONTEXT_SIZES } = require('../config/constants');
|
|
3
3
|
const { loadConfig, saveConfig } = require('../config');
|
|
4
4
|
const { selectLlamaCppRelease, selectAsset, selectInstalledAssetPrompt, displayConfigSummary } = require('./prompts');
|
|
5
5
|
const { downloadAndExtractLlamaCpp } = require('../services/asset-manager');
|
|
@@ -54,9 +54,35 @@ async function runSetupWizard(isEdit = false) {
|
|
|
54
54
|
}
|
|
55
55
|
]);
|
|
56
56
|
|
|
57
|
-
// Q4:
|
|
57
|
+
// Q4: Context window size
|
|
58
|
+
const savedContextSize = existingConfig.contextSize || DEFAULTS.CONTEXT_SIZE;
|
|
59
|
+
const contextSizeChoices = [
|
|
60
|
+
{ name: '4K (4096 tokens) - Default', value: CONTEXT_SIZES['4K'] },
|
|
61
|
+
{ name: '8K (8192 tokens)', value: CONTEXT_SIZES['8K'] },
|
|
62
|
+
{ name: '16K (16384 tokens)', value: CONTEXT_SIZES['16K'] },
|
|
63
|
+
{ name: '32K (32768 tokens)', value: CONTEXT_SIZES['32K'] },
|
|
64
|
+
{ name: '64K (65536 tokens)', value: CONTEXT_SIZES['64K'] },
|
|
65
|
+
{ name: '128K (131072 tokens)', value: CONTEXT_SIZES['128K'] },
|
|
66
|
+
{ name: '256K (262144 tokens)', value: CONTEXT_SIZES['256K'] }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Find the index of the saved/default context size
|
|
70
|
+
const defaultContextSizeIndex = contextSizeChoices.findIndex(choice => choice.value === savedContextSize);
|
|
71
|
+
const defaultContextSize = defaultContextSizeIndex >= 0 ? defaultContextSizeIndex : 0;
|
|
72
|
+
|
|
73
|
+
const { contextSize } = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'contextSize',
|
|
77
|
+
message: 'How big should the context window be?',
|
|
78
|
+
choices: contextSizeChoices,
|
|
79
|
+
default: defaultContextSize
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
// Q5: Custom model directory
|
|
58
84
|
const existingModelDir = existingConfig.modelDir;
|
|
59
|
-
const hasExistingModelDir = existingModelDir
|
|
85
|
+
const hasExistingModelDir = existingModelDir !== undefined;
|
|
60
86
|
|
|
61
87
|
const { useCustomModelDir } = await inquirer.prompt([
|
|
62
88
|
{
|
|
@@ -83,7 +109,7 @@ async function runSetupWizard(isEdit = false) {
|
|
|
83
109
|
finalModelDir = DEFAULTS.MODEL_DIR;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
|
-
//
|
|
112
|
+
// Q6: System tray vs headless
|
|
87
113
|
const { runMode } = await inquirer.prompt([
|
|
88
114
|
{
|
|
89
115
|
type: 'list',
|
|
@@ -97,7 +123,7 @@ async function runSetupWizard(isEdit = false) {
|
|
|
97
123
|
}
|
|
98
124
|
]);
|
|
99
125
|
|
|
100
|
-
//
|
|
126
|
+
// Q7: Custom llama.cpp args
|
|
101
127
|
const existingLlamacppArgs = existingConfig.llamacppArgs || '';
|
|
102
128
|
const hasExistingArgs = existingLlamacppArgs.length > 0;
|
|
103
129
|
|
|
@@ -117,7 +143,7 @@ async function runSetupWizard(isEdit = false) {
|
|
|
117
143
|
{
|
|
118
144
|
type: 'input',
|
|
119
145
|
name: 'llamacppArgs',
|
|
120
|
-
message: 'Enter llama.cpp arguments (comma-separated, e.g., --
|
|
146
|
+
message: 'Enter llama.cpp arguments (comma-separated, e.g., --no-mmap,--batch-size 512):',
|
|
121
147
|
default: existingLlamacppArgs
|
|
122
148
|
}
|
|
123
149
|
]);
|
|
@@ -126,7 +152,7 @@ async function runSetupWizard(isEdit = false) {
|
|
|
126
152
|
finalLlamacppArgs = '';
|
|
127
153
|
}
|
|
128
154
|
|
|
129
|
-
//
|
|
155
|
+
// Q8: Custom llama.cpp build
|
|
130
156
|
const existingCustomPath = existingConfig.customLlamacppPath || '';
|
|
131
157
|
const hasExistingBuild = existingCustomPath.length > 0;
|
|
132
158
|
|
|
@@ -209,6 +235,7 @@ async function runSetupWizard(isEdit = false) {
|
|
|
209
235
|
host,
|
|
210
236
|
port: parseInt(port),
|
|
211
237
|
logLevel,
|
|
238
|
+
contextSize,
|
|
212
239
|
backend,
|
|
213
240
|
modelDir: finalModelDir,
|
|
214
241
|
runMode,
|
package/src/config/constants.js
CHANGED
|
@@ -57,6 +57,17 @@ const HOSTS = {
|
|
|
57
57
|
ALL_INTERFACES: '0.0.0.0'
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// Context window sizes (in tokens)
|
|
61
|
+
const CONTEXT_SIZES = {
|
|
62
|
+
'4K': 4096,
|
|
63
|
+
'8K': 8192,
|
|
64
|
+
'16K': 16384,
|
|
65
|
+
'32K': 32768,
|
|
66
|
+
'64K': 65536,
|
|
67
|
+
'128K': 131072,
|
|
68
|
+
'256K': 262144
|
|
69
|
+
};
|
|
70
|
+
|
|
60
71
|
// Default values
|
|
61
72
|
const DEFAULTS = {
|
|
62
73
|
PORT: 8080,
|
|
@@ -65,7 +76,8 @@ const DEFAULTS = {
|
|
|
65
76
|
EXPOSE_TO_NETWORK: false,
|
|
66
77
|
RUN_MODE: RUN_MODES.SYSTEM_TRAY,
|
|
67
78
|
MODEL_DIR: 'None',
|
|
68
|
-
BACKEND: BACKEND_TYPES.AUTO
|
|
79
|
+
BACKEND: BACKEND_TYPES.AUTO,
|
|
80
|
+
CONTEXT_SIZE: CONTEXT_SIZES['4K']
|
|
69
81
|
};
|
|
70
82
|
|
|
71
83
|
module.exports = {
|
|
@@ -79,5 +91,6 @@ module.exports = {
|
|
|
79
91
|
LOG_LEVELS,
|
|
80
92
|
RUN_MODES,
|
|
81
93
|
HOSTS,
|
|
94
|
+
CONTEXT_SIZES,
|
|
82
95
|
DEFAULTS
|
|
83
96
|
};
|
package/src/services/server.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const { execSync, spawn } = require('child_process');
|
|
3
|
+
const kill = require('tree-kill');
|
|
3
4
|
const { LEMONADE_SERVER_DEFAULT_PATH } = require('../config/constants');
|
|
4
5
|
const { findLlamaServer } = require('../utils/system');
|
|
5
6
|
const { getLlamaServerPath } = require('./asset-manager');
|
|
6
7
|
|
|
7
8
|
// Track the server process for graceful shutdown
|
|
8
9
|
let serverProcess = null;
|
|
10
|
+
let isShuttingDown = false;
|
|
11
|
+
let hasExited = false;
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Build server command arguments
|
|
@@ -13,7 +16,7 @@ let serverProcess = null;
|
|
|
13
16
|
* @returns {Array} Array of command arguments
|
|
14
17
|
*/
|
|
15
18
|
function buildServerArgs(config) {
|
|
16
|
-
const { host, port, logLevel, modelDir, llamacppArgs } = config;
|
|
19
|
+
const { host, port, logLevel, modelDir, llamacppArgs, contextSize } = config;
|
|
17
20
|
const args = [
|
|
18
21
|
'serve',
|
|
19
22
|
'--log-level', logLevel || 'info',
|
|
@@ -21,6 +24,10 @@ function buildServerArgs(config) {
|
|
|
21
24
|
'--port', port.toString()
|
|
22
25
|
];
|
|
23
26
|
|
|
27
|
+
if (contextSize) {
|
|
28
|
+
args.push('--ctx-size', contextSize.toString());
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
if (modelDir && modelDir !== 'None') {
|
|
25
32
|
args.push('--extra-models-dir', modelDir);
|
|
26
33
|
}
|
|
@@ -76,6 +83,7 @@ async function launchLemonadeServer(config) {
|
|
|
76
83
|
host,
|
|
77
84
|
port,
|
|
78
85
|
logLevel,
|
|
86
|
+
contextSize,
|
|
79
87
|
modelDir,
|
|
80
88
|
llamacppArgs,
|
|
81
89
|
runMode,
|
|
@@ -89,6 +97,7 @@ async function launchLemonadeServer(config) {
|
|
|
89
97
|
console.log(`Host: ${host}`);
|
|
90
98
|
console.log(`Port: ${port}`);
|
|
91
99
|
console.log(`Log Level: ${logLevel}`);
|
|
100
|
+
console.log(`Context Size: ${contextSize || 'default'}`);
|
|
92
101
|
console.log(`Backend: ${backend || 'auto'}`);
|
|
93
102
|
console.log(`Model Directory: ${modelDir || 'default'}`);
|
|
94
103
|
console.log(`Run Mode: ${runMode || 'headless'}`);
|
|
@@ -175,8 +184,11 @@ async function launchLemonadeServer(config) {
|
|
|
175
184
|
|
|
176
185
|
// Wait for the process to exit (blocking)
|
|
177
186
|
await new Promise((resolve, reject) => {
|
|
178
|
-
serverProcess.on('
|
|
179
|
-
|
|
187
|
+
serverProcess.on('exit', (code, signal) => {
|
|
188
|
+
// Only log if shutdown wasn't initiated by us
|
|
189
|
+
if (!hasExited) {
|
|
190
|
+
console.log(`\nServer exited with status ${code || 'None'} and signal ${signal || 'None'}`);
|
|
191
|
+
}
|
|
180
192
|
serverProcess = null;
|
|
181
193
|
resolve(code);
|
|
182
194
|
});
|
|
@@ -197,37 +209,95 @@ async function launchLemonadeServer(config) {
|
|
|
197
209
|
}
|
|
198
210
|
|
|
199
211
|
/**
|
|
200
|
-
* Gracefully shutdown the lemonade server
|
|
212
|
+
* Gracefully shutdown the lemonade server and all child processes
|
|
201
213
|
*/
|
|
202
214
|
function shutdownLemonadeServer() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
215
|
+
// Prevent duplicate shutdown calls
|
|
216
|
+
if (isShuttingDown) {
|
|
217
|
+
console.log('Shutdown already in progress...');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!serverProcess || !serverProcess.pid) {
|
|
222
|
+
console.log('No server process to shut down.');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
isShuttingDown = true;
|
|
227
|
+
hasExited = false;
|
|
228
|
+
console.log('\n\nShutting down Lemonade Server and child processes...');
|
|
229
|
+
|
|
230
|
+
// Check if process is still running
|
|
231
|
+
try {
|
|
232
|
+
// Sending signal 0 checks if process exists without actually sending a signal
|
|
233
|
+
process.kill(serverProcess.pid, 0);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
// Process is already dead
|
|
236
|
+
console.log('Server process is already terminated.');
|
|
237
|
+
serverProcess = null;
|
|
238
|
+
isShuttingDown = false;
|
|
239
|
+
return;
|
|
225
240
|
}
|
|
241
|
+
|
|
242
|
+
// Remove existing event listeners to prevent duplicates
|
|
243
|
+
serverProcess.removeAllListeners('exit');
|
|
244
|
+
serverProcess.removeAllListeners('error');
|
|
245
|
+
|
|
246
|
+
// Try graceful shutdown first (SIGINT)
|
|
247
|
+
serverProcess.on('exit', (code, signal) => {
|
|
248
|
+
hasExited = true;
|
|
249
|
+
console.log(`Server exited with status ${code || 'None'} and signal ${signal || 'None'}`);
|
|
250
|
+
console.log('Server shut down successfully.');
|
|
251
|
+
serverProcess = null;
|
|
252
|
+
isShuttingDown = false;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
serverProcess.on('error', (err) => {
|
|
256
|
+
console.error(`Error shutting down server: ${err.message}`);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Use tree-kill to terminate the process and all its children
|
|
260
|
+
kill(serverProcess.pid, 'SIGINT', (err) => {
|
|
261
|
+
if (err && !hasExited) {
|
|
262
|
+
// Only log error if process hasn't already exited naturally
|
|
263
|
+
if (err.code !== 'ESRCH') {
|
|
264
|
+
console.log(`Note: Could not kill process tree: ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Force kill with SIGKILL after 3 seconds if still running
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
// Don't force kill if already exited
|
|
272
|
+
if (hasExited || !isShuttingDown) return;
|
|
273
|
+
|
|
274
|
+
// Check again if process is still running before force kill
|
|
275
|
+
try {
|
|
276
|
+
process.kill(serverProcess.pid, 0);
|
|
277
|
+
// Process still exists, force kill it
|
|
278
|
+
kill(serverProcess.pid, 'SIGKILL', (err) => {
|
|
279
|
+
if (err && !hasExited) {
|
|
280
|
+
// Only log error if process hasn't already exited naturally
|
|
281
|
+
if (err.code !== 'ESRCH') {
|
|
282
|
+
console.log(`Note: Could not force kill process: ${err.message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
} catch (err) {
|
|
287
|
+
// Process has already exited
|
|
288
|
+
console.log('Process has already exited.');
|
|
289
|
+
}
|
|
290
|
+
}, 3000);
|
|
226
291
|
}
|
|
227
292
|
|
|
228
293
|
// Set up signal handlers for graceful shutdown
|
|
229
294
|
function setupShutdownHandlers() {
|
|
230
295
|
const shutdown = (signal) => {
|
|
296
|
+
if (isShuttingDown) {
|
|
297
|
+
console.log(`Received ${signal}, but shutdown already in progress...`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
231
301
|
console.log(`\n\nReceived ${signal}. Shutting down...`);
|
|
232
302
|
shutdownLemonadeServer();
|
|
233
303
|
setTimeout(() => process.exit(0), 2000);
|