@appkit/llamacpp-cli 1.12.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 (114) hide show
  1. package/README.md +217 -168
  2. package/package.json +10 -2
  3. package/web/dist/assets/index-Bin89Lwr.css +1 -0
  4. package/web/dist/assets/index-CVmonw3T.js +17 -0
  5. package/web/{index.html → dist/index.html} +2 -1
  6. package/.versionrc.json +0 -16
  7. package/CHANGELOG.md +0 -213
  8. package/docs/images/.gitkeep +0 -1
  9. package/docs/images/web-ui-servers.png +0 -0
  10. package/src/cli.ts +0 -523
  11. package/src/commands/admin/config.ts +0 -121
  12. package/src/commands/admin/logs.ts +0 -91
  13. package/src/commands/admin/restart.ts +0 -26
  14. package/src/commands/admin/start.ts +0 -27
  15. package/src/commands/admin/status.ts +0 -84
  16. package/src/commands/admin/stop.ts +0 -16
  17. package/src/commands/config-global.ts +0 -38
  18. package/src/commands/config.ts +0 -323
  19. package/src/commands/create.ts +0 -183
  20. package/src/commands/delete.ts +0 -74
  21. package/src/commands/list.ts +0 -37
  22. package/src/commands/logs-all.ts +0 -251
  23. package/src/commands/logs.ts +0 -345
  24. package/src/commands/monitor.ts +0 -110
  25. package/src/commands/ps.ts +0 -84
  26. package/src/commands/pull.ts +0 -44
  27. package/src/commands/rm.ts +0 -107
  28. package/src/commands/router/config.ts +0 -116
  29. package/src/commands/router/logs.ts +0 -256
  30. package/src/commands/router/restart.ts +0 -36
  31. package/src/commands/router/start.ts +0 -60
  32. package/src/commands/router/status.ts +0 -119
  33. package/src/commands/router/stop.ts +0 -33
  34. package/src/commands/run.ts +0 -233
  35. package/src/commands/search.ts +0 -107
  36. package/src/commands/server-show.ts +0 -161
  37. package/src/commands/show.ts +0 -207
  38. package/src/commands/start.ts +0 -101
  39. package/src/commands/stop.ts +0 -39
  40. package/src/commands/tui.ts +0 -25
  41. package/src/lib/admin-manager.ts +0 -435
  42. package/src/lib/admin-server.ts +0 -1243
  43. package/src/lib/config-generator.ts +0 -130
  44. package/src/lib/download-job-manager.ts +0 -213
  45. package/src/lib/history-manager.ts +0 -172
  46. package/src/lib/launchctl-manager.ts +0 -225
  47. package/src/lib/metrics-aggregator.ts +0 -257
  48. package/src/lib/model-downloader.ts +0 -328
  49. package/src/lib/model-scanner.ts +0 -157
  50. package/src/lib/model-search.ts +0 -114
  51. package/src/lib/models-dir-setup.ts +0 -46
  52. package/src/lib/port-manager.ts +0 -80
  53. package/src/lib/router-logger.ts +0 -201
  54. package/src/lib/router-manager.ts +0 -414
  55. package/src/lib/router-server.ts +0 -538
  56. package/src/lib/state-manager.ts +0 -206
  57. package/src/lib/status-checker.ts +0 -113
  58. package/src/lib/system-collector.ts +0 -315
  59. package/src/tui/ConfigApp.ts +0 -1085
  60. package/src/tui/HistoricalMonitorApp.ts +0 -587
  61. package/src/tui/ModelsApp.ts +0 -368
  62. package/src/tui/MonitorApp.ts +0 -386
  63. package/src/tui/MultiServerMonitorApp.ts +0 -1833
  64. package/src/tui/RootNavigator.ts +0 -74
  65. package/src/tui/SearchApp.ts +0 -511
  66. package/src/tui/SplashScreen.ts +0 -149
  67. package/src/types/admin-config.ts +0 -25
  68. package/src/types/global-config.ts +0 -26
  69. package/src/types/history-types.ts +0 -39
  70. package/src/types/model-info.ts +0 -8
  71. package/src/types/monitor-types.ts +0 -162
  72. package/src/types/router-config.ts +0 -25
  73. package/src/types/server-config.ts +0 -46
  74. package/src/utils/downsample-utils.ts +0 -128
  75. package/src/utils/file-utils.ts +0 -146
  76. package/src/utils/format-utils.ts +0 -98
  77. package/src/utils/log-parser.ts +0 -284
  78. package/src/utils/log-utils.ts +0 -178
  79. package/src/utils/process-utils.ts +0 -316
  80. package/src/utils/prompt-utils.ts +0 -47
  81. package/test-load.sh +0 -100
  82. package/tsconfig.json +0 -20
  83. package/web/eslint.config.js +0 -23
  84. package/web/llamacpp-web-dist.tar.gz +0 -0
  85. package/web/package-lock.json +0 -4017
  86. package/web/package.json +0 -38
  87. package/web/postcss.config.js +0 -6
  88. package/web/src/App.css +0 -42
  89. package/web/src/App.tsx +0 -86
  90. package/web/src/assets/react.svg +0 -1
  91. package/web/src/components/ApiKeyPrompt.tsx +0 -71
  92. package/web/src/components/CreateServerModal.tsx +0 -372
  93. package/web/src/components/DownloadProgress.tsx +0 -123
  94. package/web/src/components/Nav.tsx +0 -89
  95. package/web/src/components/RouterConfigModal.tsx +0 -240
  96. package/web/src/components/SearchModal.tsx +0 -306
  97. package/web/src/components/ServerConfigModal.tsx +0 -291
  98. package/web/src/hooks/useApi.ts +0 -259
  99. package/web/src/index.css +0 -42
  100. package/web/src/lib/api.ts +0 -226
  101. package/web/src/main.tsx +0 -10
  102. package/web/src/pages/Dashboard.tsx +0 -103
  103. package/web/src/pages/Models.tsx +0 -258
  104. package/web/src/pages/Router.tsx +0 -270
  105. package/web/src/pages/RouterLogs.tsx +0 -201
  106. package/web/src/pages/ServerLogs.tsx +0 -553
  107. package/web/src/pages/Servers.tsx +0 -358
  108. package/web/src/types/api.ts +0 -140
  109. package/web/tailwind.config.js +0 -31
  110. package/web/tsconfig.app.json +0 -28
  111. package/web/tsconfig.json +0 -7
  112. package/web/tsconfig.node.json +0 -26
  113. package/web/vite.config.ts +0 -25
  114. /package/web/{public → dist}/vite.svg +0 -0
@@ -1,345 +0,0 @@
1
- import chalk from 'chalk';
2
- import { spawn } from 'child_process';
3
- import * as readline from 'readline';
4
- import * as fs from 'fs';
5
- import { stateManager } from '../lib/state-manager';
6
- import { fileExists } from '../utils/file-utils';
7
- import { execCommand } from '../utils/process-utils';
8
- import { logParser } from '../utils/log-parser';
9
- import {
10
- getFileSize,
11
- formatFileSize,
12
- rotateLogFile,
13
- clearLogFile,
14
- getArchivedLogInfo,
15
- deleteArchivedLogs,
16
- } from '../utils/log-utils';
17
-
18
- interface LogsOptions {
19
- follow?: boolean;
20
- lines?: number;
21
- errors?: boolean;
22
- verbose?: boolean;
23
- http?: boolean;
24
- stdout?: boolean;
25
- filter?: string;
26
- clear?: boolean;
27
- rotate?: boolean;
28
- clearArchived?: boolean;
29
- clearAll?: boolean;
30
- includeHealth?: boolean;
31
- }
32
-
33
- export async function logsCommand(identifier: string, options: LogsOptions): Promise<void> {
34
- // Find server
35
- const server = await stateManager.findServer(identifier);
36
- if (!server) {
37
- throw new Error(`Server not found: ${identifier}\n\nUse: llamacpp ps`);
38
- }
39
-
40
- // Determine log file (default to stderr where verbose logs go)
41
- const logPath = options.stdout ? server.stdoutPath : server.stderrPath;
42
- const logType = options.stdout ? 'stdout' : 'stderr';
43
-
44
- // Handle --clear-archived option (deletes only archived logs)
45
- if (options.clearArchived) {
46
- const archivedInfo = await deleteArchivedLogs(server.id);
47
-
48
- if (archivedInfo.count === 0) {
49
- console.log(chalk.yellow(`⚠️ No archived logs found for ${server.modelName}`));
50
- console.log(chalk.dim(` Archived logs are created via --rotate or automatic rotation`));
51
- return;
52
- }
53
-
54
- console.log(chalk.green(`✅ Deleted archived logs for ${server.modelName}`));
55
- console.log(chalk.dim(` Files deleted: ${archivedInfo.count}`));
56
- console.log(chalk.dim(` Space freed: ${formatFileSize(archivedInfo.totalSize)}`));
57
- console.log(chalk.dim(` Current logs preserved`));
58
- return;
59
- }
60
-
61
- // Handle --clear-all option (clears both current and archived logs)
62
- if (options.clearAll) {
63
- let totalFreed = 0;
64
- let currentSize = 0;
65
- let archivedSize = 0;
66
-
67
- // Clear current stderr
68
- if (await fileExists(server.stderrPath)) {
69
- currentSize += await getFileSize(server.stderrPath);
70
- await clearLogFile(server.stderrPath);
71
- }
72
-
73
- // Clear current stdout
74
- if (await fileExists(server.stdoutPath)) {
75
- currentSize += await getFileSize(server.stdoutPath);
76
- await clearLogFile(server.stdoutPath);
77
- }
78
-
79
- // Delete all archived logs
80
- const archivedInfo = await deleteArchivedLogs(server.id);
81
- archivedSize = archivedInfo.totalSize;
82
-
83
- totalFreed = currentSize + archivedSize;
84
-
85
- console.log(chalk.green(`✅ Cleared all logs for ${server.modelName}`));
86
- if (currentSize > 0) {
87
- console.log(chalk.dim(` Current logs: ${formatFileSize(currentSize)}`));
88
- }
89
- if (archivedSize > 0) {
90
- console.log(chalk.dim(` Archived logs: ${formatFileSize(archivedSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`));
91
- }
92
- console.log(chalk.dim(` Total freed: ${formatFileSize(totalFreed)}`));
93
- return;
94
- }
95
-
96
- // Handle --clear option
97
- if (options.clear) {
98
- if (!(await fileExists(logPath))) {
99
- console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
100
- console.log(chalk.dim(` Log file does not exist: ${logPath}`));
101
- return;
102
- }
103
-
104
- const sizeBefore = await getFileSize(logPath);
105
- await clearLogFile(logPath);
106
-
107
- console.log(chalk.green(`✅ Cleared ${logType} for ${server.modelName}`));
108
- console.log(chalk.dim(` Freed: ${formatFileSize(sizeBefore)}`));
109
- console.log(chalk.dim(` ${logPath}`));
110
- return;
111
- }
112
-
113
- // Handle --rotate option
114
- if (options.rotate) {
115
- if (!(await fileExists(logPath))) {
116
- console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
117
- console.log(chalk.dim(` Log file does not exist: ${logPath}`));
118
- return;
119
- }
120
-
121
- try {
122
- const archivedPath = await rotateLogFile(logPath);
123
- const size = await getFileSize(archivedPath);
124
-
125
- console.log(chalk.green(`✅ Rotated ${logType} for ${server.modelName}`));
126
- console.log(chalk.dim(` Archived: ${formatFileSize(size)}`));
127
- console.log(chalk.dim(` → ${archivedPath}`));
128
- } catch (error) {
129
- throw new Error(`Failed to rotate log: ${(error as Error).message}`);
130
- }
131
- return;
132
- }
133
-
134
- // Check if log file exists
135
- if (!(await fileExists(logPath))) {
136
- console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
137
- console.log(chalk.dim(` Log file does not exist: ${logPath}`));
138
- return;
139
- }
140
-
141
- // Determine filter pattern and mode
142
- let filterPattern: string | null = null;
143
- let filterDesc = '';
144
- let useCompactMode = false;
145
-
146
- // Whether to include health check requests (filtered by default)
147
- const includeHealth = options.includeHealth ?? false;
148
-
149
- if (options.verbose) {
150
- // Show everything (no filter)
151
- filterDesc = ' (all messages)';
152
- } else if (options.errors) {
153
- // Show only errors
154
- filterPattern = 'error|Error|ERROR|failed|Failed|FAILED';
155
- filterDesc = ' (errors only)';
156
- } else if (options.http) {
157
- // Full HTTP JSON logs
158
- filterPattern = 'log_server_r';
159
- filterDesc = ' (HTTP JSON)';
160
- } else if (options.filter) {
161
- // Custom filter
162
- filterPattern = options.filter;
163
- filterDesc = ` (filter: ${options.filter})`;
164
- } else {
165
- // Default: Compact one-liner format
166
- filterPattern = 'log_server_r';
167
- filterDesc = ' (compact)';
168
- useCompactMode = true;
169
- }
170
-
171
- console.log(chalk.blue(`📋 Logs for ${server.modelName} (${logType}${filterDesc})`));
172
- console.log(chalk.dim(` ${logPath}`));
173
-
174
- // Show log size information
175
- const currentSize = await getFileSize(logPath);
176
- const archivedInfo = await getArchivedLogInfo(server.id);
177
-
178
- if (archivedInfo.count > 0) {
179
- console.log(chalk.dim(` Current: ${formatFileSize(currentSize)} | Archived: ${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`));
180
- } else {
181
- console.log(chalk.dim(` Current: ${formatFileSize(currentSize)}`));
182
- }
183
-
184
- // Show subtle note if verbose logging is not enabled
185
- if (!server.verbose && !options.verbose && !options.errors && !options.http && !options.filter) {
186
- console.log(chalk.dim(` verbosity is disabled`));
187
- }
188
- console.log();
189
-
190
- if (options.follow) {
191
- // Follow logs in real-time with optional filtering
192
- if (useCompactMode) {
193
- // Compact mode with follow: parse lines in real-time
194
- const tailProcess = spawn('tail', ['-f', logPath]);
195
- const rl = readline.createInterface({
196
- input: tailProcess.stdout,
197
- crlfDelay: Infinity,
198
- });
199
-
200
- rl.on('line', (line) => {
201
- if (line.includes('log_server_r')) {
202
- // Skip health check requests unless --include-health is set
203
- if (!includeHealth && logParser.isHealthCheckRequest(line)) {
204
- return;
205
- }
206
- logParser.processLine(line, (compactLine) => {
207
- // Double-check the parsed line for health checks (in case buffered)
208
- if (!includeHealth && logParser.isHealthCheckRequest(compactLine)) {
209
- return;
210
- }
211
- console.log(compactLine);
212
- });
213
- }
214
- });
215
-
216
- // Handle Ctrl+C gracefully
217
- process.on('SIGINT', () => {
218
- tailProcess.kill();
219
- rl.close();
220
- console.log();
221
- process.exit(0);
222
- });
223
-
224
- tailProcess.on('exit', () => {
225
- process.exit(0);
226
- });
227
- } else if (filterPattern) {
228
- // Use tail piped to grep for filtering
229
- const grepProcess = spawn('sh', ['-c', `tail -f "${logPath}" | grep --line-buffered -E "${filterPattern}"`], {
230
- stdio: 'inherit',
231
- });
232
-
233
- // Handle Ctrl+C gracefully
234
- process.on('SIGINT', () => {
235
- grepProcess.kill();
236
- console.log();
237
- process.exit(0);
238
- });
239
-
240
- grepProcess.on('exit', () => {
241
- process.exit(0);
242
- });
243
- } else {
244
- // No filter, just tail
245
- const tail = spawn('tail', ['-f', logPath], {
246
- stdio: 'inherit',
247
- });
248
-
249
- process.on('SIGINT', () => {
250
- tail.kill();
251
- console.log();
252
- process.exit(0);
253
- });
254
-
255
- tail.on('exit', () => {
256
- process.exit(0);
257
- });
258
- }
259
- } else {
260
- // Show last N lines with optional filtering
261
- const lines = options.lines || 50;
262
-
263
- if (useCompactMode) {
264
- // Compact mode: read file and parse
265
- try {
266
- // Use large multiplier to account for verbose debug output between requests
267
- // Add || true to prevent grep from failing when no matches found
268
- const command = `tail -n ${lines * 100} "${logPath}" | grep -E "log_server_r" || true`;
269
- const output = await execCommand(command);
270
- const logLines = output.split('\n').filter((l) => l.trim());
271
-
272
- if (logLines.length === 0) {
273
- console.log(chalk.dim('No HTTP request logs in compact format.'));
274
- console.log(chalk.dim('The server may be starting up, or only simple GET requests have been made.'));
275
- console.log(chalk.dim('\nTip: Use --http to see raw HTTP logs, or --verbose for all server logs.'));
276
- return;
277
- }
278
-
279
- const compactLines: string[] = [];
280
- for (const line of logLines) {
281
- // Skip health check requests unless --include-health is set
282
- if (!includeHealth && logParser.isHealthCheckRequest(line)) {
283
- continue;
284
- }
285
- logParser.processLine(line, (compactLine) => {
286
- // Double-check the parsed line for health checks (in case buffered)
287
- if (!includeHealth && logParser.isHealthCheckRequest(compactLine)) {
288
- return;
289
- }
290
- compactLines.push(compactLine);
291
- });
292
- }
293
-
294
- // Flush any remaining buffered logs (handles simple format)
295
- logParser.flush((compactLine) => {
296
- // Filter health checks from flushed lines too
297
- if (!includeHealth && logParser.isHealthCheckRequest(compactLine)) {
298
- return;
299
- }
300
- compactLines.push(compactLine);
301
- });
302
-
303
- // Check if we got any parsed output
304
- if (compactLines.length === 0) {
305
- console.log(chalk.dim('HTTP request logs found, but could not parse in compact format.'));
306
- console.log(chalk.dim('This usually happens with simple GET requests (health checks, slots, etc.).'));
307
- console.log(chalk.dim('\nTip: Use --http to see raw HTTP logs instead.'));
308
- return;
309
- }
310
-
311
- // Show only the last N compact lines
312
- const limitedLines = compactLines.slice(-lines);
313
- limitedLines.forEach((line) => console.log(line));
314
- } catch (error) {
315
- throw new Error(`Failed to read logs: ${(error as Error).message}`);
316
- }
317
- } else {
318
- // Regular filtering
319
- try {
320
- let command: string;
321
-
322
- if (filterPattern) {
323
- // Use tail piped to grep
324
- // Add || true to prevent grep from failing when no matches found
325
- command = `tail -n ${lines} "${logPath}" | grep -E "${filterPattern}" || true`;
326
- } else {
327
- // No filter
328
- command = `tail -n ${lines} "${logPath}"`;
329
- }
330
-
331
- const output = await execCommand(command);
332
-
333
- if (filterPattern && output.trim() === '') {
334
- console.log(chalk.dim(`No logs matching pattern: ${filterPattern}`));
335
- console.log(chalk.dim('\nTip: Try --verbose to see all logs, or adjust your filter pattern.'));
336
- return;
337
- }
338
-
339
- console.log(output);
340
- } catch (error) {
341
- throw new Error(`Failed to read logs: ${(error as Error).message}`);
342
- }
343
- }
344
- }
345
- }
@@ -1,110 +0,0 @@
1
- import chalk from 'chalk';
2
- import blessed from 'blessed';
3
- import { stateManager } from '../lib/state-manager.js';
4
- import { createMonitorUI } from '../tui/MonitorApp.js';
5
- import { createMultiServerMonitorUI } from '../tui/MultiServerMonitorApp.js';
6
-
7
- export async function monitorCommand(identifier?: string): Promise<void> {
8
- // Initialize state manager
9
- await stateManager.initialize();
10
-
11
- // Get all servers
12
- const allServers = await stateManager.getAllServers();
13
- if (allServers.length === 0) {
14
- throw new Error(
15
- `No servers configured.\n\n` +
16
- `Create a server first: llamacpp server create <model>`
17
- );
18
- }
19
-
20
- // Create blessed screen
21
- const screen = blessed.screen({
22
- smartCSR: true,
23
- title: 'llama.cpp Server Monitor',
24
- });
25
-
26
- // Determine which UI to launch
27
- if (identifier) {
28
- // User specified a server - single server mode
29
- const server = await stateManager.findServer(identifier);
30
- if (!server) {
31
- screen.destroy();
32
- throw new Error(
33
- `Server not found: ${identifier}\n\n` +
34
- `Use: llamacpp ps (to list servers)\n` +
35
- `Or create a new server: llamacpp server create <model>`
36
- );
37
- }
38
-
39
- // Update server status to reflect actual launchctl state
40
- const { statusChecker } = await import('../lib/status-checker.js');
41
- const status = await statusChecker.checkServer(server);
42
- server.status = status.isRunning ? 'running' : 'stopped';
43
- server.pid = status.pid || undefined;
44
-
45
- // Check if server is running
46
- if (server.status !== 'running') {
47
- screen.destroy();
48
- throw new Error(
49
- `Server ${server.modelName} is not running.\n\n` +
50
- `Start it first: llamacpp server start ${server.id}`
51
- );
52
- }
53
-
54
- // Launch single-server TUI
55
- await createMonitorUI(screen, server);
56
- } else if (allServers.length === 1) {
57
- // Only one server - single server mode
58
- const server = allServers[0];
59
-
60
- // Update server status to reflect actual launchctl state
61
- const { statusChecker } = await import('../lib/status-checker.js');
62
- const status = await statusChecker.checkServer(server);
63
- server.status = status.isRunning ? 'running' : 'stopped';
64
- server.pid = status.pid || undefined;
65
-
66
- // Check if server is running
67
- if (server.status !== 'running') {
68
- screen.destroy();
69
- throw new Error(
70
- `Server ${server.modelName} is not running.\n\n` +
71
- `Start it first: llamacpp server start ${server.id}`
72
- );
73
- }
74
-
75
- // Launch single-server TUI
76
- await createMonitorUI(screen, server);
77
- } else {
78
- // Multiple servers - multi-server mode
79
- // Update all server statuses to reflect actual launchctl state
80
- const { statusChecker } = await import('../lib/status-checker.js');
81
- for (const server of allServers) {
82
- const status = await statusChecker.checkServer(server);
83
- server.status = status.isRunning ? 'running' : 'stopped';
84
- server.pid = status.pid || undefined;
85
- }
86
-
87
- // Filter to only running servers for monitoring
88
- const runningServers = allServers.filter(s => s.status === 'running');
89
-
90
- if (runningServers.length === 0) {
91
- screen.destroy();
92
- throw new Error(
93
- `No servers are currently running.\n\n` +
94
- `Start a server first:\n` +
95
- allServers
96
- .map((s) => ` llamacpp server start ${s.id} # ${s.modelName}`)
97
- .join('\n')
98
- );
99
- }
100
-
101
- // Launch multi-server TUI (pass all servers so we can see stopped ones too)
102
- await createMultiServerMonitorUI(screen, allServers);
103
- }
104
-
105
- // Render the screen
106
- screen.render();
107
-
108
- // Note: TUI functions handle their own key events and exit directly
109
- // The process will stay alive until user presses Q/Ctrl+C
110
- }
@@ -1,84 +0,0 @@
1
- import chalk from 'chalk';
2
- import Table from 'cli-table3';
3
- import { stateManager } from '../lib/state-manager.js';
4
- import { statusChecker } from '../lib/status-checker.js';
5
- import { formatUptime, formatBytes } from '../utils/format-utils.js';
6
- import { getProcessMemory } from '../utils/process-utils.js';
7
- import { ServerConfig } from '../types/server-config.js';
8
-
9
- const STATUS_CONFIG = {
10
- running: { text: '✅ RUNNING', color: chalk.green },
11
- crashed: { text: '❌ CRASHED', color: chalk.red },
12
- stopped: { text: '⚠️ STOPPED', color: chalk.yellow },
13
- } as const;
14
-
15
- async function getServerMemory(server: ServerConfig): Promise<string> {
16
- if (server.status !== 'running' || !server.pid) {
17
- return '-';
18
- }
19
-
20
- const cpuMemoryBytes = await getProcessMemory(server.pid);
21
- if (cpuMemoryBytes === null) {
22
- return '-';
23
- }
24
-
25
- const metalMemoryBytes = server.metalMemoryMB ? server.metalMemoryMB * 1024 * 1024 : 0;
26
- return formatBytes(cpuMemoryBytes + metalMemoryBytes);
27
- }
28
-
29
- export async function psCommand(): Promise<void> {
30
- const servers = await stateManager.getAllServers();
31
-
32
- if (servers.length === 0) {
33
- console.log(chalk.yellow('No servers configured.'));
34
- console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
35
- return;
36
- }
37
-
38
- console.log(chalk.dim('Checking server statuses...\n'));
39
- const serversWithStatus = await statusChecker.updateAllServerStatuses();
40
-
41
- const table = new Table({
42
- head: ['SERVER ID', 'MODEL', 'PORT', 'STATUS', 'PID', 'MEMORY', 'UPTIME'],
43
- });
44
-
45
- const counts = { running: 0, stopped: 0, crashed: 0 };
46
-
47
- for (const server of serversWithStatus) {
48
- const status = server.status || 'stopped';
49
- const config = STATUS_CONFIG[status] || STATUS_CONFIG.stopped;
50
- counts[status]++;
51
-
52
- const uptime = server.status === 'running' && server.lastStarted
53
- ? formatUptime(server.lastStarted)
54
- : '-';
55
-
56
- const memoryText = await getServerMemory(server);
57
-
58
- table.push([
59
- server.id,
60
- server.modelName,
61
- server.port.toString(),
62
- config.color(config.text),
63
- server.pid?.toString() || '-',
64
- memoryText,
65
- uptime,
66
- ]);
67
- }
68
-
69
- console.log(table.toString());
70
-
71
- const summary = [
72
- chalk.green(`${counts.running} running`),
73
- chalk.yellow(`${counts.stopped} stopped`),
74
- ];
75
- if (counts.crashed > 0) {
76
- summary.push(chalk.red(`${counts.crashed} crashed`));
77
- }
78
-
79
- console.log(chalk.dim(`\nTotal: ${servers.length} servers (${summary.join(', ')})`));
80
-
81
- if (counts.crashed > 0) {
82
- console.log(chalk.red('\n⚠️ Some servers have crashed. Check logs with: llamacpp server logs <id> --errors'));
83
- }
84
- }
@@ -1,44 +0,0 @@
1
- import chalk from 'chalk';
2
- import { modelDownloader } from '../lib/model-downloader';
3
- import { ensureModelsDirectory } from '../lib/models-dir-setup';
4
-
5
- interface PullOptions {
6
- file?: string;
7
- }
8
-
9
- export async function pullCommand(identifier: string, options: PullOptions): Promise<void> {
10
- // Parse repository identifier
11
- const parsed = modelDownloader.parseHFIdentifier(identifier);
12
-
13
- // Determine filename - from --file flag or from identifier path
14
- let filename = options.file || parsed.file;
15
-
16
- if (!filename) {
17
- throw new Error(
18
- 'Please specify a file to download:\n\n' +
19
- 'Option 1: llamacpp pull owner/repo/filename.gguf\n' +
20
- 'Option 2: llamacpp pull owner/repo --file filename.gguf'
21
- );
22
- }
23
-
24
- // Ensure filename ends with .gguf
25
- if (!filename.toLowerCase().endsWith('.gguf')) {
26
- filename += '.gguf';
27
- }
28
-
29
- // Ensure models directory exists (prompts user if needed)
30
- const modelsDir = await ensureModelsDirectory();
31
-
32
- // Download the model
33
- try {
34
- const modelPath = await modelDownloader.downloadModel(parsed.repo, filename, undefined, modelsDir);
35
-
36
- console.log();
37
- console.log(chalk.dim(`Create server: llamacpp server create ${filename}`));
38
- } catch (error) {
39
- if ((error as Error).message.includes('interrupted')) {
40
- console.log(chalk.dim('\nDownload was interrupted. Run the same command again to retry.'));
41
- }
42
- throw error;
43
- }
44
- }
@@ -1,107 +0,0 @@
1
- import chalk from 'chalk';
2
- import * as readline from 'readline';
3
- import * as fs from 'fs/promises';
4
- import { modelScanner } from '../lib/model-scanner';
5
- import { stateManager } from '../lib/state-manager';
6
- import { launchctlManager } from '../lib/launchctl-manager';
7
-
8
- export async function rmCommand(modelIdentifier: string): Promise<void> {
9
- await stateManager.initialize();
10
-
11
- // 1. Resolve model path
12
- const modelPath = await modelScanner.resolveModelPath(modelIdentifier);
13
- if (!modelPath) {
14
- throw new Error(`Model not found: ${modelIdentifier}\n\nRun: llamacpp ls`);
15
- }
16
-
17
- // 2. Check if any servers are using this model
18
- const allServers = await stateManager.getAllServers();
19
- const serversUsingModel = allServers.filter((s) => s.modelPath === modelPath);
20
-
21
- // 3. Confirm deletion
22
- console.log(chalk.yellow(`⚠️ Delete model file: ${modelPath}`));
23
-
24
- if (serversUsingModel.length > 0) {
25
- console.log(chalk.yellow(`\n This model has ${serversUsingModel.length} server(s) configured:`));
26
- for (const server of serversUsingModel) {
27
- const statusColor = server.status === 'running' ? chalk.green : chalk.dim;
28
- console.log(chalk.yellow(` - ${server.id} (${statusColor(server.status)})`));
29
- }
30
- console.log(chalk.yellow(`\n These servers will be removed before deleting the model.`));
31
- }
32
-
33
- console.log();
34
-
35
- const confirmed = await confirmDeletion();
36
- if (!confirmed) {
37
- console.log(chalk.dim('Cancelled'));
38
- return;
39
- }
40
-
41
- console.log();
42
-
43
- // 4. Delete all servers using this model
44
- if (serversUsingModel.length > 0) {
45
- console.log(chalk.blue(`🗑️ Removing ${serversUsingModel.length} server(s)...\n`));
46
-
47
- for (const server of serversUsingModel) {
48
- console.log(chalk.dim(` Removing server: ${server.id}`));
49
-
50
- // Unload service (stops and removes from launchd)
51
- try {
52
- await launchctlManager.unloadService(server.plistPath);
53
- if (server.status === 'running') {
54
- await launchctlManager.waitForServiceStop(server.label, 5000);
55
- }
56
- } catch (error) {
57
- console.log(chalk.yellow(` ⚠️ Failed to unload service gracefully`));
58
- }
59
-
60
- // Delete plist
61
- await launchctlManager.deletePlist(server.plistPath);
62
-
63
- // Delete server config
64
- await stateManager.deleteServerConfig(server.id);
65
-
66
- console.log(chalk.dim(` ✓ Server removed`));
67
- }
68
-
69
- console.log();
70
- }
71
-
72
- // 5. Delete model file
73
- console.log(chalk.blue(`🗑️ Deleting model file...`));
74
-
75
- try {
76
- await fs.unlink(modelPath);
77
- } catch (error) {
78
- throw new Error(`Failed to delete model file: ${(error as Error).message}`);
79
- }
80
-
81
- // Success
82
- console.log();
83
- console.log(chalk.green('✅ Model deleted successfully'));
84
-
85
- if (serversUsingModel.length > 0) {
86
- console.log(chalk.dim(` Removed ${serversUsingModel.length} server(s)`));
87
- }
88
-
89
- console.log(chalk.dim(` Deleted: ${modelPath}`));
90
- }
91
-
92
- /**
93
- * Prompt user for confirmation
94
- */
95
- function confirmDeletion(): Promise<boolean> {
96
- return new Promise((resolve) => {
97
- const rl = readline.createInterface({
98
- input: process.stdin,
99
- output: process.stdout,
100
- });
101
-
102
- rl.question(chalk.yellow(" Type 'yes' to confirm: "), (answer) => {
103
- rl.close();
104
- resolve(answer.toLowerCase() === 'yes');
105
- });
106
- });
107
- }