@appkit/llamacpp-cli 1.11.0 → 1.12.1

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.
Files changed (126) hide show
  1. package/README.md +572 -170
  2. package/dist/cli.js +99 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/admin/config.d.ts +10 -0
  5. package/dist/commands/admin/config.d.ts.map +1 -0
  6. package/dist/commands/admin/config.js +100 -0
  7. package/dist/commands/admin/config.js.map +1 -0
  8. package/dist/commands/admin/logs.d.ts +10 -0
  9. package/dist/commands/admin/logs.d.ts.map +1 -0
  10. package/dist/commands/admin/logs.js +114 -0
  11. package/dist/commands/admin/logs.js.map +1 -0
  12. package/dist/commands/admin/restart.d.ts +2 -0
  13. package/dist/commands/admin/restart.d.ts.map +1 -0
  14. package/dist/commands/admin/restart.js +29 -0
  15. package/dist/commands/admin/restart.js.map +1 -0
  16. package/dist/commands/admin/start.d.ts +2 -0
  17. package/dist/commands/admin/start.d.ts.map +1 -0
  18. package/dist/commands/admin/start.js +30 -0
  19. package/dist/commands/admin/start.js.map +1 -0
  20. package/dist/commands/admin/status.d.ts +2 -0
  21. package/dist/commands/admin/status.d.ts.map +1 -0
  22. package/dist/commands/admin/status.js +82 -0
  23. package/dist/commands/admin/status.js.map +1 -0
  24. package/dist/commands/admin/stop.d.ts +2 -0
  25. package/dist/commands/admin/stop.d.ts.map +1 -0
  26. package/dist/commands/admin/stop.js +21 -0
  27. package/dist/commands/admin/stop.js.map +1 -0
  28. package/dist/commands/logs.d.ts +1 -0
  29. package/dist/commands/logs.d.ts.map +1 -1
  30. package/dist/commands/logs.js +22 -0
  31. package/dist/commands/logs.js.map +1 -1
  32. package/dist/lib/admin-manager.d.ts +111 -0
  33. package/dist/lib/admin-manager.d.ts.map +1 -0
  34. package/dist/lib/admin-manager.js +413 -0
  35. package/dist/lib/admin-manager.js.map +1 -0
  36. package/dist/lib/admin-server.d.ts +148 -0
  37. package/dist/lib/admin-server.d.ts.map +1 -0
  38. package/dist/lib/admin-server.js +1161 -0
  39. package/dist/lib/admin-server.js.map +1 -0
  40. package/dist/lib/download-job-manager.d.ts +64 -0
  41. package/dist/lib/download-job-manager.d.ts.map +1 -0
  42. package/dist/lib/download-job-manager.js +164 -0
  43. package/dist/lib/download-job-manager.js.map +1 -0
  44. package/dist/tui/MultiServerMonitorApp.js +1 -1
  45. package/dist/types/admin-config.d.ts +19 -0
  46. package/dist/types/admin-config.d.ts.map +1 -0
  47. package/dist/types/admin-config.js +3 -0
  48. package/dist/types/admin-config.js.map +1 -0
  49. package/dist/utils/log-parser.d.ts +9 -0
  50. package/dist/utils/log-parser.d.ts.map +1 -1
  51. package/dist/utils/log-parser.js +11 -0
  52. package/dist/utils/log-parser.js.map +1 -1
  53. package/package.json +10 -2
  54. package/web/README.md +429 -0
  55. package/web/dist/assets/index-Bin89Lwr.css +1 -0
  56. package/web/dist/assets/index-CVmonw3T.js +17 -0
  57. package/web/dist/index.html +14 -0
  58. package/web/dist/vite.svg +1 -0
  59. package/.versionrc.json +0 -16
  60. package/CHANGELOG.md +0 -203
  61. package/MONITORING-ACCURACY-FIX.md +0 -199
  62. package/PER-PROCESS-METRICS.md +0 -190
  63. package/docs/images/.gitkeep +0 -1
  64. package/src/cli.ts +0 -423
  65. package/src/commands/config-global.ts +0 -38
  66. package/src/commands/config.ts +0 -323
  67. package/src/commands/create.ts +0 -183
  68. package/src/commands/delete.ts +0 -74
  69. package/src/commands/list.ts +0 -37
  70. package/src/commands/logs-all.ts +0 -251
  71. package/src/commands/logs.ts +0 -321
  72. package/src/commands/monitor.ts +0 -110
  73. package/src/commands/ps.ts +0 -84
  74. package/src/commands/pull.ts +0 -44
  75. package/src/commands/rm.ts +0 -107
  76. package/src/commands/router/config.ts +0 -116
  77. package/src/commands/router/logs.ts +0 -256
  78. package/src/commands/router/restart.ts +0 -36
  79. package/src/commands/router/start.ts +0 -60
  80. package/src/commands/router/status.ts +0 -119
  81. package/src/commands/router/stop.ts +0 -33
  82. package/src/commands/run.ts +0 -233
  83. package/src/commands/search.ts +0 -107
  84. package/src/commands/server-show.ts +0 -161
  85. package/src/commands/show.ts +0 -207
  86. package/src/commands/start.ts +0 -101
  87. package/src/commands/stop.ts +0 -39
  88. package/src/commands/tui.ts +0 -25
  89. package/src/lib/config-generator.ts +0 -130
  90. package/src/lib/history-manager.ts +0 -172
  91. package/src/lib/launchctl-manager.ts +0 -225
  92. package/src/lib/metrics-aggregator.ts +0 -257
  93. package/src/lib/model-downloader.ts +0 -328
  94. package/src/lib/model-scanner.ts +0 -157
  95. package/src/lib/model-search.ts +0 -114
  96. package/src/lib/models-dir-setup.ts +0 -46
  97. package/src/lib/port-manager.ts +0 -80
  98. package/src/lib/router-logger.ts +0 -201
  99. package/src/lib/router-manager.ts +0 -414
  100. package/src/lib/router-server.ts +0 -538
  101. package/src/lib/state-manager.ts +0 -206
  102. package/src/lib/status-checker.ts +0 -113
  103. package/src/lib/system-collector.ts +0 -315
  104. package/src/tui/ConfigApp.ts +0 -1085
  105. package/src/tui/HistoricalMonitorApp.ts +0 -587
  106. package/src/tui/ModelsApp.ts +0 -368
  107. package/src/tui/MonitorApp.ts +0 -386
  108. package/src/tui/MultiServerMonitorApp.ts +0 -1833
  109. package/src/tui/RootNavigator.ts +0 -74
  110. package/src/tui/SearchApp.ts +0 -511
  111. package/src/tui/SplashScreen.ts +0 -149
  112. package/src/types/global-config.ts +0 -26
  113. package/src/types/history-types.ts +0 -39
  114. package/src/types/model-info.ts +0 -8
  115. package/src/types/monitor-types.ts +0 -162
  116. package/src/types/router-config.ts +0 -25
  117. package/src/types/server-config.ts +0 -46
  118. package/src/utils/downsample-utils.ts +0 -128
  119. package/src/utils/file-utils.ts +0 -146
  120. package/src/utils/format-utils.ts +0 -98
  121. package/src/utils/log-parser.ts +0 -271
  122. package/src/utils/log-utils.ts +0 -178
  123. package/src/utils/process-utils.ts +0 -316
  124. package/src/utils/prompt-utils.ts +0 -47
  125. package/test-load.sh +0 -100
  126. package/tsconfig.json +0 -20
@@ -1,323 +0,0 @@
1
- import chalk from 'chalk';
2
- import * as path from 'path';
3
- import * as fs from 'fs/promises';
4
- import { stateManager } from '../lib/state-manager';
5
- import { statusChecker } from '../lib/status-checker';
6
- import { launchctlManager } from '../lib/launchctl-manager';
7
- import { configGenerator } from '../lib/config-generator';
8
- import { autoRotateIfNeeded } from '../utils/log-utils';
9
- import { modelScanner } from '../lib/model-scanner';
10
- import { sanitizeModelName } from '../types/server-config';
11
- import { getLogsDir, getLaunchAgentsDir } from '../utils/file-utils';
12
-
13
- export interface ConfigUpdateOptions {
14
- model?: string;
15
- host?: string;
16
- threads?: number;
17
- ctxSize?: number;
18
- gpuLayers?: number;
19
- verbose?: boolean;
20
- flags?: string;
21
- restart?: boolean;
22
- }
23
-
24
- export async function serverConfigCommand(
25
- identifier: string,
26
- options: ConfigUpdateOptions
27
- ): Promise<void> {
28
- // Find the server
29
- const server = await stateManager.findServer(identifier);
30
-
31
- if (!server) {
32
- console.error(chalk.red(`❌ Server not found: ${identifier}`));
33
- console.log(chalk.dim('\nAvailable servers:'));
34
- const allServers = await stateManager.getAllServers();
35
- if (allServers.length === 0) {
36
- console.log(chalk.dim(' (none)'));
37
- console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
38
- } else {
39
- allServers.forEach(s => {
40
- console.log(chalk.dim(` - ${s.id} (port ${s.port})`));
41
- });
42
- }
43
- process.exit(1);
44
- }
45
-
46
- // Check if any config options were provided
47
- const hasChanges = options.model !== undefined ||
48
- options.host !== undefined ||
49
- options.threads !== undefined ||
50
- options.ctxSize !== undefined ||
51
- options.gpuLayers !== undefined ||
52
- options.verbose !== undefined ||
53
- options.flags !== undefined;
54
-
55
- if (!hasChanges) {
56
- console.error(chalk.red('❌ No configuration changes specified'));
57
- console.log(chalk.dim('\nAvailable options:'));
58
- console.log(chalk.dim(' --model <filename> Model filename or path'));
59
- console.log(chalk.dim(' --host <address> Bind address (127.0.0.1 or 0.0.0.0)'));
60
- console.log(chalk.dim(' --threads <n> Number of threads'));
61
- console.log(chalk.dim(' --ctx-size <n> Context size'));
62
- console.log(chalk.dim(' --gpu-layers <n> GPU layers'));
63
- console.log(chalk.dim(' --verbose Enable verbose logging'));
64
- console.log(chalk.dim(' --no-verbose Disable verbose logging'));
65
- console.log(chalk.dim(' --flags <flags> Custom llama-server flags (comma-separated)'));
66
- console.log(chalk.dim(' --restart Auto-restart if running'));
67
- console.log(chalk.dim('\nExamples:'));
68
- console.log(chalk.dim(` llamacpp server config ${server.id} --model llama-3.2-1b.gguf --restart`));
69
- console.log(chalk.dim(` llamacpp server config ${server.id} --ctx-size 8192 --restart`));
70
- console.log(chalk.dim(` llamacpp server config ${server.id} --flags="--pooling,mean" --restart`));
71
- process.exit(1);
72
- }
73
-
74
- // Resolve model path if model option is provided
75
- let newModelPath: string | undefined;
76
- let newModelName: string | undefined;
77
- let newServerId: string | undefined;
78
- let isModelMigration = false;
79
-
80
- if (options.model !== undefined) {
81
- const resolvedPath = await modelScanner.resolveModelPath(options.model);
82
- if (!resolvedPath) {
83
- console.error(chalk.red(`❌ Model not found: ${options.model}`));
84
- console.log(chalk.dim('\nRun: llamacpp ls'));
85
- process.exit(1);
86
- }
87
- newModelPath = resolvedPath;
88
- newModelName = path.basename(resolvedPath);
89
- newServerId = sanitizeModelName(newModelName);
90
-
91
- // Check if this is a model migration (ID will change)
92
- if (newServerId !== server.id) {
93
- isModelMigration = true;
94
-
95
- // Check for ID conflict
96
- const existingServer = await stateManager.loadServerConfig(newServerId);
97
- if (existingServer) {
98
- console.error(chalk.red(`❌ A server with ID "${newServerId}" already exists`));
99
- console.log(chalk.dim('\nChanging the model would create this server ID, but it conflicts with an existing server.'));
100
- console.log(chalk.dim('Delete the existing server first:'));
101
- console.log(chalk.dim(` llamacpp server rm ${newServerId}`));
102
- process.exit(1);
103
- }
104
-
105
- console.log(chalk.yellow('⚠️ Changing the model will migrate to a new server ID'));
106
- console.log(chalk.dim(` Old ID: ${server.id}`));
107
- console.log(chalk.dim(` New ID: ${newServerId}\n`));
108
- }
109
- }
110
-
111
- // Check current status
112
- const updatedServer = await statusChecker.updateServerStatus(server);
113
- const wasRunning = updatedServer.status === 'running';
114
-
115
- if (wasRunning && !options.restart) {
116
- console.warn(chalk.yellow('⚠️ Server is currently running'));
117
- console.log(chalk.dim('Changes will require a restart to take effect.'));
118
- console.log(chalk.dim('Use --restart flag to automatically restart the server.\n'));
119
- }
120
-
121
- // Show what will change
122
- console.log(chalk.bold('Configuration Changes:'));
123
- console.log('─'.repeat(70));
124
-
125
- if (newModelPath !== undefined && newModelName !== undefined) {
126
- const oldModelName = path.basename(server.modelPath);
127
- console.log(`${chalk.bold('Model:')} ${chalk.dim(oldModelName)} → ${chalk.green(newModelName)}`);
128
- console.log(`${chalk.dim(' ')}${chalk.dim(server.modelPath)}`);
129
- console.log(`${chalk.dim(' ')}${chalk.dim(newModelPath)}`);
130
- if (isModelMigration && newServerId) {
131
- console.log(`${chalk.bold('Server ID:')} ${chalk.dim(server.id)} → ${chalk.green(newServerId)}`);
132
- }
133
- }
134
- if (options.host !== undefined) {
135
- console.log(`${chalk.bold('Host:')} ${chalk.dim(server.host)} → ${chalk.green(options.host)}`);
136
-
137
- // Security warning for 0.0.0.0
138
- if (options.host === '0.0.0.0') {
139
- console.log(chalk.yellow('\n⚠️ WARNING: Binding to 0.0.0.0 allows remote access from any network interface.'));
140
- console.log(chalk.yellow(' This exposes your server to your local network and potentially the internet.'));
141
- console.log(chalk.yellow(' Use 127.0.0.1 for localhost-only access (recommended for local development).\n'));
142
- }
143
- }
144
- if (options.threads !== undefined) {
145
- console.log(`${chalk.bold('Threads:')} ${chalk.dim(server.threads.toString())} → ${chalk.green(options.threads.toString())}`);
146
- }
147
- if (options.ctxSize !== undefined) {
148
- console.log(`${chalk.bold('Context Size:')} ${chalk.dim(server.ctxSize.toLocaleString())} → ${chalk.green(options.ctxSize.toLocaleString())}`);
149
- }
150
- if (options.gpuLayers !== undefined) {
151
- console.log(`${chalk.bold('GPU Layers:')} ${chalk.dim(server.gpuLayers.toString())} → ${chalk.green(options.gpuLayers.toString())}`);
152
- }
153
- if (options.verbose !== undefined) {
154
- const oldValue = server.verbose ? 'enabled' : 'disabled';
155
- const newValue = options.verbose ? 'enabled' : 'disabled';
156
- console.log(`${chalk.bold('Verbose Logs:')} ${chalk.dim(oldValue)} → ${chalk.green(newValue)}`);
157
- }
158
- if (options.flags !== undefined) {
159
- const oldValue = server.customFlags?.join(' ') || 'none';
160
- const newValue = options.flags || 'none';
161
- console.log(`${chalk.bold('Custom Flags:')} ${chalk.dim(oldValue)} → ${chalk.green(newValue)}`);
162
- }
163
- console.log('');
164
-
165
- // Parse custom flags if provided
166
- let customFlags: string[] | undefined;
167
- if (options.flags !== undefined) {
168
- if (options.flags === '') {
169
- customFlags = undefined;
170
- } else {
171
- customFlags = options.flags.split(',').map(f => f.trim()).filter(f => f.length > 0);
172
- }
173
- }
174
-
175
- // Handle model migration (different code path when server ID changes)
176
- if (isModelMigration && newServerId && newModelPath && newModelName) {
177
- // Stop old server if running
178
- if (wasRunning) {
179
- console.log(chalk.dim('Stopping old server...'));
180
- await launchctlManager.unloadService(server.plistPath);
181
- await new Promise(resolve => setTimeout(resolve, 1000));
182
- }
183
-
184
- // Delete old plist and config
185
- console.log(chalk.dim('Removing old server configuration...'));
186
- try {
187
- await fs.unlink(server.plistPath);
188
- } catch (err) {
189
- // Plist might not exist, that's ok
190
- }
191
- await stateManager.deleteServerConfig(server.id);
192
-
193
- // Create new config with new ID and all settings
194
- const logsDir = getLogsDir();
195
- const plistDir = getLaunchAgentsDir();
196
-
197
- const newConfig = {
198
- ...server,
199
- id: newServerId,
200
- modelPath: newModelPath,
201
- modelName: newModelName,
202
- ...(options.host !== undefined && { host: options.host }),
203
- ...(options.threads !== undefined && { threads: options.threads }),
204
- ...(options.ctxSize !== undefined && { ctxSize: options.ctxSize }),
205
- ...(options.gpuLayers !== undefined && { gpuLayers: options.gpuLayers }),
206
- ...(options.verbose !== undefined && { verbose: options.verbose }),
207
- ...(options.flags !== undefined && { customFlags }),
208
- // Update plist-related paths
209
- label: `com.llama.${newServerId}`,
210
- plistPath: path.join(plistDir, `com.llama.${newServerId}.plist`),
211
- stdoutPath: path.join(logsDir, `${newServerId}.stdout`),
212
- stderrPath: path.join(logsDir, `${newServerId}.stderr`),
213
- status: 'stopped' as const,
214
- pid: undefined,
215
- lastStopped: new Date().toISOString(),
216
- };
217
-
218
- console.log(chalk.dim('Creating new server configuration...'));
219
- await stateManager.saveServerConfig(newConfig);
220
- await launchctlManager.createPlist(newConfig);
221
-
222
- // Start new server if restart flag is set
223
- if (wasRunning && options.restart) {
224
- console.log(chalk.dim('Starting new server...'));
225
- await launchctlManager.loadService(newConfig.plistPath);
226
- await launchctlManager.startService(newConfig.label);
227
-
228
- // Wait and verify
229
- await new Promise(resolve => setTimeout(resolve, 2000));
230
- const finalStatus = await statusChecker.updateServerStatus(newConfig);
231
-
232
- if (finalStatus.status === 'running') {
233
- console.log(chalk.green(`✅ Server migrated successfully to new ID: ${newServerId}`));
234
- console.log(chalk.dim(` Port: http://localhost:${finalStatus.port}`));
235
- if (finalStatus.pid) {
236
- console.log(chalk.dim(` PID: ${finalStatus.pid}`));
237
- }
238
- } else {
239
- console.error(chalk.red('❌ Server failed to start with new configuration'));
240
- console.log(chalk.dim(' Check logs: ') + `llamacpp server logs ${newServerId} --errors`);
241
- process.exit(1);
242
- }
243
- } else {
244
- console.log(chalk.green(`✅ Server migrated successfully to new ID: ${newServerId}`));
245
- if (!wasRunning) {
246
- console.log(chalk.dim('\n Start server: ') + `llamacpp server start ${newServerId}`);
247
- } else {
248
- console.log(chalk.dim('\n Start server: ') + `llamacpp server start ${newServerId}`);
249
- }
250
- }
251
-
252
- return; // Exit early for migration path
253
- }
254
-
255
- // Normal config update (no model migration)
256
- // Unload service if running and restart flag is set
257
- if (wasRunning && options.restart) {
258
- console.log(chalk.dim('Stopping server...'));
259
- await launchctlManager.unloadService(server.plistPath);
260
- await new Promise(resolve => setTimeout(resolve, 1000));
261
- }
262
-
263
- const updatedConfig = {
264
- ...server,
265
- ...(newModelPath !== undefined && { modelPath: newModelPath, modelName: newModelName }),
266
- ...(options.host !== undefined && { host: options.host }),
267
- ...(options.threads !== undefined && { threads: options.threads }),
268
- ...(options.ctxSize !== undefined && { ctxSize: options.ctxSize }),
269
- ...(options.gpuLayers !== undefined && { gpuLayers: options.gpuLayers }),
270
- ...(options.verbose !== undefined && { verbose: options.verbose }),
271
- ...(options.flags !== undefined && { customFlags }),
272
- };
273
-
274
- await stateManager.updateServerConfig(server.id, updatedConfig);
275
-
276
- console.log(chalk.dim('Regenerating service configuration...'));
277
- await launchctlManager.createPlist(updatedConfig);
278
-
279
- // Restart server if it was running and restart flag is set
280
- if (wasRunning && options.restart) {
281
- // Auto-rotate logs if they exceed 100MB
282
- try {
283
- const result = await autoRotateIfNeeded(updatedConfig.stdoutPath, updatedConfig.stderrPath, 100);
284
- if (result.rotated) {
285
- console.log(chalk.dim('Auto-rotated large log files:'));
286
- for (const file of result.files) {
287
- console.log(chalk.dim(` → ${file}`));
288
- }
289
- }
290
- } catch (error) {
291
- // Non-fatal, just warn
292
- console.log(chalk.yellow(`⚠️ Failed to rotate logs: ${(error as Error).message}`));
293
- }
294
-
295
- console.log(chalk.dim('Starting server with new configuration...'));
296
- await launchctlManager.loadService(updatedConfig.plistPath);
297
- await launchctlManager.startService(updatedConfig.label);
298
-
299
- // Wait and verify
300
- await new Promise(resolve => setTimeout(resolve, 2000));
301
- const finalStatus = await statusChecker.updateServerStatus(updatedConfig);
302
-
303
- if (finalStatus.status === 'running') {
304
- console.log(chalk.green(`✅ Server restarted successfully with new configuration`));
305
- console.log(chalk.dim(` Port: http://localhost:${finalStatus.port}`));
306
- if (finalStatus.pid) {
307
- console.log(chalk.dim(` PID: ${finalStatus.pid}`));
308
- }
309
- } else {
310
- console.error(chalk.red('❌ Server failed to start with new configuration'));
311
- console.log(chalk.dim(' Check logs: ') + `llamacpp server logs ${server.id} --errors`);
312
- process.exit(1);
313
- }
314
- } else {
315
- console.log(chalk.green('✅ Configuration updated successfully'));
316
- if (wasRunning && !options.restart) {
317
- console.log(chalk.yellow('\n⚠️ Server is still running with old configuration'));
318
- console.log(chalk.dim(' Restart to apply changes: ') + `llamacpp server stop ${server.id} && llamacpp server start ${server.id}`);
319
- } else if (!wasRunning) {
320
- console.log(chalk.dim('\n Start server: ') + `llamacpp server start ${server.id}`);
321
- }
322
- }
323
- }
@@ -1,183 +0,0 @@
1
- import chalk from 'chalk';
2
- import * as path from 'path';
3
- import * as fs from 'fs';
4
- import { modelScanner } from '../lib/model-scanner';
5
- import { stateManager } from '../lib/state-manager';
6
- import { configGenerator, ServerOptions } from '../lib/config-generator';
7
- import { portManager } from '../lib/port-manager';
8
- import { launchctlManager } from '../lib/launchctl-manager';
9
- import { statusChecker } from '../lib/status-checker';
10
- import { commandExists } from '../utils/process-utils';
11
- import { formatBytes } from '../utils/format-utils';
12
- import { ensureDir, parseMetalMemoryFromLog } from '../utils/file-utils';
13
- import { ensureModelsDirectory } from '../lib/models-dir-setup';
14
-
15
- interface CreateOptions {
16
- port?: number;
17
- host?: string;
18
- threads?: number;
19
- ctxSize?: number;
20
- gpuLayers?: number;
21
- verbose?: boolean;
22
- flags?: string;
23
- }
24
-
25
- export async function createCommand(model: string, options: CreateOptions): Promise<void> {
26
- // Initialize state manager
27
- await stateManager.initialize();
28
-
29
- // 1. Check if llama-server exists
30
- if (!(await commandExists('llama-server'))) {
31
- throw new Error('llama-server not found. Install with: brew install llama.cpp');
32
- }
33
-
34
- // 2. Ensure models directory exists if model is not an absolute path
35
- if (!path.isAbsolute(model)) {
36
- const modelsDir = await stateManager.getModelsDirectory();
37
- if (!fs.existsSync(modelsDir)) {
38
- await ensureModelsDirectory();
39
- }
40
- }
41
-
42
- // 3. Resolve model path
43
- const modelPath = await modelScanner.resolveModelPath(model);
44
- if (!modelPath) {
45
- throw new Error(`Model not found: ${model}\n\nRun: llamacpp ls`);
46
- }
47
-
48
- const modelName = path.basename(modelPath);
49
-
50
- // 4. Check if server already exists for this model
51
- const existingServer = await stateManager.serverExistsForModel(modelPath);
52
- if (existingServer) {
53
- throw new Error(`Server already exists for ${modelName}\n\nUse: llamacpp server start ${modelName}`);
54
- }
55
-
56
- // 5. Get model size
57
- const modelSize = await modelScanner.getModelSize(modelName);
58
- if (!modelSize) {
59
- throw new Error(`Failed to read model file: ${modelPath}`);
60
- }
61
-
62
- // 6. Determine port
63
- let port: number;
64
- if (options.port) {
65
- portManager.validatePort(options.port);
66
- const available = await portManager.isPortAvailable(options.port);
67
- if (!available) {
68
- throw new Error(`Port ${options.port} is already in use`);
69
- }
70
- port = options.port;
71
- } else {
72
- port = await portManager.findAvailablePort();
73
- }
74
-
75
- // 7. Generate server configuration
76
- console.log(chalk.blue(`🚀 Creating server for ${modelName}\n`));
77
-
78
- // Parse custom flags if provided
79
- let customFlags: string[] | undefined;
80
- if (options.flags) {
81
- customFlags = options.flags.split(',').map(f => f.trim()).filter(f => f.length > 0);
82
- }
83
-
84
- const serverOptions: ServerOptions = {
85
- port: options.port,
86
- host: options.host,
87
- threads: options.threads,
88
- ctxSize: options.ctxSize,
89
- gpuLayers: options.gpuLayers,
90
- verbose: options.verbose,
91
- customFlags,
92
- };
93
-
94
- const config = await configGenerator.generateConfig(
95
- modelPath,
96
- modelName,
97
- modelSize,
98
- port,
99
- serverOptions
100
- );
101
-
102
- // Security warning for 0.0.0.0
103
- if (config.host === '0.0.0.0') {
104
- console.log(chalk.yellow('⚠️ WARNING: Binding to 0.0.0.0 allows remote access from any network interface.'));
105
- console.log(chalk.yellow(' This exposes your server to your local network and potentially the internet.'));
106
- console.log(chalk.yellow(' Use 127.0.0.1 for localhost-only access (recommended for local development).\n'));
107
- }
108
-
109
- // Display configuration
110
- console.log(chalk.dim(`Model: ${modelPath}`));
111
- console.log(chalk.dim(`Size: ${formatBytes(modelSize)}`));
112
- console.log(chalk.dim(`Host: ${config.host}`));
113
- console.log(chalk.dim(`Port: ${config.port}${options.port ? '' : ' (auto-assigned)'}`));
114
- console.log(chalk.dim(`Threads: ${config.threads}`));
115
- console.log(chalk.dim(`Context Size: ${config.ctxSize}`));
116
- console.log(chalk.dim(`GPU Layers: ${config.gpuLayers}`));
117
- console.log(chalk.dim(`Verbose Logging: ${config.verbose ? 'enabled' : 'disabled'}`));
118
- if (config.customFlags && config.customFlags.length > 0) {
119
- console.log(chalk.dim(`Custom Flags: ${config.customFlags.join(' ')}`));
120
- }
121
- console.log();
122
-
123
- // 7. Ensure log directory exists
124
- await ensureDir(path.dirname(config.stdoutPath));
125
-
126
- // 8. Create plist file
127
- console.log(chalk.dim('Creating launchctl service...'));
128
- await launchctlManager.createPlist(config);
129
-
130
- // 9. Load service
131
- try {
132
- await launchctlManager.loadService(config.plistPath);
133
- } catch (error) {
134
- // Clean up plist if load fails
135
- await launchctlManager.deletePlist(config.plistPath);
136
- throw new Error(`Failed to load service: ${(error as Error).message}`);
137
- }
138
-
139
- // 10. Start service
140
- try {
141
- await launchctlManager.startService(config.label);
142
- } catch (error) {
143
- // Clean up if start fails
144
- await launchctlManager.unloadService(config.plistPath);
145
- await launchctlManager.deletePlist(config.plistPath);
146
- throw new Error(`Failed to start service: ${(error as Error).message}`);
147
- }
148
-
149
- // 11. Wait for startup
150
- console.log(chalk.dim('Waiting for server to start...'));
151
- const started = await launchctlManager.waitForServiceStart(config.label, 5000);
152
-
153
- if (!started) {
154
- // Clean up if startup fails
155
- await launchctlManager.unloadService(config.plistPath);
156
- await launchctlManager.deletePlist(config.plistPath);
157
- throw new Error('Server failed to start. Check logs with: llamacpp server logs --errors');
158
- }
159
-
160
- // 12. Update config with running status
161
- let updatedConfig = await statusChecker.updateServerStatus(config);
162
-
163
- // 13. Parse Metal (GPU) memory allocation from logs
164
- // Wait a few seconds for model to start loading (large models take time)
165
- console.log(chalk.dim('Detecting Metal (GPU) memory allocation...'));
166
- await new Promise(resolve => setTimeout(resolve, 8000)); // 8 second delay
167
- const metalMemoryMB = await parseMetalMemoryFromLog(updatedConfig.stderrPath);
168
- if (metalMemoryMB) {
169
- updatedConfig = { ...updatedConfig, metalMemoryMB };
170
- console.log(chalk.dim(`Metal memory: ${metalMemoryMB.toFixed(0)} MB`));
171
- }
172
-
173
- // 14. Save server config
174
- await stateManager.saveServerConfig(updatedConfig);
175
-
176
- // 15. Display success message
177
- console.log();
178
- console.log(chalk.green('✅ Server created and started successfully!'));
179
- console.log();
180
- console.log(chalk.dim(`Connect: http://${config.host}:${config.port}`));
181
- console.log(chalk.dim(`View logs: llamacpp server logs ${config.id}`));
182
- console.log(chalk.dim(`Stop: llamacpp server stop ${config.id}`));
183
- }
@@ -1,74 +0,0 @@
1
- import chalk from 'chalk';
2
- import * as readline from 'readline';
3
- import { stateManager } from '../lib/state-manager';
4
- import { launchctlManager } from '../lib/launchctl-manager';
5
-
6
- export async function deleteCommand(identifier: string): Promise<void> {
7
- // Find server
8
- const server = await stateManager.findServer(identifier);
9
- if (!server) {
10
- throw new Error(`Server not found: ${identifier}\n\nUse: llamacpp ps`);
11
- }
12
-
13
- // Confirm deletion
14
- console.log(chalk.yellow(`⚠️ Delete server configuration for ${server.modelName}?`));
15
- console.log(chalk.dim(' This will remove the launchd service but keep the model file.'));
16
- console.log();
17
-
18
- const confirmed = await confirmDeletion();
19
- if (!confirmed) {
20
- console.log(chalk.dim('Cancelled'));
21
- return;
22
- }
23
-
24
- console.log();
25
- console.log(chalk.blue(`🗑️ Deleting server ${server.modelName}...`));
26
-
27
- // Unload service (stops and removes from launchd)
28
- if (server.status === 'running') {
29
- console.log(chalk.dim('Stopping and unloading service...'));
30
- } else {
31
- console.log(chalk.dim('Unloading service...'));
32
- }
33
- try {
34
- await launchctlManager.unloadService(server.plistPath);
35
- if (server.status === 'running') {
36
- await launchctlManager.waitForServiceStop(server.label, 5000);
37
- }
38
- } catch (error) {
39
- console.log(chalk.yellow('⚠️ Failed to unload service gracefully'));
40
- }
41
-
42
- // Delete plist
43
- console.log(chalk.dim('Deleting plist file...'));
44
- await launchctlManager.deletePlist(server.plistPath);
45
-
46
- // Delete server config
47
- console.log(chalk.dim('Deleting server configuration...'));
48
- await stateManager.deleteServerConfig(server.id);
49
-
50
- // Success
51
- console.log();
52
- console.log(chalk.green('✅ Server deleted'));
53
- console.log(chalk.dim(` Plist removed: ${server.plistPath}`));
54
- console.log(chalk.dim(` Config removed`));
55
- console.log();
56
- console.log(chalk.dim(` Model file preserved at: ${server.modelPath}`));
57
- }
58
-
59
- /**
60
- * Prompt user for confirmation
61
- */
62
- function confirmDeletion(): Promise<boolean> {
63
- return new Promise((resolve) => {
64
- const rl = readline.createInterface({
65
- input: process.stdin,
66
- output: process.stdout,
67
- });
68
-
69
- rl.question(chalk.yellow(" Type 'yes' to confirm: "), (answer) => {
70
- rl.close();
71
- resolve(answer.toLowerCase() === 'yes');
72
- });
73
- });
74
- }
@@ -1,37 +0,0 @@
1
- import chalk from 'chalk';
2
- import Table from 'cli-table3';
3
- import { modelScanner } from '../lib/model-scanner';
4
- import { formatBytes, formatDateShort } from '../utils/format-utils';
5
- import { stateManager } from '../lib/state-manager';
6
-
7
- export async function listCommand(): Promise<void> {
8
- const modelsDir = await stateManager.getModelsDirectory();
9
- console.log(chalk.blue(`📦 Available models in ${modelsDir}\n`));
10
-
11
- const models = await modelScanner.scanModels();
12
-
13
- if (models.length === 0) {
14
- console.log(chalk.yellow('No GGUF models found.'));
15
- console.log(chalk.dim(`\nDownload models with: llamacpp pull <repo> --file <filename>`));
16
- return;
17
- }
18
-
19
- const table = new Table({
20
- head: ['MODEL', 'SIZE', 'MODIFIED'],
21
- colWidths: [50, 12, 15],
22
- });
23
-
24
- for (const model of models) {
25
- table.push([
26
- model.filename,
27
- model.sizeFormatted,
28
- formatDateShort(model.modified),
29
- ]);
30
- }
31
-
32
- console.log(table.toString());
33
-
34
- const totalSize = models.reduce((sum, m) => sum + m.size, 0);
35
- console.log(chalk.dim(`\nTotal: ${models.length} models (${formatBytes(totalSize)})`));
36
- console.log(chalk.dim(`\nCreate a server: llamacpp server create <model-filename>`));
37
- }