@appkit/llamacpp-cli 1.9.0 → 1.10.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.
- package/CHANGELOG.md +28 -0
- package/README.md +171 -42
- package/dist/cli.js +75 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts +9 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +83 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/monitor.js +1 -1
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/ps.d.ts +1 -3
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +36 -115
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/router/config.d.ts +1 -0
- package/dist/commands/router/config.d.ts.map +1 -1
- package/dist/commands/router/config.js +7 -2
- package/dist/commands/router/config.js.map +1 -1
- package/dist/commands/router/logs.d.ts +12 -0
- package/dist/commands/router/logs.d.ts.map +1 -0
- package/dist/commands/router/logs.js +238 -0
- package/dist/commands/router/logs.js.map +1 -0
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +27 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/lib/completion.d.ts +5 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +195 -0
- package/dist/lib/completion.js.map +1 -0
- package/dist/lib/model-downloader.d.ts +5 -1
- package/dist/lib/model-downloader.d.ts.map +1 -1
- package/dist/lib/model-downloader.js +53 -20
- package/dist/lib/model-downloader.js.map +1 -1
- package/dist/lib/router-logger.d.ts +61 -0
- package/dist/lib/router-logger.d.ts.map +1 -0
- package/dist/lib/router-logger.js +200 -0
- package/dist/lib/router-logger.js.map +1 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +1 -0
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts +9 -0
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +169 -57
- package/dist/lib/router-server.js.map +1 -1
- package/dist/tui/ConfigApp.d.ts +7 -0
- package/dist/tui/ConfigApp.d.ts.map +1 -0
- package/dist/tui/ConfigApp.js +1002 -0
- package/dist/tui/ConfigApp.js.map +1 -0
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
- package/dist/tui/HistoricalMonitorApp.js +85 -49
- package/dist/tui/HistoricalMonitorApp.js.map +1 -1
- package/dist/tui/ModelsApp.d.ts +7 -0
- package/dist/tui/ModelsApp.d.ts.map +1 -0
- package/dist/tui/ModelsApp.js +362 -0
- package/dist/tui/ModelsApp.js.map +1 -0
- package/dist/tui/MultiServerMonitorApp.d.ts +6 -1
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
- package/dist/tui/MultiServerMonitorApp.js +1038 -122
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/tui/RootNavigator.d.ts +7 -0
- package/dist/tui/RootNavigator.d.ts.map +1 -0
- package/dist/tui/RootNavigator.js +55 -0
- package/dist/tui/RootNavigator.js.map +1 -0
- package/dist/tui/SearchApp.d.ts +6 -0
- package/dist/tui/SearchApp.d.ts.map +1 -0
- package/dist/tui/SearchApp.js +451 -0
- package/dist/tui/SearchApp.js.map +1 -0
- package/dist/tui/SplashScreen.d.ts +16 -0
- package/dist/tui/SplashScreen.d.ts.map +1 -0
- package/dist/tui/SplashScreen.js +129 -0
- package/dist/tui/SplashScreen.js.map +1 -0
- package/dist/types/router-config.d.ts +1 -0
- package/dist/types/router-config.d.ts.map +1 -1
- package/dist/utils/log-parser.d.ts +14 -1
- package/dist/utils/log-parser.d.ts.map +1 -1
- package/dist/utils/log-parser.js +57 -26
- package/dist/utils/log-parser.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +41 -10
- package/src/commands/monitor.ts +1 -1
- package/src/commands/ps.ts +44 -133
- package/src/commands/router/config.ts +9 -2
- package/src/commands/router/logs.ts +256 -0
- package/src/commands/tui.ts +25 -0
- package/src/lib/model-downloader.ts +57 -20
- package/src/lib/router-logger.ts +201 -0
- package/src/lib/router-manager.ts +1 -0
- package/src/lib/router-server.ts +193 -62
- package/src/tui/ConfigApp.ts +1085 -0
- package/src/tui/HistoricalMonitorApp.ts +88 -49
- package/src/tui/ModelsApp.ts +368 -0
- package/src/tui/MultiServerMonitorApp.ts +1163 -122
- package/src/tui/RootNavigator.ts +74 -0
- package/src/tui/SearchApp.ts +511 -0
- package/src/tui/SplashScreen.ts +149 -0
- package/src/types/router-config.ts +1 -0
- package/src/utils/log-parser.ts +61 -25
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import blessed from "blessed";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
// Get version from package.json at runtime
|
|
6
|
+
function getVersion(): string {
|
|
7
|
+
try {
|
|
8
|
+
// Try to find package.json relative to this file's location
|
|
9
|
+
// Works both in src/ (dev) and dist/ (production)
|
|
10
|
+
const possiblePaths = [
|
|
11
|
+
join(__dirname, "../../package.json"), // From dist/tui/
|
|
12
|
+
join(__dirname, "../../../package.json"), // Fallback
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
for (const pkgPath of possiblePaths) {
|
|
16
|
+
try {
|
|
17
|
+
const content = readFileSync(pkgPath, "utf-8");
|
|
18
|
+
const pkg = JSON.parse(content);
|
|
19
|
+
if (pkg.version) return pkg.version;
|
|
20
|
+
} catch {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return "1.0.0";
|
|
25
|
+
} catch {
|
|
26
|
+
return "1.0.0";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ASCII art logo for llama.cpp
|
|
31
|
+
const LOGO = `
|
|
32
|
+
{cyan-fg} ██╗ ██╗ █████╗ ███╗ ███╗ █████╗ ██████╗██████╗ ██████╗{/cyan-fg}
|
|
33
|
+
{cyan-fg}██║ ██║ ██╔══██╗████╗ ████║██╔══██╗ ██╔════╝██╔══██╗██╔══██╗{/cyan-fg}
|
|
34
|
+
{cyan-fg}██║ ██║ ███████║██╔████╔██║███████║ ██║ ██████╔╝██████╔╝{/cyan-fg}
|
|
35
|
+
{cyan-fg}██║ ██║ ██╔══██║██║╚██╔╝██║██╔══██║ ██║ ██╔═══╝ ██╔═══╝{/cyan-fg}
|
|
36
|
+
{cyan-fg}███████╗███████╗██║ ██║██║ ╚═╝ ██║██║ ██║ ╚██████╗██║ ██║{/cyan-fg}
|
|
37
|
+
{cyan-fg}╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝{/cyan-fg}
|
|
38
|
+
`.trim();
|
|
39
|
+
|
|
40
|
+
export interface SplashCallbacks {
|
|
41
|
+
onLoadConfigs: () => Promise<void>;
|
|
42
|
+
onCheckServices: () => Promise<void>;
|
|
43
|
+
onInitMetrics: () => Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SplashScreenControls {
|
|
47
|
+
updateStatus: (line: number, text: string, done?: boolean) => void;
|
|
48
|
+
complete: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates and displays the splash screen with loading status
|
|
53
|
+
* Returns a cleanup function to remove the splash when the main UI is ready
|
|
54
|
+
*/
|
|
55
|
+
export async function createSplashScreen(
|
|
56
|
+
screen: blessed.Widgets.Screen,
|
|
57
|
+
callbacks: SplashCallbacks,
|
|
58
|
+
): Promise<() => void> {
|
|
59
|
+
const version = getVersion();
|
|
60
|
+
|
|
61
|
+
// Create top-left aligned container
|
|
62
|
+
const container = blessed.box({
|
|
63
|
+
top: 0,
|
|
64
|
+
left: 0,
|
|
65
|
+
width: 75,
|
|
66
|
+
height: 19,
|
|
67
|
+
tags: true,
|
|
68
|
+
});
|
|
69
|
+
screen.append(container);
|
|
70
|
+
|
|
71
|
+
// Logo box
|
|
72
|
+
const logoBox = blessed.box({
|
|
73
|
+
parent: container,
|
|
74
|
+
top: 0,
|
|
75
|
+
left: 0,
|
|
76
|
+
width: 73,
|
|
77
|
+
height: 7,
|
|
78
|
+
tags: true,
|
|
79
|
+
content: LOGO,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Info box with border
|
|
83
|
+
const infoBox = blessed.box({
|
|
84
|
+
parent: container,
|
|
85
|
+
top: 7,
|
|
86
|
+
left: 0,
|
|
87
|
+
width: 73,
|
|
88
|
+
height: 10,
|
|
89
|
+
tags: true,
|
|
90
|
+
border: {
|
|
91
|
+
type: "line",
|
|
92
|
+
},
|
|
93
|
+
style: {
|
|
94
|
+
border: {
|
|
95
|
+
fg: "blue",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Status lines (inside the info box)
|
|
101
|
+
const statusLines = [
|
|
102
|
+
"{bold}Local LLM Server Manager{/bold} v" +
|
|
103
|
+
version,
|
|
104
|
+
"",
|
|
105
|
+
"{gray-fg}>{/gray-fg} Loading server configurations...",
|
|
106
|
+
"{gray-fg}>{/gray-fg} Checking launchctl services...",
|
|
107
|
+
"{gray-fg}>{/gray-fg} Initializing metrics collectors...",
|
|
108
|
+
"{gray-fg}>{/gray-fg} Loading UI...",
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
function updateDisplay() {
|
|
112
|
+
infoBox.setContent(statusLines.join("\n"));
|
|
113
|
+
screen.render();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function updateStatus(line: number, text: string, done: boolean = false) {
|
|
117
|
+
const prefix = done ? "{green-fg}✓{/green-fg}" : "{yellow-fg}>{/yellow-fg}";
|
|
118
|
+
statusLines[line + 2] = `${prefix} ${text}`;
|
|
119
|
+
updateDisplay();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Initial render
|
|
123
|
+
updateDisplay();
|
|
124
|
+
|
|
125
|
+
// Run loading sequence
|
|
126
|
+
// Step 1: Load configs
|
|
127
|
+
updateStatus(0, "Loading server configurations...", false);
|
|
128
|
+
await callbacks.onLoadConfigs();
|
|
129
|
+
updateStatus(0, "Server configurations loaded", true);
|
|
130
|
+
|
|
131
|
+
// Step 2: Check services
|
|
132
|
+
updateStatus(1, "Checking launchctl services...", false);
|
|
133
|
+
await callbacks.onCheckServices();
|
|
134
|
+
updateStatus(1, "Launchctl services checked", true);
|
|
135
|
+
|
|
136
|
+
// Step 3: Init metrics
|
|
137
|
+
updateStatus(2, "Initializing metrics collectors...", false);
|
|
138
|
+
await callbacks.onInitMetrics();
|
|
139
|
+
updateStatus(2, "Metrics collectors ready", true);
|
|
140
|
+
|
|
141
|
+
// Step 4: Loading UI (stays active until cleanup is called)
|
|
142
|
+
updateStatus(3, "Loading UI...", false);
|
|
143
|
+
|
|
144
|
+
// Return cleanup function - caller decides when to remove splash
|
|
145
|
+
return () => {
|
|
146
|
+
screen.remove(container);
|
|
147
|
+
screen.render();
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -21,4 +21,5 @@ export interface RouterConfig {
|
|
|
21
21
|
// Router settings
|
|
22
22
|
healthCheckInterval: number; // ms between health checks (default: 5000)
|
|
23
23
|
requestTimeout: number; // ms for backend requests (default: 120000)
|
|
24
|
+
verbose: boolean; // Enable verbose logging to file (default: false)
|
|
24
25
|
}
|
package/src/utils/log-parser.ts
CHANGED
|
@@ -18,13 +18,27 @@ export class LogParser {
|
|
|
18
18
|
private buffer: string[] = [];
|
|
19
19
|
private isBuffering = false;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Check if line is a request status line (contains method/endpoint/status, no JSON)
|
|
23
|
+
* Handles both old and new formats:
|
|
24
|
+
* - Old: log_server_r: request: POST /v1/chat/completions 127.0.0.1 200
|
|
25
|
+
* - New: log_server_r: done request: POST /v1/messages 172.16.0.114 200
|
|
26
|
+
*/
|
|
27
|
+
private isRequestStatusLine(line: string): boolean {
|
|
28
|
+
return (
|
|
29
|
+
(line.includes('log_server_r: request:') || line.includes('log_server_r: done request:')) &&
|
|
30
|
+
!line.includes('{') &&
|
|
31
|
+
/(?:done )?request: (POST|GET|PUT|DELETE)/.test(line)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/**
|
|
22
36
|
* Process log lines and output compact format
|
|
23
37
|
*/
|
|
24
38
|
processLine(line: string, callback: (compactLine: string) => void): void {
|
|
25
|
-
// Check if this is a
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
39
|
+
// Check if this is a request status line (no JSON, has method/endpoint/status)
|
|
40
|
+
// Handles both old format (request:) and new format (done request:)
|
|
41
|
+
if (this.isRequestStatusLine(line)) {
|
|
28
42
|
// Check if this is the start of verbose format (status line before JSON)
|
|
29
43
|
// or a simple single-line log
|
|
30
44
|
if (this.isBuffering) {
|
|
@@ -79,12 +93,15 @@ export class LogParser {
|
|
|
79
93
|
|
|
80
94
|
/**
|
|
81
95
|
* Parse simple single-line format (non-verbose mode)
|
|
82
|
-
*
|
|
96
|
+
* Handles both old and new formats:
|
|
97
|
+
* - Old: srv log_server_r: request: POST /v1/chat/completions 127.0.0.1 200
|
|
98
|
+
* - New: srv log_server_r: done request: POST /v1/messages 172.16.0.114 200
|
|
83
99
|
*/
|
|
84
100
|
private parseSimpleFormat(line: string): string | null {
|
|
85
101
|
try {
|
|
86
102
|
const timestamp = this.extractTimestamp(line);
|
|
87
|
-
|
|
103
|
+
// Match both "request:" and "done request:" formats
|
|
104
|
+
const requestMatch = line.match(/(?:done )?request: (POST|GET|PUT|DELETE) ([^\s]+) ([^\s]+) (\d+)/);
|
|
88
105
|
if (!requestMatch) return null;
|
|
89
106
|
|
|
90
107
|
const [, method, endpoint, ip, status] = requestMatch;
|
|
@@ -98,40 +115,46 @@ export class LogParser {
|
|
|
98
115
|
|
|
99
116
|
/**
|
|
100
117
|
* Consolidate buffered request/response lines into single line
|
|
118
|
+
* Handles both old and new llama.cpp log formats
|
|
101
119
|
*/
|
|
102
120
|
private consolidateRequest(lines: string[]): string | null {
|
|
103
121
|
try {
|
|
104
122
|
// Parse first line: timestamp and request info
|
|
123
|
+
// Match both "request:" and "done request:" formats
|
|
105
124
|
const firstLine = lines[0];
|
|
106
125
|
const timestamp = this.extractTimestamp(firstLine);
|
|
107
|
-
const requestMatch = firstLine.match(/request: (POST|GET|PUT|DELETE) (\/[^\s]+) ([^\s]+) (\d+)/);
|
|
126
|
+
const requestMatch = firstLine.match(/(?:done )?request: (POST|GET|PUT|DELETE) (\/[^\s]+) ([^\s]+) (\d+)/);
|
|
108
127
|
if (!requestMatch) return null;
|
|
109
128
|
|
|
110
129
|
const [, method, endpoint, ip, status] = requestMatch;
|
|
111
130
|
|
|
112
|
-
// Parse request JSON (
|
|
131
|
+
// Parse request JSON (line with JSON body)
|
|
113
132
|
const requestLine = lines.find((l) => l.includes('log_server_r: request:') && l.includes('{'));
|
|
114
|
-
if (!requestLine) return null;
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
let userMessage = '';
|
|
135
|
+
if (requestLine) {
|
|
136
|
+
const requestJson = this.extractJson(requestLine);
|
|
137
|
+
if (requestJson) {
|
|
138
|
+
userMessage = this.extractUserMessage(requestJson);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
120
141
|
|
|
121
|
-
// Parse response JSON (
|
|
142
|
+
// Parse response JSON (may be empty in new format)
|
|
122
143
|
const responseLine = lines.find((l) => l.includes('log_server_r: response:'));
|
|
123
|
-
|
|
144
|
+
let tokensIn = 0;
|
|
145
|
+
let tokensOut = 0;
|
|
146
|
+
let responseTimeMs = 0;
|
|
124
147
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
if (responseLine) {
|
|
149
|
+
const responseJson = this.extractJson(responseLine);
|
|
150
|
+
if (responseJson) {
|
|
151
|
+
tokensIn = responseJson.usage?.prompt_tokens || 0;
|
|
152
|
+
tokensOut = responseJson.usage?.completion_tokens || 0;
|
|
153
|
+
responseTimeMs = this.extractResponseTime(responseJson);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
133
156
|
|
|
134
|
-
// Format compact line
|
|
157
|
+
// Format compact line (works even without response data)
|
|
135
158
|
return this.formatCompactLine({
|
|
136
159
|
timestamp,
|
|
137
160
|
method,
|
|
@@ -179,14 +202,27 @@ export class LogParser {
|
|
|
179
202
|
|
|
180
203
|
/**
|
|
181
204
|
* Extract first user message from request JSON
|
|
205
|
+
* Handles both string content and array content formats:
|
|
206
|
+
* - String: {"role":"user","content":"Hello"}
|
|
207
|
+
* - Array: {"role":"user","content":[{"type":"text","text":"Hello"}]}
|
|
182
208
|
*/
|
|
183
209
|
private extractUserMessage(requestJson: any): string {
|
|
184
210
|
const messages = requestJson.messages || [];
|
|
185
211
|
const userMsg = messages.find((m: any) => m.role === 'user');
|
|
186
212
|
if (!userMsg || !userMsg.content) return '';
|
|
187
213
|
|
|
188
|
-
|
|
189
|
-
|
|
214
|
+
let content: string;
|
|
215
|
+
|
|
216
|
+
// Handle array format (e.g., Claude/Anthropic API style)
|
|
217
|
+
if (Array.isArray(userMsg.content)) {
|
|
218
|
+
const textPart = userMsg.content.find((p: any) => p.type === 'text');
|
|
219
|
+
content = textPart?.text || '';
|
|
220
|
+
} else {
|
|
221
|
+
content = userMsg.content;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Clean and truncate to first 50 characters
|
|
225
|
+
content = content.replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
|
|
190
226
|
return content.length > 50 ? content.substring(0, 47) + '...' : content;
|
|
191
227
|
}
|
|
192
228
|
|