@appkit/llamacpp-cli 1.4.1 → 1.6.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/MONITORING-ACCURACY-FIX.md +199 -0
  3. package/PER-PROCESS-METRICS.md +190 -0
  4. package/README.md +136 -1
  5. package/dist/cli.js +21 -4
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/create.d.ts.map +1 -1
  8. package/dist/commands/create.js +12 -3
  9. package/dist/commands/create.js.map +1 -1
  10. package/dist/commands/monitor.d.ts +2 -0
  11. package/dist/commands/monitor.d.ts.map +1 -0
  12. package/dist/commands/monitor.js +126 -0
  13. package/dist/commands/monitor.js.map +1 -0
  14. package/dist/commands/ps.d.ts +3 -1
  15. package/dist/commands/ps.d.ts.map +1 -1
  16. package/dist/commands/ps.js +75 -5
  17. package/dist/commands/ps.js.map +1 -1
  18. package/dist/commands/server-show.d.ts.map +1 -1
  19. package/dist/commands/server-show.js +10 -3
  20. package/dist/commands/server-show.js.map +1 -1
  21. package/dist/commands/start.d.ts.map +1 -1
  22. package/dist/commands/start.js +14 -2
  23. package/dist/commands/start.js.map +1 -1
  24. package/dist/lib/history-manager.d.ts +46 -0
  25. package/dist/lib/history-manager.d.ts.map +1 -0
  26. package/dist/lib/history-manager.js +157 -0
  27. package/dist/lib/history-manager.js.map +1 -0
  28. package/dist/lib/metrics-aggregator.d.ts +40 -0
  29. package/dist/lib/metrics-aggregator.d.ts.map +1 -0
  30. package/dist/lib/metrics-aggregator.js +211 -0
  31. package/dist/lib/metrics-aggregator.js.map +1 -0
  32. package/dist/lib/system-collector.d.ts +80 -0
  33. package/dist/lib/system-collector.d.ts.map +1 -0
  34. package/dist/lib/system-collector.js +311 -0
  35. package/dist/lib/system-collector.js.map +1 -0
  36. package/dist/tui/HistoricalMonitorApp.d.ts +5 -0
  37. package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -0
  38. package/dist/tui/HistoricalMonitorApp.js +490 -0
  39. package/dist/tui/HistoricalMonitorApp.js.map +1 -0
  40. package/dist/tui/MonitorApp.d.ts +4 -0
  41. package/dist/tui/MonitorApp.d.ts.map +1 -0
  42. package/dist/tui/MonitorApp.js +315 -0
  43. package/dist/tui/MonitorApp.js.map +1 -0
  44. package/dist/tui/MultiServerMonitorApp.d.ts +4 -0
  45. package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -0
  46. package/dist/tui/MultiServerMonitorApp.js +712 -0
  47. package/dist/tui/MultiServerMonitorApp.js.map +1 -0
  48. package/dist/types/history-types.d.ts +30 -0
  49. package/dist/types/history-types.d.ts.map +1 -0
  50. package/dist/types/history-types.js +11 -0
  51. package/dist/types/history-types.js.map +1 -0
  52. package/dist/types/monitor-types.d.ts +123 -0
  53. package/dist/types/monitor-types.d.ts.map +1 -0
  54. package/dist/types/monitor-types.js +3 -0
  55. package/dist/types/monitor-types.js.map +1 -0
  56. package/dist/types/server-config.d.ts +1 -0
  57. package/dist/types/server-config.d.ts.map +1 -1
  58. package/dist/types/server-config.js.map +1 -1
  59. package/dist/utils/downsample-utils.d.ts +35 -0
  60. package/dist/utils/downsample-utils.d.ts.map +1 -0
  61. package/dist/utils/downsample-utils.js +107 -0
  62. package/dist/utils/downsample-utils.js.map +1 -0
  63. package/dist/utils/file-utils.d.ts +6 -0
  64. package/dist/utils/file-utils.d.ts.map +1 -1
  65. package/dist/utils/file-utils.js +38 -0
  66. package/dist/utils/file-utils.js.map +1 -1
  67. package/dist/utils/process-utils.d.ts +35 -2
  68. package/dist/utils/process-utils.d.ts.map +1 -1
  69. package/dist/utils/process-utils.js +220 -25
  70. package/dist/utils/process-utils.js.map +1 -1
  71. package/docs/images/.gitkeep +1 -0
  72. package/package.json +5 -1
  73. package/src/cli.ts +21 -4
  74. package/src/commands/create.ts +14 -4
  75. package/src/commands/monitor.ts +110 -0
  76. package/src/commands/ps.ts +88 -5
  77. package/src/commands/server-show.ts +10 -3
  78. package/src/commands/start.ts +15 -2
  79. package/src/lib/history-manager.ts +172 -0
  80. package/src/lib/metrics-aggregator.ts +257 -0
  81. package/src/lib/system-collector.ts +315 -0
  82. package/src/tui/HistoricalMonitorApp.ts +548 -0
  83. package/src/tui/MonitorApp.ts +386 -0
  84. package/src/tui/MultiServerMonitorApp.ts +792 -0
  85. package/src/types/history-types.ts +39 -0
  86. package/src/types/monitor-types.ts +162 -0
  87. package/src/types/server-config.ts +1 -0
  88. package/src/utils/downsample-utils.ts +128 -0
  89. package/src/utils/file-utils.ts +40 -0
  90. package/src/utils/process-utils.ts +243 -25
  91. package/test-load.sh +100 -0
@@ -6,7 +6,11 @@ exports.execCommandFull = execCommandFull;
6
6
  exports.commandExists = commandExists;
7
7
  exports.isProcessRunning = isProcessRunning;
8
8
  exports.isPortInUse = isPortInUse;
9
+ exports.spawnAndReadOneLine = spawnAndReadOneLine;
10
+ exports.getBatchProcessMemory = getBatchProcessMemory;
9
11
  exports.getProcessMemory = getProcessMemory;
12
+ exports.getBatchProcessCpu = getBatchProcessCpu;
13
+ exports.getProcessCpu = getProcessCpu;
10
14
  const child_process_1 = require("child_process");
11
15
  const util_1 = require("util");
12
16
  exports.execAsync = (0, util_1.promisify)(child_process_1.exec);
@@ -65,37 +69,228 @@ async function isPortInUse(port) {
65
69
  }
66
70
  }
67
71
  /**
68
- * Get memory usage for a process in bytes
69
- * Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
72
+ * Spawn a streaming command, read one line, and kill it
73
+ * Useful for commands like 'macmon pipe' that stream indefinitely
74
+ * Ensures the process is killed to prevent leaks
75
+ */
76
+ async function spawnAndReadOneLine(command, args, timeoutMs = 2000) {
77
+ return new Promise((resolve) => {
78
+ const child = (0, child_process_1.spawn)(command, args, {
79
+ stdio: ['ignore', 'pipe', 'ignore'],
80
+ detached: false, // Keep in same process group for easier cleanup
81
+ });
82
+ let resolved = false;
83
+ let output = '';
84
+ const cleanup = () => {
85
+ try {
86
+ // Try SIGKILL immediately (SIGTERM may not work for macmon)
87
+ child.kill('SIGKILL');
88
+ }
89
+ catch {
90
+ // Process might already be dead
91
+ }
92
+ };
93
+ // Set timeout to kill process if it doesn't produce output
94
+ const timeout = setTimeout(() => {
95
+ if (!resolved) {
96
+ resolved = true;
97
+ cleanup();
98
+ resolve(null);
99
+ }
100
+ }, timeoutMs);
101
+ // Read stdout line by line
102
+ child.stdout?.on('data', (data) => {
103
+ if (resolved)
104
+ return;
105
+ output += data.toString();
106
+ // Check if we have a complete line
107
+ const newlineIndex = output.indexOf('\n');
108
+ if (newlineIndex !== -1) {
109
+ const line = output.substring(0, newlineIndex).trim();
110
+ if (line.length > 0) {
111
+ resolved = true;
112
+ clearTimeout(timeout);
113
+ cleanup();
114
+ resolve(line);
115
+ }
116
+ }
117
+ });
118
+ // Handle process errors
119
+ child.on('error', () => {
120
+ if (!resolved) {
121
+ resolved = true;
122
+ clearTimeout(timeout);
123
+ resolve(null);
124
+ }
125
+ });
126
+ // Handle process exit
127
+ child.on('exit', () => {
128
+ if (!resolved) {
129
+ resolved = true;
130
+ clearTimeout(timeout);
131
+ // Return partial output if we have any
132
+ const line = output.trim();
133
+ resolve(line.length > 0 ? line : null);
134
+ }
135
+ });
136
+ });
137
+ }
138
+ // Process memory cache to prevent spawning too many 'top' processes
139
+ // Cache per PID with 3-second TTL
140
+ const processMemoryCache = new Map();
141
+ const PROCESS_MEMORY_CACHE_TTL = 3000; // 3 seconds
142
+ /**
143
+ * Batch get memory usage for multiple processes in one top call
144
+ * Much more efficient than calling getProcessMemory() multiple times
145
+ * Returns Map<pid, bytes> for all requested PIDs
146
+ */
147
+ async function getBatchProcessMemory(pids) {
148
+ const result = new Map();
149
+ const now = Date.now();
150
+ // Check cache and collect PIDs that need fetching
151
+ const pidsToFetch = [];
152
+ for (const pid of pids) {
153
+ const cached = processMemoryCache.get(pid);
154
+ if (cached && (now - cached.timestamp) < PROCESS_MEMORY_CACHE_TTL) {
155
+ result.set(pid, cached.value);
156
+ }
157
+ else {
158
+ pidsToFetch.push(pid);
159
+ }
160
+ }
161
+ // If all PIDs were cached, return early
162
+ if (pidsToFetch.length === 0) {
163
+ return result;
164
+ }
165
+ try {
166
+ // Build top command with all PIDs: top -l 1 -pid X -pid Y -pid Z -stats pid,mem
167
+ const pidArgs = pidsToFetch.map(pid => `-pid ${pid}`).join(' ');
168
+ const output = await execCommand(`top -l 1 ${pidArgs} -stats pid,mem 2>/dev/null`);
169
+ // Parse output: each line is "PID MEM" (e.g., "1438 299M")
170
+ const lines = output.split('\n');
171
+ for (const line of lines) {
172
+ const match = line.trim().match(/^(\d+)\s+([\d.]+)([KMGT])\s*$/);
173
+ if (!match)
174
+ continue;
175
+ const pid = parseInt(match[1], 10);
176
+ const value = parseFloat(match[2]);
177
+ const unit = match[3];
178
+ // Convert to bytes
179
+ const multipliers = {
180
+ K: 1024,
181
+ M: 1024 * 1024,
182
+ G: 1024 * 1024 * 1024,
183
+ T: 1024 * 1024 * 1024 * 1024,
184
+ };
185
+ const bytes = Math.round(value * multipliers[unit]);
186
+ // Cache and store result
187
+ processMemoryCache.set(pid, { value: bytes, timestamp: now });
188
+ result.set(pid, bytes);
189
+ }
190
+ // For any PIDs that weren't in the output, cache null
191
+ for (const pid of pidsToFetch) {
192
+ if (!result.has(pid)) {
193
+ processMemoryCache.set(pid, { value: null, timestamp: now });
194
+ result.set(pid, null);
195
+ }
196
+ }
197
+ return result;
198
+ }
199
+ catch {
200
+ // On error, cache null for all requested PIDs
201
+ for (const pid of pidsToFetch) {
202
+ processMemoryCache.set(pid, { value: null, timestamp: now });
203
+ result.set(pid, null);
204
+ }
205
+ return result;
206
+ }
207
+ }
208
+ /**
209
+ * Get memory usage for a single process in bytes
210
+ * Uses 'top' on macOS which reports CPU memory only (NOT GPU/Metal memory)
70
211
  * Returns null if process not found or error occurs
212
+ * Caches results for 3 seconds to prevent spawning too many top processes
213
+ *
214
+ * Note: For llama-server processes with GPU offloading, use ServerConfig.metalMemoryMB
215
+ * to get GPU memory allocation (parsed from logs during server startup)
216
+ *
217
+ * Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
71
218
  */
72
219
  async function getProcessMemory(pid) {
220
+ const result = await getBatchProcessMemory([pid]);
221
+ return result.get(pid) ?? null;
222
+ }
223
+ // Process CPU cache to prevent spawning too many 'ps' processes
224
+ // Cache per PID with 3-second TTL
225
+ const processCpuCache = new Map();
226
+ const PROCESS_CPU_CACHE_TTL = 3000; // 3 seconds
227
+ /**
228
+ * Batch get CPU usage for multiple processes in one ps call
229
+ * Much more efficient than calling getProcessCpu() multiple times
230
+ * Returns Map<pid, percentage> for all requested PIDs
231
+ */
232
+ async function getBatchProcessCpu(pids) {
233
+ const result = new Map();
234
+ const now = Date.now();
235
+ // Check cache and collect PIDs that need fetching
236
+ const pidsToFetch = [];
237
+ for (const pid of pids) {
238
+ const cached = processCpuCache.get(pid);
239
+ if (cached && (now - cached.timestamp) < PROCESS_CPU_CACHE_TTL) {
240
+ result.set(pid, cached.value);
241
+ }
242
+ else {
243
+ pidsToFetch.push(pid);
244
+ }
245
+ }
246
+ // If all PIDs were cached, return early
247
+ if (pidsToFetch.length === 0) {
248
+ return result;
249
+ }
73
250
  try {
74
- // Use top with -l 1 (one sample) to get memory stats
75
- // MEM column shows resident memory including GPU memory on macOS
76
- const output = await execCommand(`top -l 1 -pid ${pid} -stats mem`);
77
- // Get the last non-empty line which contains the memory value
78
- const lines = output.split('\n').filter((line) => line.trim().length > 0);
79
- if (lines.length === 0)
80
- return null;
81
- const memStr = lines[lines.length - 1].trim();
82
- // Parse memory string (e.g., "10.5G", "512M", "1024K", "10G")
83
- const match = memStr.match(/^([\d.]+)([KMGT])$/);
84
- if (!match)
85
- return null;
86
- const value = parseFloat(match[1]);
87
- const unit = match[2];
88
- // Convert to bytes
89
- const multipliers = {
90
- K: 1024,
91
- M: 1024 * 1024,
92
- G: 1024 * 1024 * 1024,
93
- T: 1024 * 1024 * 1024 * 1024,
94
- };
95
- return Math.round(value * multipliers[unit]);
251
+ // Build ps command with all PIDs: ps -p X,Y,Z -o pid=,%cpu=
252
+ const pidList = pidsToFetch.join(',');
253
+ const output = await execCommand(`ps -p ${pidList} -o pid=,%cpu= 2>/dev/null`);
254
+ // Parse output: each line is "PID %CPU" (e.g., "1438 45.2")
255
+ const lines = output.split('\n');
256
+ for (const line of lines) {
257
+ const match = line.trim().match(/^(\d+)\s+([\d.]+)\s*$/);
258
+ if (!match)
259
+ continue;
260
+ const pid = parseInt(match[1], 10);
261
+ const cpuPercent = parseFloat(match[2]);
262
+ // Cache and store result
263
+ processCpuCache.set(pid, { value: cpuPercent, timestamp: now });
264
+ result.set(pid, cpuPercent);
265
+ }
266
+ // For any PIDs that weren't in the output, cache null (process not running)
267
+ for (const pid of pidsToFetch) {
268
+ if (!result.has(pid)) {
269
+ processCpuCache.set(pid, { value: null, timestamp: now });
270
+ result.set(pid, null);
271
+ }
272
+ }
273
+ return result;
96
274
  }
97
275
  catch {
98
- return null;
276
+ // On error, cache null for all requested PIDs
277
+ for (const pid of pidsToFetch) {
278
+ processCpuCache.set(pid, { value: null, timestamp: now });
279
+ result.set(pid, null);
280
+ }
281
+ return result;
99
282
  }
100
283
  }
284
+ /**
285
+ * Get CPU usage for a single process as percentage (0-100+)
286
+ * Uses 'ps -o %cpu' on macOS
287
+ * Returns null if process not found or error occurs
288
+ * Caches results for 3 seconds to prevent spawning too many ps processes
289
+ *
290
+ * Note: For multiple PIDs, use getBatchProcessCpu() instead - much more efficient
291
+ */
292
+ async function getProcessCpu(pid) {
293
+ const result = await getBatchProcessCpu([pid]);
294
+ return result.get(pid) ?? null;
295
+ }
101
296
  //# sourceMappingURL=process-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ # Screenshots directory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appkit/llamacpp-cli",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "CLI tool to manage local llama.cpp servers on macOS",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -40,11 +40,15 @@
40
40
  "url": "https://github.com/appkitstudio/llamacpp-cli/issues"
41
41
  },
42
42
  "dependencies": {
43
+ "asciichart": "^1.5.25",
44
+ "blessed": "^0.1.81",
43
45
  "chalk": "^4.1.2",
44
46
  "cli-table3": "^0.6.5",
45
47
  "commander": "^13.0.0"
46
48
  },
47
49
  "devDependencies": {
50
+ "@types/asciichart": "^1.5.8",
51
+ "@types/blessed": "^0.1.27",
48
52
  "@types/node": "^20.12.7",
49
53
  "commit-and-tag-version": "^12.6.1",
50
54
  "tsx": "^4.7.2",
package/src/cli.ts CHANGED
@@ -17,6 +17,7 @@ import { showCommand } from './commands/show';
17
17
  import { serverShowCommand } from './commands/server-show';
18
18
  import { serverConfigCommand } from './commands/config';
19
19
  import { configGlobalCommand } from './commands/config-global';
20
+ import { monitorCommand } from './commands/monitor';
20
21
  import packageJson from '../package.json';
21
22
 
22
23
  const program = new Command();
@@ -41,11 +42,12 @@ program
41
42
 
42
43
  // List running servers
43
44
  program
44
- .command('ps')
45
- .description('List all servers with status')
46
- .action(async () => {
45
+ .command('ps [identifier]')
46
+ .description('Interactive server monitoring dashboard')
47
+ .option('--table', 'Show static table instead of TUI (for scripting)')
48
+ .action(async (identifier?: string, options?: { table?: boolean }) => {
47
49
  try {
48
- await psCommand();
50
+ await psCommand(identifier, options);
49
51
  } catch (error) {
50
52
  console.error(chalk.red('❌ Error:'), (error as Error).message);
51
53
  process.exit(1);
@@ -268,5 +270,20 @@ server
268
270
  }
269
271
  });
270
272
 
273
+ // Monitor server (deprecated - redirects to ps)
274
+ server
275
+ .command('monitor [identifier]')
276
+ .description('Monitor server with real-time metrics TUI (deprecated: use "llamacpp ps" instead)')
277
+ .action(async (identifier?: string) => {
278
+ try {
279
+ console.log(chalk.yellow('⚠️ The "monitor" command is deprecated and will be removed in a future version.'));
280
+ console.log(chalk.dim(' Please use "llamacpp ps" instead for the same functionality.\n'));
281
+ await monitorCommand(identifier);
282
+ } catch (error) {
283
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
284
+ process.exit(1);
285
+ }
286
+ });
287
+
271
288
  // Parse arguments
272
289
  program.parse();
@@ -9,7 +9,7 @@ import { launchctlManager } from '../lib/launchctl-manager';
9
9
  import { statusChecker } from '../lib/status-checker';
10
10
  import { commandExists } from '../utils/process-utils';
11
11
  import { formatBytes } from '../utils/format-utils';
12
- import { ensureDir } from '../utils/file-utils';
12
+ import { ensureDir, parseMetalMemoryFromLog } from '../utils/file-utils';
13
13
  import { ensureModelsDirectory } from '../lib/models-dir-setup';
14
14
 
15
15
  interface CreateOptions {
@@ -159,12 +159,22 @@ export async function createCommand(model: string, options: CreateOptions): Prom
159
159
  }
160
160
 
161
161
  // 12. Update config with running status
162
- const updatedConfig = await statusChecker.updateServerStatus(config);
162
+ let updatedConfig = await statusChecker.updateServerStatus(config);
163
+
164
+ // 13. Parse Metal (GPU) memory allocation from logs
165
+ // Wait a few seconds for model to start loading (large models take time)
166
+ console.log(chalk.dim('Detecting Metal (GPU) memory allocation...'));
167
+ await new Promise(resolve => setTimeout(resolve, 8000)); // 8 second delay
168
+ const metalMemoryMB = await parseMetalMemoryFromLog(updatedConfig.stderrPath);
169
+ if (metalMemoryMB) {
170
+ updatedConfig = { ...updatedConfig, metalMemoryMB };
171
+ console.log(chalk.dim(`Metal memory: ${metalMemoryMB.toFixed(0)} MB`));
172
+ }
163
173
 
164
- // 13. Save server config
174
+ // 14. Save server config
165
175
  await stateManager.saveServerConfig(updatedConfig);
166
176
 
167
- // 14. Display success message
177
+ // 15. Display success message
168
178
  console.log();
169
179
  console.log(chalk.green('✅ Server created and started successfully!'));
170
180
  console.log();
@@ -0,0 +1,110 @@
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\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,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
- export async function psCommand(): Promise<void> {
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 memoryBytes = await getProcessMemory(server.pid);
59
- if (memoryBytes !== null) {
60
- memoryText = formatBytes(memoryBytes);
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
+ }
@@ -68,9 +68,16 @@ export async function serverShowCommand(identifier: string): Promise<void> {
68
68
  }
69
69
 
70
70
  if (updatedServer.pid) {
71
- const memoryBytes = await getProcessMemory(updatedServer.pid);
72
- if (memoryBytes !== null) {
73
- console.log(`${chalk.bold('Memory:')} ${formatBytes(memoryBytes)}`);
71
+ const cpuMemoryBytes = await getProcessMemory(updatedServer.pid);
72
+ if (cpuMemoryBytes !== null) {
73
+ const metalMemoryBytes = updatedServer.metalMemoryMB ? updatedServer.metalMemoryMB * 1024 * 1024 : 0;
74
+ const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
75
+
76
+ if (metalMemoryBytes > 0) {
77
+ console.log(`${chalk.bold('Memory:')} ${formatBytes(totalMemoryBytes)} (CPU: ${formatBytes(cpuMemoryBytes)}, GPU: ${formatBytes(metalMemoryBytes)})`);
78
+ } else {
79
+ console.log(`${chalk.bold('Memory:')} ${formatBytes(cpuMemoryBytes)} (CPU only)`);
80
+ }
74
81
  }
75
82
  }
76
83
  }