@appkit/llamacpp-cli 1.4.0 → 1.5.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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +87 -1
  3. package/dist/cli.js +14 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/monitor.d.ts +2 -0
  6. package/dist/commands/monitor.d.ts.map +1 -0
  7. package/dist/commands/monitor.js +76 -0
  8. package/dist/commands/monitor.js.map +1 -0
  9. package/dist/lib/metrics-aggregator.d.ts +39 -0
  10. package/dist/lib/metrics-aggregator.d.ts.map +1 -0
  11. package/dist/lib/metrics-aggregator.js +200 -0
  12. package/dist/lib/metrics-aggregator.js.map +1 -0
  13. package/dist/lib/system-collector.d.ts +75 -0
  14. package/dist/lib/system-collector.d.ts.map +1 -0
  15. package/dist/lib/system-collector.js +310 -0
  16. package/dist/lib/system-collector.js.map +1 -0
  17. package/dist/tui/MonitorApp.d.ts +4 -0
  18. package/dist/tui/MonitorApp.d.ts.map +1 -0
  19. package/dist/tui/MonitorApp.js +293 -0
  20. package/dist/tui/MonitorApp.js.map +1 -0
  21. package/dist/tui/MultiServerMonitorApp.d.ts +4 -0
  22. package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -0
  23. package/dist/tui/MultiServerMonitorApp.js +496 -0
  24. package/dist/tui/MultiServerMonitorApp.js.map +1 -0
  25. package/dist/tui/components/ErrorState.d.ts +8 -0
  26. package/dist/tui/components/ErrorState.d.ts.map +1 -0
  27. package/dist/tui/components/ErrorState.js +22 -0
  28. package/dist/tui/components/ErrorState.js.map +1 -0
  29. package/dist/tui/components/LoadingState.d.ts +8 -0
  30. package/dist/tui/components/LoadingState.d.ts.map +1 -0
  31. package/dist/tui/components/LoadingState.js +21 -0
  32. package/dist/tui/components/LoadingState.js.map +1 -0
  33. package/dist/types/monitor-types.d.ts +122 -0
  34. package/dist/types/monitor-types.d.ts.map +1 -0
  35. package/dist/types/monitor-types.js +3 -0
  36. package/dist/types/monitor-types.js.map +1 -0
  37. package/dist/utils/process-utils.d.ts +16 -1
  38. package/dist/utils/process-utils.d.ts.map +1 -1
  39. package/dist/utils/process-utils.js +144 -27
  40. package/dist/utils/process-utils.js.map +1 -1
  41. package/package.json +4 -2
  42. package/src/cli.ts +14 -0
  43. package/src/commands/monitor.ts +90 -0
  44. package/src/lib/metrics-aggregator.ts +244 -0
  45. package/src/lib/system-collector.ts +312 -0
  46. package/src/tui/MonitorApp.ts +361 -0
  47. package/src/tui/MultiServerMonitorApp.ts +547 -0
  48. package/src/types/monitor-types.ts +161 -0
  49. package/src/utils/process-utils.ts +160 -26
@@ -0,0 +1,122 @@
1
+ import { ServerConfig } from './server-config.js';
2
+ export interface HealthResponse {
3
+ status: string;
4
+ error?: string;
5
+ }
6
+ export interface PropsResponse {
7
+ default_generation_settings: {
8
+ n_ctx: number;
9
+ n_predict: number;
10
+ model: string;
11
+ seed: number;
12
+ temperature: number;
13
+ top_k: number;
14
+ top_p: number;
15
+ min_p: number;
16
+ n_keep: number;
17
+ stream: boolean;
18
+ };
19
+ total_slots: number;
20
+ model_loaded: boolean;
21
+ model_path: string;
22
+ model_alias?: string;
23
+ }
24
+ export interface SlotInfo {
25
+ id: number;
26
+ state: 'idle' | 'processing';
27
+ task_id?: number;
28
+ prompt?: string;
29
+ n_prompt_tokens?: number;
30
+ n_decoded?: number;
31
+ n_ctx: number;
32
+ truncated?: boolean;
33
+ stopped_eos?: boolean;
34
+ stopped_word?: boolean;
35
+ stopped_limit?: boolean;
36
+ stopping_word?: string;
37
+ tokens_predicted?: number;
38
+ tokens_evaluated?: number;
39
+ generation_settings?: {
40
+ n_ctx: number;
41
+ n_predict: number;
42
+ seed: number;
43
+ temperature: number;
44
+ top_k: number;
45
+ top_p: number;
46
+ };
47
+ prompt_tokens_processed?: number;
48
+ t_prompt_processing?: number;
49
+ t_token_generation?: number;
50
+ timings?: {
51
+ prompt_n: number;
52
+ prompt_ms: number;
53
+ prompt_per_token_ms: number;
54
+ prompt_per_second: number;
55
+ predicted_n: number;
56
+ predicted_ms: number;
57
+ predicted_per_token_ms: number;
58
+ predicted_per_second: number;
59
+ };
60
+ }
61
+ export interface SlotsResponse {
62
+ slots: SlotInfo[];
63
+ }
64
+ export interface SystemMetrics {
65
+ gpuUsage?: number;
66
+ cpuUsage?: number;
67
+ cpuCores?: number;
68
+ aneUsage?: number;
69
+ temperature?: number;
70
+ memoryUsed: number;
71
+ memoryTotal: number;
72
+ swapUsed?: number;
73
+ processMemory?: number;
74
+ timestamp: number;
75
+ source: 'macmon' | 'vm_stat' | 'none';
76
+ warnings?: string[];
77
+ }
78
+ export interface ServerMetrics {
79
+ server: ServerConfig;
80
+ healthy: boolean;
81
+ uptime?: string;
82
+ error?: string;
83
+ modelLoaded: boolean;
84
+ modelName: string;
85
+ contextSize: number;
86
+ totalSlots: number;
87
+ activeSlots: number;
88
+ idleSlots: number;
89
+ slots: SlotInfo[];
90
+ avgPromptSpeed?: number;
91
+ avgGenerateSpeed?: number;
92
+ requestsPerMinute?: number;
93
+ avgLatency?: number;
94
+ cacheHitRate?: number;
95
+ processMemory?: number;
96
+ timestamp: number;
97
+ stale: boolean;
98
+ }
99
+ export interface MonitorData {
100
+ server: ServerMetrics;
101
+ system?: SystemMetrics;
102
+ lastUpdated: Date;
103
+ updateInterval: number;
104
+ consecutiveFailures: number;
105
+ }
106
+ export interface ErrorState {
107
+ error: string;
108
+ canRetry: boolean;
109
+ suggestions?: string[];
110
+ }
111
+ export interface LoadingState {
112
+ message: string;
113
+ progress?: number;
114
+ }
115
+ export interface CollectionResult<T> {
116
+ success: boolean;
117
+ data: T | null;
118
+ error?: string;
119
+ warnings?: string[];
120
+ stale?: boolean;
121
+ }
122
+ //# sourceMappingURL=monitor-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-types.d.ts","sourceRoot":"","sources":["../../src/types/monitor-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=monitor-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor-types.js","sourceRoot":"","sources":["../../src/types/monitor-types.ts"],"names":[],"mappings":""}
@@ -25,9 +25,24 @@ export declare function isProcessRunning(pid: number): Promise<boolean>;
25
25
  */
26
26
  export declare function isPortInUse(port: number): Promise<boolean>;
27
27
  /**
28
- * Get memory usage for a process in bytes
28
+ * Spawn a streaming command, read one line, and kill it
29
+ * Useful for commands like 'macmon pipe' that stream indefinitely
30
+ * Ensures the process is killed to prevent leaks
31
+ */
32
+ export declare function spawnAndReadOneLine(command: string, args: string[], timeoutMs?: number): Promise<string | null>;
33
+ /**
34
+ * Batch get memory usage for multiple processes in one top call
35
+ * Much more efficient than calling getProcessMemory() multiple times
36
+ * Returns Map<pid, bytes> for all requested PIDs
37
+ */
38
+ export declare function getBatchProcessMemory(pids: number[]): Promise<Map<number, number | null>>;
39
+ /**
40
+ * Get memory usage for a single process in bytes
29
41
  * Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
30
42
  * Returns null if process not found or error occurs
43
+ * Caches results for 3 seconds to prevent spawning too many top processes
44
+ *
45
+ * Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
31
46
  */
32
47
  export declare function getProcessMemory(pid: number): Promise<number | null>;
33
48
  //# sourceMappingURL=process-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-utils.d.ts","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAGrC,eAAO,MAAM,SAAS,2BAAkB,CAAC;AAEzC;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAMlG;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhE;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+B1E"}
1
+ {"version":3,"file":"process-utils.d.ts","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAS,MAAM,eAAe,CAAC;AAG5C,eAAO,MAAM,SAAS,2BAAkB,CAAC;AAEzC;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAMlG;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOhE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,GAAE,MAAa,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqExB;AAOD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAmE/F;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG1E"}
@@ -6,6 +6,8 @@ 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;
10
12
  const child_process_1 = require("child_process");
11
13
  const util_1 = require("util");
@@ -65,37 +67,152 @@ async function isPortInUse(port) {
65
67
  }
66
68
  }
67
69
  /**
68
- * Get memory usage for a process in bytes
69
- * Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
70
- * Returns null if process not found or error occurs
70
+ * Spawn a streaming command, read one line, and kill it
71
+ * Useful for commands like 'macmon pipe' that stream indefinitely
72
+ * Ensures the process is killed to prevent leaks
71
73
  */
72
- async function getProcessMemory(pid) {
73
- 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,
74
+ async function spawnAndReadOneLine(command, args, timeoutMs = 2000) {
75
+ return new Promise((resolve) => {
76
+ const child = (0, child_process_1.spawn)(command, args, {
77
+ stdio: ['ignore', 'pipe', 'ignore'],
78
+ detached: false, // Keep in same process group for easier cleanup
79
+ });
80
+ let resolved = false;
81
+ let output = '';
82
+ const cleanup = () => {
83
+ try {
84
+ // Try SIGKILL immediately (SIGTERM may not work for macmon)
85
+ child.kill('SIGKILL');
86
+ }
87
+ catch {
88
+ // Process might already be dead
89
+ }
94
90
  };
95
- return Math.round(value * multipliers[unit]);
91
+ // Set timeout to kill process if it doesn't produce output
92
+ const timeout = setTimeout(() => {
93
+ if (!resolved) {
94
+ resolved = true;
95
+ cleanup();
96
+ resolve(null);
97
+ }
98
+ }, timeoutMs);
99
+ // Read stdout line by line
100
+ child.stdout?.on('data', (data) => {
101
+ if (resolved)
102
+ return;
103
+ output += data.toString();
104
+ // Check if we have a complete line
105
+ const newlineIndex = output.indexOf('\n');
106
+ if (newlineIndex !== -1) {
107
+ const line = output.substring(0, newlineIndex).trim();
108
+ if (line.length > 0) {
109
+ resolved = true;
110
+ clearTimeout(timeout);
111
+ cleanup();
112
+ resolve(line);
113
+ }
114
+ }
115
+ });
116
+ // Handle process errors
117
+ child.on('error', () => {
118
+ if (!resolved) {
119
+ resolved = true;
120
+ clearTimeout(timeout);
121
+ resolve(null);
122
+ }
123
+ });
124
+ // Handle process exit
125
+ child.on('exit', () => {
126
+ if (!resolved) {
127
+ resolved = true;
128
+ clearTimeout(timeout);
129
+ // Return partial output if we have any
130
+ const line = output.trim();
131
+ resolve(line.length > 0 ? line : null);
132
+ }
133
+ });
134
+ });
135
+ }
136
+ // Process memory cache to prevent spawning too many 'top' processes
137
+ // Cache per PID with 3-second TTL
138
+ const processMemoryCache = new Map();
139
+ const PROCESS_MEMORY_CACHE_TTL = 3000; // 3 seconds
140
+ /**
141
+ * Batch get memory usage for multiple processes in one top call
142
+ * Much more efficient than calling getProcessMemory() multiple times
143
+ * Returns Map<pid, bytes> for all requested PIDs
144
+ */
145
+ async function getBatchProcessMemory(pids) {
146
+ const result = new Map();
147
+ const now = Date.now();
148
+ // Check cache and collect PIDs that need fetching
149
+ const pidsToFetch = [];
150
+ for (const pid of pids) {
151
+ const cached = processMemoryCache.get(pid);
152
+ if (cached && (now - cached.timestamp) < PROCESS_MEMORY_CACHE_TTL) {
153
+ result.set(pid, cached.value);
154
+ }
155
+ else {
156
+ pidsToFetch.push(pid);
157
+ }
158
+ }
159
+ // If all PIDs were cached, return early
160
+ if (pidsToFetch.length === 0) {
161
+ return result;
162
+ }
163
+ try {
164
+ // Build top command with all PIDs: top -l 1 -pid X -pid Y -pid Z -stats pid,mem
165
+ const pidArgs = pidsToFetch.map(pid => `-pid ${pid}`).join(' ');
166
+ const output = await execCommand(`top -l 1 ${pidArgs} -stats pid,mem 2>/dev/null`);
167
+ // Parse output: each line is "PID MEM" (e.g., "1438 299M")
168
+ const lines = output.split('\n');
169
+ for (const line of lines) {
170
+ const match = line.trim().match(/^(\d+)\s+([\d.]+)([KMGT])\s*$/);
171
+ if (!match)
172
+ continue;
173
+ const pid = parseInt(match[1], 10);
174
+ const value = parseFloat(match[2]);
175
+ const unit = match[3];
176
+ // Convert to bytes
177
+ const multipliers = {
178
+ K: 1024,
179
+ M: 1024 * 1024,
180
+ G: 1024 * 1024 * 1024,
181
+ T: 1024 * 1024 * 1024 * 1024,
182
+ };
183
+ const bytes = Math.round(value * multipliers[unit]);
184
+ // Cache and store result
185
+ processMemoryCache.set(pid, { value: bytes, timestamp: now });
186
+ result.set(pid, bytes);
187
+ }
188
+ // For any PIDs that weren't in the output, cache null
189
+ for (const pid of pidsToFetch) {
190
+ if (!result.has(pid)) {
191
+ processMemoryCache.set(pid, { value: null, timestamp: now });
192
+ result.set(pid, null);
193
+ }
194
+ }
195
+ return result;
96
196
  }
97
197
  catch {
98
- return null;
198
+ // On error, cache null for all requested PIDs
199
+ for (const pid of pidsToFetch) {
200
+ processMemoryCache.set(pid, { value: null, timestamp: now });
201
+ result.set(pid, null);
202
+ }
203
+ return result;
99
204
  }
100
205
  }
206
+ /**
207
+ * Get memory usage for a single process in bytes
208
+ * Uses 'top' on macOS which includes GPU/Metal memory (more accurate for llama-server)
209
+ * Returns null if process not found or error occurs
210
+ * Caches results for 3 seconds to prevent spawning too many top processes
211
+ *
212
+ * Note: For multiple PIDs, use getBatchProcessMemory() instead - much more efficient
213
+ */
214
+ async function getProcessMemory(pid) {
215
+ const result = await getBatchProcessMemory([pid]);
216
+ return result.get(pid) ?? null;
217
+ }
101
218
  //# 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":";;;AASA,kCAGC;AAKD,0CAMC;AAKD,sCAOC;AAKD,4CAOC;AAKD,kCAOC;AAOD,kDAyEC;AAYD,sDAmEC;AAUD,4CAGC;AAvOD,iDAA4C;AAC5C,+BAAiC;AAEpB,QAAA,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAEzC;;;GAGG;AACI,KAAK,UAAU,WAAW,CAAC,OAAe;IAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAA,iBAAS,EAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAA,iBAAS,EAAC,OAAO,CAAC,CAAC;IACpD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KACtB,CAAC;AACJ,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,SAAS,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,SAAS,GAAG,EAAE,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,cAAc,IAAI,kBAAkB,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,mBAAmB,CACvC,OAAe,EACf,IAAc,EACd,YAAoB,IAAI;IAExB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,OAAO,EAAE,IAAI,EAAE;YACjC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,QAAQ,EAAE,KAAK,EAAE,gDAAgD;SAClE,CAAC,CAAC;QAEH,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,IAAI,CAAC;gBACH,4DAA4D;gBAC5D,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACxB,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;QACH,CAAC,CAAC;QAEF,2DAA2D;QAC3D,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,2BAA2B;QAC3B,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAChC,IAAI,QAAQ;gBAAE,OAAO;YAErB,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAE1B,mCAAmC;YACnC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;gBAEtD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACpB,QAAQ,GAAG,IAAI,CAAC;oBAChB,YAAY,CAAC,OAAO,CAAC,CAAC;oBACtB,OAAO,EAAE,CAAC;oBACV,OAAO,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,wBAAwB;QACxB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAC;gBAChB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,sBAAsB;QACtB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAC;gBAChB,YAAY,CAAC,OAAO,CAAC,CAAC;gBAEtB,uCAAuC;gBACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC3B,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACzC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oEAAoE;AACpE,kCAAkC;AAClC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAuD,CAAC;AAC1F,MAAM,wBAAwB,GAAG,IAAI,CAAC,CAAC,YAAY;AAEnD;;;;GAIG;AACI,KAAK,UAAU,qBAAqB,CAAC,IAAc;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,kDAAkD;IAClD,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,MAAM,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,wBAAwB,EAAE,CAAC;YAClE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,CAAC;QACH,gFAAgF;QAChF,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,YAAY,OAAO,6BAA6B,CAAC,CAAC;QAEnF,6DAA6D;QAC7D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACjE,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACnC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAEtB,mBAAmB;YACnB,MAAM,WAAW,GAA8B;gBAC7C,CAAC,EAAE,IAAI;gBACP,CAAC,EAAE,IAAI,GAAG,IAAI;gBACd,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI;gBACrB,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;aAC7B,CAAC;YAEF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;YAEpD,yBAAyB;YACzB,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QAED,sDAAsD;QACtD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC7D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;QAC9C,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,kBAAkB,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7D,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AACjC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appkit/llamacpp-cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.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,13 @@
40
40
  "url": "https://github.com/appkitstudio/llamacpp-cli/issues"
41
41
  },
42
42
  "dependencies": {
43
- "chalk": "^5.3.0",
43
+ "blessed": "^0.1.81",
44
+ "chalk": "^4.1.2",
44
45
  "cli-table3": "^0.6.5",
45
46
  "commander": "^13.0.0"
46
47
  },
47
48
  "devDependencies": {
49
+ "@types/blessed": "^0.1.27",
48
50
  "@types/node": "^20.12.7",
49
51
  "commit-and-tag-version": "^12.6.1",
50
52
  "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();
@@ -268,5 +269,18 @@ server
268
269
  }
269
270
  });
270
271
 
272
+ // Monitor server
273
+ server
274
+ .command('monitor [identifier]')
275
+ .description('Monitor server with real-time metrics TUI')
276
+ .action(async (identifier?: string) => {
277
+ try {
278
+ await monitorCommand(identifier);
279
+ } catch (error) {
280
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
281
+ process.exit(1);
282
+ }
283
+ });
284
+
271
285
  // Parse arguments
272
286
  program.parse();
@@ -0,0 +1,90 @@
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
+ // Check if server is running
40
+ if (server.status !== 'running') {
41
+ screen.destroy();
42
+ throw new Error(
43
+ `Server ${server.modelName} is not running.\n\n` +
44
+ `Start it first: llamacpp server start ${server.id}`
45
+ );
46
+ }
47
+
48
+ // Launch single-server TUI
49
+ await createMonitorUI(screen, server);
50
+ } else if (allServers.length === 1) {
51
+ // Only one server - single server mode
52
+ const server = allServers[0];
53
+
54
+ // Check if server is running
55
+ if (server.status !== 'running') {
56
+ screen.destroy();
57
+ throw new Error(
58
+ `Server ${server.modelName} is not running.\n\n` +
59
+ `Start it first: llamacpp server start ${server.id}`
60
+ );
61
+ }
62
+
63
+ // Launch single-server TUI
64
+ await createMonitorUI(screen, server);
65
+ } else {
66
+ // Multiple servers - multi-server mode
67
+ // Filter to only running servers for monitoring
68
+ const runningServers = allServers.filter(s => s.status === 'running');
69
+
70
+ if (runningServers.length === 0) {
71
+ screen.destroy();
72
+ throw new Error(
73
+ `No servers are currently running.\n\n` +
74
+ `Start a server first:\n` +
75
+ allServers
76
+ .map((s) => ` llamacpp server start ${s.id} # ${s.modelName}`)
77
+ .join('\n')
78
+ );
79
+ }
80
+
81
+ // Launch multi-server TUI
82
+ await createMultiServerMonitorUI(screen, allServers);
83
+ }
84
+
85
+ // Render the screen
86
+ screen.render();
87
+
88
+ // Note: TUI functions handle their own key events and exit directly
89
+ // The process will stay alive until user presses Q/Ctrl+C
90
+ }