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 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 8 simple questions:
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. **Model directory?** Point to existing models (like LM Studio) if needed
92
- 5. **Interface type?** System tray or headless mode
93
- 6. **Custom arguments?** Any additional llama.cpp parameters?
94
- 7. **Custom build?** Use a specific llama.cpp build from GitHub?
95
- 8. **Backend?** Choose auto, vulkan, rocm, or cpu
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemonade-interactive-loader",
3
- "version": "1.0.3",
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**: 8-question setup wizard
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}`);
@@ -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}`);
@@ -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: Custom model directory
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 && existingModelDir !== 'None';
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
- // Q5: System tray vs headless
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
- // Q6: Custom llama.cpp args
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., --ctx-size 4096,--batch-size 512):',
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
- // Q7: Custom llama.cpp build
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,
@@ -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
  };
@@ -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('close', (code) => {
179
- console.log(`\nServer exited with code ${code}`);
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
- 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);
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);