@appkit/llamacpp-cli 1.5.0 → 1.7.0
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/CHANGELOG.md +20 -0
- package/MONITORING-ACCURACY-FIX.md +199 -0
- package/PER-PROCESS-METRICS.md +190 -0
- package/README.md +124 -9
- package/dist/cli.js +32 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +15 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +12 -4
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/delete.js +12 -10
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/logs-all.d.ts +9 -0
- package/dist/commands/logs-all.d.ts.map +1 -0
- package/dist/commands/logs-all.js +209 -0
- package/dist/commands/logs-all.js.map +1 -0
- package/dist/commands/logs.d.ts +4 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +108 -2
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/monitor.d.ts.map +1 -1
- package/dist/commands/monitor.js +51 -1
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/ps.d.ts +3 -1
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +75 -5
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +5 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/server-show.d.ts.map +1 -1
- package/dist/commands/server-show.js +30 -3
- package/dist/commands/server-show.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +34 -7
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.js +3 -3
- package/dist/commands/stop.js.map +1 -1
- package/dist/lib/history-manager.d.ts +46 -0
- package/dist/lib/history-manager.d.ts.map +1 -0
- package/dist/lib/history-manager.js +157 -0
- package/dist/lib/history-manager.js.map +1 -0
- package/dist/lib/metrics-aggregator.d.ts +2 -1
- package/dist/lib/metrics-aggregator.d.ts.map +1 -1
- package/dist/lib/metrics-aggregator.js +15 -4
- package/dist/lib/metrics-aggregator.js.map +1 -1
- package/dist/lib/system-collector.d.ts +9 -4
- package/dist/lib/system-collector.d.ts.map +1 -1
- package/dist/lib/system-collector.js +29 -28
- package/dist/lib/system-collector.js.map +1 -1
- package/dist/tui/HistoricalMonitorApp.d.ts +5 -0
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -0
- package/dist/tui/HistoricalMonitorApp.js +490 -0
- package/dist/tui/HistoricalMonitorApp.js.map +1 -0
- package/dist/tui/MonitorApp.d.ts.map +1 -1
- package/dist/tui/MonitorApp.js +84 -62
- package/dist/tui/MonitorApp.js.map +1 -1
- package/dist/tui/MultiServerMonitorApp.d.ts +1 -1
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
- package/dist/tui/MultiServerMonitorApp.js +293 -77
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/types/history-types.d.ts +30 -0
- package/dist/types/history-types.d.ts.map +1 -0
- package/dist/types/history-types.js +11 -0
- package/dist/types/history-types.js.map +1 -0
- package/dist/types/monitor-types.d.ts +1 -0
- package/dist/types/monitor-types.d.ts.map +1 -1
- package/dist/types/server-config.d.ts +1 -0
- package/dist/types/server-config.d.ts.map +1 -1
- package/dist/types/server-config.js.map +1 -1
- package/dist/utils/downsample-utils.d.ts +35 -0
- package/dist/utils/downsample-utils.d.ts.map +1 -0
- package/dist/utils/downsample-utils.js +107 -0
- package/dist/utils/downsample-utils.js.map +1 -0
- package/dist/utils/file-utils.d.ts +6 -0
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +38 -0
- package/dist/utils/file-utils.js.map +1 -1
- package/dist/utils/log-utils.d.ts +43 -0
- package/dist/utils/log-utils.d.ts.map +1 -0
- package/dist/utils/log-utils.js +190 -0
- package/dist/utils/log-utils.js.map +1 -0
- package/dist/utils/process-utils.d.ts +19 -1
- package/dist/utils/process-utils.d.ts.map +1 -1
- package/dist/utils/process-utils.js +79 -1
- package/dist/utils/process-utils.js.map +1 -1
- package/docs/images/.gitkeep +1 -0
- package/package.json +3 -1
- package/src/cli.ts +32 -7
- package/src/commands/config.ts +15 -1
- package/src/commands/create.ts +14 -5
- package/src/commands/delete.ts +10 -10
- package/src/commands/logs-all.ts +251 -0
- package/src/commands/logs.ts +138 -2
- package/src/commands/monitor.ts +21 -1
- package/src/commands/ps.ts +88 -5
- package/src/commands/rm.ts +5 -12
- package/src/commands/server-show.ts +35 -3
- package/src/commands/start.ts +35 -7
- package/src/commands/stop.ts +3 -3
- package/src/lib/history-manager.ts +172 -0
- package/src/lib/metrics-aggregator.ts +18 -5
- package/src/lib/system-collector.ts +31 -28
- package/src/tui/HistoricalMonitorApp.ts +548 -0
- package/src/tui/MonitorApp.ts +89 -64
- package/src/tui/MultiServerMonitorApp.ts +348 -103
- package/src/types/history-types.ts +39 -0
- package/src/types/monitor-types.ts +1 -0
- package/src/types/server-config.ts +1 -0
- package/src/utils/downsample-utils.ts +128 -0
- package/src/utils/file-utils.ts +40 -0
- package/src/utils/log-utils.ts +178 -0
- package/src/utils/process-utils.ts +85 -1
- package/test-load.sh +100 -0
- package/dist/tui/components/ErrorState.d.ts +0 -8
- package/dist/tui/components/ErrorState.d.ts.map +0 -1
- package/dist/tui/components/ErrorState.js +0 -22
- package/dist/tui/components/ErrorState.js.map +0 -1
- package/dist/tui/components/LoadingState.d.ts +0 -8
- package/dist/tui/components/LoadingState.d.ts.map +0 -1
- package/dist/tui/components/LoadingState.js +0 -21
- package/dist/tui/components/LoadingState.js.map +0 -1
package/src/commands/logs.ts
CHANGED
|
@@ -6,6 +6,14 @@ import { stateManager } from '../lib/state-manager';
|
|
|
6
6
|
import { fileExists } from '../utils/file-utils';
|
|
7
7
|
import { execCommand } from '../utils/process-utils';
|
|
8
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';
|
|
9
17
|
|
|
10
18
|
interface LogsOptions {
|
|
11
19
|
follow?: boolean;
|
|
@@ -15,6 +23,10 @@ interface LogsOptions {
|
|
|
15
23
|
http?: boolean;
|
|
16
24
|
stdout?: boolean;
|
|
17
25
|
filter?: string;
|
|
26
|
+
clear?: boolean;
|
|
27
|
+
rotate?: boolean;
|
|
28
|
+
clearArchived?: boolean;
|
|
29
|
+
clearAll?: boolean;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
export async function logsCommand(identifier: string, options: LogsOptions): Promise<void> {
|
|
@@ -28,6 +40,96 @@ export async function logsCommand(identifier: string, options: LogsOptions): Pro
|
|
|
28
40
|
const logPath = options.stdout ? server.stdoutPath : server.stderrPath;
|
|
29
41
|
const logType = options.stdout ? 'stdout' : 'stderr';
|
|
30
42
|
|
|
43
|
+
// Handle --clear-archived option (deletes only archived logs)
|
|
44
|
+
if (options.clearArchived) {
|
|
45
|
+
const archivedInfo = await deleteArchivedLogs(server.id);
|
|
46
|
+
|
|
47
|
+
if (archivedInfo.count === 0) {
|
|
48
|
+
console.log(chalk.yellow(`⚠️ No archived logs found for ${server.modelName}`));
|
|
49
|
+
console.log(chalk.dim(` Archived logs are created via --rotate or automatic rotation`));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(chalk.green(`✅ Deleted archived logs for ${server.modelName}`));
|
|
54
|
+
console.log(chalk.dim(` Files deleted: ${archivedInfo.count}`));
|
|
55
|
+
console.log(chalk.dim(` Space freed: ${formatFileSize(archivedInfo.totalSize)}`));
|
|
56
|
+
console.log(chalk.dim(` Current logs preserved`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle --clear-all option (clears both current and archived logs)
|
|
61
|
+
if (options.clearAll) {
|
|
62
|
+
let totalFreed = 0;
|
|
63
|
+
let currentSize = 0;
|
|
64
|
+
let archivedSize = 0;
|
|
65
|
+
|
|
66
|
+
// Clear current stderr
|
|
67
|
+
if (await fileExists(server.stderrPath)) {
|
|
68
|
+
currentSize += await getFileSize(server.stderrPath);
|
|
69
|
+
await clearLogFile(server.stderrPath);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Clear current stdout
|
|
73
|
+
if (await fileExists(server.stdoutPath)) {
|
|
74
|
+
currentSize += await getFileSize(server.stdoutPath);
|
|
75
|
+
await clearLogFile(server.stdoutPath);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Delete all archived logs
|
|
79
|
+
const archivedInfo = await deleteArchivedLogs(server.id);
|
|
80
|
+
archivedSize = archivedInfo.totalSize;
|
|
81
|
+
|
|
82
|
+
totalFreed = currentSize + archivedSize;
|
|
83
|
+
|
|
84
|
+
console.log(chalk.green(`✅ Cleared all logs for ${server.modelName}`));
|
|
85
|
+
if (currentSize > 0) {
|
|
86
|
+
console.log(chalk.dim(` Current logs: ${formatFileSize(currentSize)}`));
|
|
87
|
+
}
|
|
88
|
+
if (archivedSize > 0) {
|
|
89
|
+
console.log(chalk.dim(` Archived logs: ${formatFileSize(archivedSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`));
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.dim(` Total freed: ${formatFileSize(totalFreed)}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle --clear option
|
|
96
|
+
if (options.clear) {
|
|
97
|
+
if (!(await fileExists(logPath))) {
|
|
98
|
+
console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
|
|
99
|
+
console.log(chalk.dim(` Log file does not exist: ${logPath}`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sizeBefore = await getFileSize(logPath);
|
|
104
|
+
await clearLogFile(logPath);
|
|
105
|
+
|
|
106
|
+
console.log(chalk.green(`✅ Cleared ${logType} for ${server.modelName}`));
|
|
107
|
+
console.log(chalk.dim(` Freed: ${formatFileSize(sizeBefore)}`));
|
|
108
|
+
console.log(chalk.dim(` ${logPath}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle --rotate option
|
|
113
|
+
if (options.rotate) {
|
|
114
|
+
if (!(await fileExists(logPath))) {
|
|
115
|
+
console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
|
|
116
|
+
console.log(chalk.dim(` Log file does not exist: ${logPath}`));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const archivedPath = await rotateLogFile(logPath);
|
|
122
|
+
const size = await getFileSize(archivedPath);
|
|
123
|
+
|
|
124
|
+
console.log(chalk.green(`✅ Rotated ${logType} for ${server.modelName}`));
|
|
125
|
+
console.log(chalk.dim(` Archived: ${formatFileSize(size)}`));
|
|
126
|
+
console.log(chalk.dim(` → ${archivedPath}`));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new Error(`Failed to rotate log: ${(error as Error).message}`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
31
133
|
// Check if log file exists
|
|
32
134
|
if (!(await fileExists(logPath))) {
|
|
33
135
|
console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
|
|
@@ -65,6 +167,16 @@ export async function logsCommand(identifier: string, options: LogsOptions): Pro
|
|
|
65
167
|
console.log(chalk.blue(`📋 Logs for ${server.modelName} (${logType}${filterDesc})`));
|
|
66
168
|
console.log(chalk.dim(` ${logPath}`));
|
|
67
169
|
|
|
170
|
+
// Show log size information
|
|
171
|
+
const currentSize = await getFileSize(logPath);
|
|
172
|
+
const archivedInfo = await getArchivedLogInfo(server.id);
|
|
173
|
+
|
|
174
|
+
if (archivedInfo.count > 0) {
|
|
175
|
+
console.log(chalk.dim(` Current: ${formatFileSize(currentSize)} | Archived: ${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`));
|
|
176
|
+
} else {
|
|
177
|
+
console.log(chalk.dim(` Current: ${formatFileSize(currentSize)}`));
|
|
178
|
+
}
|
|
179
|
+
|
|
68
180
|
// Show subtle note if verbose logging is not enabled
|
|
69
181
|
if (!server.verbose && !options.verbose && !options.errors && !options.http && !options.filter) {
|
|
70
182
|
console.log(chalk.dim(` verbosity is disabled`));
|
|
@@ -140,10 +252,18 @@ export async function logsCommand(identifier: string, options: LogsOptions): Pro
|
|
|
140
252
|
// Compact mode: read file and parse
|
|
141
253
|
try {
|
|
142
254
|
// Use large multiplier to account for verbose debug output between requests
|
|
143
|
-
|
|
255
|
+
// Add || true to prevent grep from failing when no matches found
|
|
256
|
+
const command = `tail -n ${lines * 100} "${logPath}" | grep -E "log_server_r" || true`;
|
|
144
257
|
const output = await execCommand(command);
|
|
145
258
|
const logLines = output.split('\n').filter((l) => l.trim());
|
|
146
259
|
|
|
260
|
+
if (logLines.length === 0) {
|
|
261
|
+
console.log(chalk.dim('No HTTP request logs in compact format.'));
|
|
262
|
+
console.log(chalk.dim('The server may be starting up, or only simple GET requests have been made.'));
|
|
263
|
+
console.log(chalk.dim('\nTip: Use --http to see raw HTTP logs, or --verbose for all server logs.'));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
147
267
|
const compactLines: string[] = [];
|
|
148
268
|
for (const line of logLines) {
|
|
149
269
|
logParser.processLine(line, (compactLine) => {
|
|
@@ -156,6 +276,14 @@ export async function logsCommand(identifier: string, options: LogsOptions): Pro
|
|
|
156
276
|
compactLines.push(compactLine);
|
|
157
277
|
});
|
|
158
278
|
|
|
279
|
+
// Check if we got any parsed output
|
|
280
|
+
if (compactLines.length === 0) {
|
|
281
|
+
console.log(chalk.dim('HTTP request logs found, but could not parse in compact format.'));
|
|
282
|
+
console.log(chalk.dim('This usually happens with simple GET requests (health checks, slots, etc.).'));
|
|
283
|
+
console.log(chalk.dim('\nTip: Use --http to see raw HTTP logs instead.'));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
159
287
|
// Show only the last N compact lines
|
|
160
288
|
const limitedLines = compactLines.slice(-lines);
|
|
161
289
|
limitedLines.forEach((line) => console.log(line));
|
|
@@ -169,13 +297,21 @@ export async function logsCommand(identifier: string, options: LogsOptions): Pro
|
|
|
169
297
|
|
|
170
298
|
if (filterPattern) {
|
|
171
299
|
// Use tail piped to grep
|
|
172
|
-
|
|
300
|
+
// Add || true to prevent grep from failing when no matches found
|
|
301
|
+
command = `tail -n ${lines} "${logPath}" | grep -E "${filterPattern}" || true`;
|
|
173
302
|
} else {
|
|
174
303
|
// No filter
|
|
175
304
|
command = `tail -n ${lines} "${logPath}"`;
|
|
176
305
|
}
|
|
177
306
|
|
|
178
307
|
const output = await execCommand(command);
|
|
308
|
+
|
|
309
|
+
if (filterPattern && output.trim() === '') {
|
|
310
|
+
console.log(chalk.dim(`No logs matching pattern: ${filterPattern}`));
|
|
311
|
+
console.log(chalk.dim('\nTip: Try --verbose to see all logs, or adjust your filter pattern.'));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
179
315
|
console.log(output);
|
|
180
316
|
} catch (error) {
|
|
181
317
|
throw new Error(`Failed to read logs: ${(error as Error).message}`);
|
package/src/commands/monitor.ts
CHANGED
|
@@ -36,6 +36,12 @@ export async function monitorCommand(identifier?: string): Promise<void> {
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
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
|
+
|
|
39
45
|
// Check if server is running
|
|
40
46
|
if (server.status !== 'running') {
|
|
41
47
|
screen.destroy();
|
|
@@ -51,6 +57,12 @@ export async function monitorCommand(identifier?: string): Promise<void> {
|
|
|
51
57
|
// Only one server - single server mode
|
|
52
58
|
const server = allServers[0];
|
|
53
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
|
+
|
|
54
66
|
// Check if server is running
|
|
55
67
|
if (server.status !== 'running') {
|
|
56
68
|
screen.destroy();
|
|
@@ -64,6 +76,14 @@ export async function monitorCommand(identifier?: string): Promise<void> {
|
|
|
64
76
|
await createMonitorUI(screen, server);
|
|
65
77
|
} else {
|
|
66
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
|
+
|
|
67
87
|
// Filter to only running servers for monitoring
|
|
68
88
|
const runningServers = allServers.filter(s => s.status === 'running');
|
|
69
89
|
|
|
@@ -78,7 +98,7 @@ export async function monitorCommand(identifier?: string): Promise<void> {
|
|
|
78
98
|
);
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
// Launch multi-server TUI
|
|
101
|
+
// Launch multi-server TUI (pass all servers so we can see stopped ones too)
|
|
82
102
|
await createMultiServerMonitorUI(screen, allServers);
|
|
83
103
|
}
|
|
84
104
|
|
package/src/commands/ps.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import Table from 'cli-table3';
|
|
3
|
+
import blessed from 'blessed';
|
|
3
4
|
import { stateManager } from '../lib/state-manager';
|
|
4
5
|
import { statusChecker } from '../lib/status-checker';
|
|
5
6
|
import { formatUptime, formatBytes } from '../utils/format-utils';
|
|
6
7
|
import { getProcessMemory } from '../utils/process-utils';
|
|
8
|
+
import { createMultiServerMonitorUI } from '../tui/MultiServerMonitorApp.js';
|
|
9
|
+
import { ServerConfig } from '../types/server-config.js';
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
async function showStaticTable(): Promise<void> {
|
|
9
12
|
const servers = await stateManager.getAllServers();
|
|
10
13
|
|
|
11
14
|
if (servers.length === 0) {
|
|
@@ -52,12 +55,14 @@ export async function psCommand(): Promise<void> {
|
|
|
52
55
|
? formatUptime(server.lastStarted)
|
|
53
56
|
: '-';
|
|
54
57
|
|
|
55
|
-
// Get memory usage for running servers
|
|
58
|
+
// Get memory usage for running servers (CPU + Metal GPU memory)
|
|
56
59
|
let memoryText = '-';
|
|
57
60
|
if (server.status === 'running' && server.pid) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
+
const cpuMemoryBytes = await getProcessMemory(server.pid);
|
|
62
|
+
if (cpuMemoryBytes !== null) {
|
|
63
|
+
const metalMemoryBytes = server.metalMemoryMB ? server.metalMemoryMB * 1024 * 1024 : 0;
|
|
64
|
+
const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
|
|
65
|
+
memoryText = formatBytes(totalMemoryBytes);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -88,3 +93,81 @@ export async function psCommand(): Promise<void> {
|
|
|
88
93
|
console.log(chalk.red('\n⚠️ Some servers have crashed. Check logs with: llamacpp server logs <id> --errors'));
|
|
89
94
|
}
|
|
90
95
|
}
|
|
96
|
+
|
|
97
|
+
export async function psCommand(identifier?: string, options?: { table?: boolean }): Promise<void> {
|
|
98
|
+
// If --table flag is set, show static table (backward compatibility)
|
|
99
|
+
if (options?.table) {
|
|
100
|
+
await showStaticTable();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Get all servers and update their statuses
|
|
105
|
+
const servers = await stateManager.getAllServers();
|
|
106
|
+
|
|
107
|
+
if (servers.length === 0) {
|
|
108
|
+
console.log(chalk.yellow('No servers configured.'));
|
|
109
|
+
console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update all server statuses
|
|
114
|
+
const updated = await statusChecker.updateAllServerStatuses();
|
|
115
|
+
|
|
116
|
+
// If identifier is provided, find the server and jump to detail view
|
|
117
|
+
if (identifier) {
|
|
118
|
+
const server = await findServer(identifier, updated);
|
|
119
|
+
if (!server) {
|
|
120
|
+
console.log(chalk.red(`❌ Server not found: ${identifier}`));
|
|
121
|
+
console.log(chalk.dim('\nAvailable servers:'));
|
|
122
|
+
updated.forEach((s: ServerConfig) => {
|
|
123
|
+
console.log(chalk.dim(` - ${s.id} (port ${s.port})`));
|
|
124
|
+
});
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Find the server index for direct jump
|
|
129
|
+
const serverIndex = updated.findIndex(s => s.id === server.id);
|
|
130
|
+
|
|
131
|
+
// Launch multi-server TUI with direct jump to detail view
|
|
132
|
+
const screen = blessed.screen({
|
|
133
|
+
smartCSR: true,
|
|
134
|
+
title: 'llama.cpp Multi-Server Monitor',
|
|
135
|
+
fullUnicode: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await createMultiServerMonitorUI(screen, updated, true, serverIndex); // fromPs = true, directJumpIndex
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No identifier - launch multi-server TUI
|
|
143
|
+
const runningServers = updated.filter((s: ServerConfig) => s.status === 'running');
|
|
144
|
+
|
|
145
|
+
// Launch multi-server TUI (shows all servers, not just running ones)
|
|
146
|
+
const screen = blessed.screen({
|
|
147
|
+
smartCSR: true,
|
|
148
|
+
title: 'llama.cpp Multi-Server Monitor',
|
|
149
|
+
fullUnicode: true,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await createMultiServerMonitorUI(screen, updated, true); // fromPs = true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Helper function to find server by identifier
|
|
156
|
+
async function findServer(identifier: string, servers: ServerConfig[]): Promise<ServerConfig | null> {
|
|
157
|
+
// Try by port
|
|
158
|
+
const port = parseInt(identifier);
|
|
159
|
+
if (!isNaN(port)) {
|
|
160
|
+
const server = servers.find(s => s.port === port);
|
|
161
|
+
if (server) return server;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Try by exact ID
|
|
165
|
+
const byId = servers.find(s => s.id === identifier);
|
|
166
|
+
if (byId) return byId;
|
|
167
|
+
|
|
168
|
+
// Try by partial model name
|
|
169
|
+
const byModel = servers.find(s => s.modelName.toLowerCase().includes(identifier.toLowerCase()));
|
|
170
|
+
if (byModel) return byModel;
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
package/src/commands/rm.ts
CHANGED
|
@@ -47,21 +47,14 @@ export async function rmCommand(modelIdentifier: string): Promise<void> {
|
|
|
47
47
|
for (const server of serversUsingModel) {
|
|
48
48
|
console.log(chalk.dim(` Removing server: ${server.id}`));
|
|
49
49
|
|
|
50
|
-
//
|
|
51
|
-
if (server.status === 'running') {
|
|
52
|
-
try {
|
|
53
|
-
await launchctlManager.stopService(server.label);
|
|
54
|
-
await launchctlManager.waitForServiceStop(server.label, 5000);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.log(chalk.yellow(` ⚠️ Failed to stop server gracefully`));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Unload service
|
|
50
|
+
// Unload service (stops and removes from launchd)
|
|
61
51
|
try {
|
|
62
52
|
await launchctlManager.unloadService(server.plistPath);
|
|
53
|
+
if (server.status === 'running') {
|
|
54
|
+
await launchctlManager.waitForServiceStop(server.label, 5000);
|
|
55
|
+
}
|
|
63
56
|
} catch (error) {
|
|
64
|
-
|
|
57
|
+
console.log(chalk.yellow(` ⚠️ Failed to unload service gracefully`));
|
|
65
58
|
}
|
|
66
59
|
|
|
67
60
|
// Delete plist
|
|
@@ -3,6 +3,8 @@ import { stateManager } from '../lib/state-manager';
|
|
|
3
3
|
import { statusChecker } from '../lib/status-checker';
|
|
4
4
|
import { formatUptime, formatBytes } from '../utils/format-utils';
|
|
5
5
|
import { getProcessMemory } from '../utils/process-utils';
|
|
6
|
+
import { getFileSize, formatFileSize, getArchivedLogInfo } from '../utils/log-utils';
|
|
7
|
+
import { fileExists } from '../utils/file-utils';
|
|
6
8
|
|
|
7
9
|
export async function serverShowCommand(identifier: string): Promise<void> {
|
|
8
10
|
// Find the server
|
|
@@ -68,9 +70,16 @@ export async function serverShowCommand(identifier: string): Promise<void> {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
if (updatedServer.pid) {
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
73
|
+
const cpuMemoryBytes = await getProcessMemory(updatedServer.pid);
|
|
74
|
+
if (cpuMemoryBytes !== null) {
|
|
75
|
+
const metalMemoryBytes = updatedServer.metalMemoryMB ? updatedServer.metalMemoryMB * 1024 * 1024 : 0;
|
|
76
|
+
const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
|
|
77
|
+
|
|
78
|
+
if (metalMemoryBytes > 0) {
|
|
79
|
+
console.log(`${chalk.bold('Memory:')} ${formatBytes(totalMemoryBytes)} (CPU: ${formatBytes(cpuMemoryBytes)}, GPU: ${formatBytes(metalMemoryBytes)})`);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`${chalk.bold('Memory:')} ${formatBytes(cpuMemoryBytes)} (CPU only)`);
|
|
82
|
+
}
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
85
|
}
|
|
@@ -89,6 +98,29 @@ export async function serverShowCommand(identifier: string): Promise<void> {
|
|
|
89
98
|
console.log(`${chalk.bold('Custom Flags:')} ${updatedServer.customFlags.join(' ')}`);
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
// Logs section
|
|
102
|
+
console.log('\n' + '─'.repeat(70));
|
|
103
|
+
console.log(chalk.bold('Logs:'));
|
|
104
|
+
console.log('─'.repeat(70));
|
|
105
|
+
|
|
106
|
+
// Get current log sizes
|
|
107
|
+
const stderrSize = (await fileExists(updatedServer.stderrPath))
|
|
108
|
+
? await getFileSize(updatedServer.stderrPath)
|
|
109
|
+
: 0;
|
|
110
|
+
const stdoutSize = (await fileExists(updatedServer.stdoutPath))
|
|
111
|
+
? await getFileSize(updatedServer.stdoutPath)
|
|
112
|
+
: 0;
|
|
113
|
+
|
|
114
|
+
// Get archived log info
|
|
115
|
+
const archivedInfo = await getArchivedLogInfo(updatedServer.id);
|
|
116
|
+
|
|
117
|
+
console.log(`${chalk.bold('Stderr:')} ${formatFileSize(stderrSize)} (current)`);
|
|
118
|
+
console.log(`${chalk.bold('Stdout:')} ${formatFileSize(stdoutSize)} (current)`);
|
|
119
|
+
|
|
120
|
+
if (archivedInfo.count > 0) {
|
|
121
|
+
console.log(`${chalk.bold('Archived:')} ${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`);
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
// Timestamps section
|
|
93
125
|
console.log('\n' + '─'.repeat(70));
|
|
94
126
|
console.log(chalk.bold('Timestamps:'));
|
package/src/commands/start.ts
CHANGED
|
@@ -2,6 +2,8 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { stateManager } from '../lib/state-manager';
|
|
3
3
|
import { launchctlManager } from '../lib/launchctl-manager';
|
|
4
4
|
import { statusChecker } from '../lib/status-checker';
|
|
5
|
+
import { parseMetalMemoryFromLog } from '../utils/file-utils';
|
|
6
|
+
import { autoRotateIfNeeded, formatFileSize } from '../utils/log-utils';
|
|
5
7
|
|
|
6
8
|
export async function startCommand(identifier: string): Promise<void> {
|
|
7
9
|
// Initialize state manager
|
|
@@ -29,28 +31,42 @@ export async function startCommand(identifier: string): Promise<void> {
|
|
|
29
31
|
|
|
30
32
|
console.log(chalk.blue(`▶️ Starting ${server.modelName} (port ${server.port})...`));
|
|
31
33
|
|
|
32
|
-
// 3.
|
|
34
|
+
// 3. Auto-rotate logs if they exceed 100MB
|
|
35
|
+
try {
|
|
36
|
+
const result = await autoRotateIfNeeded(server.stdoutPath, server.stderrPath, 100);
|
|
37
|
+
if (result.rotated) {
|
|
38
|
+
console.log(chalk.dim('Auto-rotated large log files:'));
|
|
39
|
+
for (const file of result.files) {
|
|
40
|
+
console.log(chalk.dim(` → ${file}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Non-fatal, just warn
|
|
45
|
+
console.log(chalk.yellow(`⚠️ Failed to rotate logs: ${(error as Error).message}`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 4. Ensure plist exists (recreate if missing)
|
|
33
49
|
try {
|
|
34
50
|
await launchctlManager.createPlist(server);
|
|
35
51
|
} catch (error) {
|
|
36
52
|
// May already exist, that's okay
|
|
37
53
|
}
|
|
38
54
|
|
|
39
|
-
//
|
|
55
|
+
// 5. Load service if needed
|
|
40
56
|
try {
|
|
41
57
|
await launchctlManager.loadService(server.plistPath);
|
|
42
58
|
} catch (error) {
|
|
43
59
|
// May already be loaded, that's okay
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
//
|
|
62
|
+
// 6. Start the service
|
|
47
63
|
try {
|
|
48
64
|
await launchctlManager.startService(server.label);
|
|
49
65
|
} catch (error) {
|
|
50
66
|
throw new Error(`Failed to start service: ${(error as Error).message}`);
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
//
|
|
69
|
+
// 7. Wait for startup
|
|
54
70
|
console.log(chalk.dim('Waiting for server to start...'));
|
|
55
71
|
const started = await launchctlManager.waitForServiceStart(server.label, 5000);
|
|
56
72
|
|
|
@@ -60,10 +76,22 @@ export async function startCommand(identifier: string): Promise<void> {
|
|
|
60
76
|
);
|
|
61
77
|
}
|
|
62
78
|
|
|
63
|
-
//
|
|
64
|
-
await statusChecker.updateServerStatus(server);
|
|
79
|
+
// 8. Update server status
|
|
80
|
+
let updatedServer = await statusChecker.updateServerStatus(server);
|
|
81
|
+
|
|
82
|
+
// 9. Parse Metal (GPU) memory allocation if not already captured
|
|
83
|
+
if (!updatedServer.metalMemoryMB) {
|
|
84
|
+
console.log(chalk.dim('Detecting Metal (GPU) memory allocation...'));
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 8000)); // 8 second delay
|
|
86
|
+
const metalMemoryMB = await parseMetalMemoryFromLog(updatedServer.stderrPath);
|
|
87
|
+
if (metalMemoryMB) {
|
|
88
|
+
updatedServer = { ...updatedServer, metalMemoryMB };
|
|
89
|
+
await stateManager.saveServerConfig(updatedServer);
|
|
90
|
+
console.log(chalk.dim(`Metal memory: ${metalMemoryMB.toFixed(0)} MB`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
65
93
|
|
|
66
|
-
//
|
|
94
|
+
// 10. Display success
|
|
67
95
|
console.log();
|
|
68
96
|
console.log(chalk.green('✅ Server started successfully!'));
|
|
69
97
|
console.log();
|
package/src/commands/stop.ts
CHANGED
|
@@ -18,11 +18,11 @@ export async function stopCommand(identifier: string): Promise<void> {
|
|
|
18
18
|
|
|
19
19
|
console.log(chalk.blue(`⏹️ Stopping ${server.modelName} (port ${server.port})...`));
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Unload the service (removes from launchd management - won't auto-restart)
|
|
22
22
|
try {
|
|
23
|
-
await launchctlManager.
|
|
23
|
+
await launchctlManager.unloadService(server.plistPath);
|
|
24
24
|
} catch (error) {
|
|
25
|
-
throw new Error(`Failed to
|
|
25
|
+
throw new Error(`Failed to unload service: ${(error as Error).message}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Wait for clean shutdown
|