@appkit/llamacpp-cli 1.8.0 → 1.10.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 +58 -0
- package/README.md +249 -40
- package/dist/cli.js +154 -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 +11 -0
- package/dist/commands/router/config.d.ts.map +1 -0
- package/dist/commands/router/config.js +100 -0
- package/dist/commands/router/config.js.map +1 -0
- 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/router/restart.d.ts +2 -0
- package/dist/commands/router/restart.d.ts.map +1 -0
- package/dist/commands/router/restart.js +39 -0
- package/dist/commands/router/restart.js.map +1 -0
- package/dist/commands/router/start.d.ts +2 -0
- package/dist/commands/router/start.d.ts.map +1 -0
- package/dist/commands/router/start.js +60 -0
- package/dist/commands/router/start.js.map +1 -0
- package/dist/commands/router/status.d.ts +2 -0
- package/dist/commands/router/status.d.ts.map +1 -0
- package/dist/commands/router/status.js +116 -0
- package/dist/commands/router/status.js.map +1 -0
- package/dist/commands/router/stop.d.ts +2 -0
- package/dist/commands/router/stop.d.ts.map +1 -0
- package/dist/commands/router/stop.js +36 -0
- package/dist/commands/router/stop.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 +103 -0
- package/dist/lib/router-manager.d.ts.map +1 -0
- package/dist/lib/router-manager.js +394 -0
- package/dist/lib/router-manager.js.map +1 -0
- package/dist/lib/router-server.d.ts +61 -0
- package/dist/lib/router-server.d.ts.map +1 -0
- package/dist/lib/router-server.js +485 -0
- package/dist/lib/router-server.js.map +1 -0
- 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 +19 -0
- package/dist/types/router-config.d.ts.map +1 -0
- package/dist/types/router-config.js +3 -0
- package/dist/types/router-config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +121 -10
- package/src/commands/monitor.ts +1 -1
- package/src/commands/ps.ts +44 -133
- package/src/commands/router/config.ts +116 -0
- package/src/commands/router/logs.ts +256 -0
- package/src/commands/router/restart.ts +36 -0
- package/src/commands/router/start.ts +60 -0
- package/src/commands/router/status.ts +119 -0
- package/src/commands/router/stop.ts +33 -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 +414 -0
- package/src/lib/router-server.ts +538 -0
- 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 +25 -0
|
@@ -13,6 +13,11 @@ export interface DownloadProgress {
|
|
|
13
13
|
speed: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface DownloadOptions {
|
|
17
|
+
silent?: boolean; // Suppress console output (for TUI)
|
|
18
|
+
signal?: AbortSignal; // Abort signal for cancellation
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export class ModelDownloader {
|
|
17
22
|
private modelsDir?: string;
|
|
18
23
|
private getModelsDirFn?: () => Promise<string>;
|
|
@@ -68,7 +73,8 @@ export class ModelDownloader {
|
|
|
68
73
|
private downloadFile(
|
|
69
74
|
url: string,
|
|
70
75
|
destPath: string,
|
|
71
|
-
onProgress?: (downloaded: number, total: number) => void
|
|
76
|
+
onProgress?: (downloaded: number, total: number) => void,
|
|
77
|
+
signal?: AbortSignal
|
|
72
78
|
): Promise<void> {
|
|
73
79
|
return new Promise((resolve, reject) => {
|
|
74
80
|
const file = fs.createWriteStream(destPath);
|
|
@@ -77,6 +83,7 @@ export class ModelDownloader {
|
|
|
77
83
|
let lastUpdateTime = Date.now();
|
|
78
84
|
let lastDownloadedBytes = 0;
|
|
79
85
|
let completed = false;
|
|
86
|
+
let request: ReturnType<typeof https.get> | null = null;
|
|
80
87
|
|
|
81
88
|
const cleanup = (sigintHandler?: () => void) => {
|
|
82
89
|
if (sigintHandler) {
|
|
@@ -95,22 +102,37 @@ export class ModelDownloader {
|
|
|
95
102
|
};
|
|
96
103
|
|
|
97
104
|
const sigintHandler = () => {
|
|
98
|
-
request.destroy();
|
|
105
|
+
if (request) request.destroy();
|
|
99
106
|
handleError(new Error('Download interrupted by user'), sigintHandler);
|
|
100
107
|
};
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
// Handle abort signal
|
|
110
|
+
const abortHandler = () => {
|
|
111
|
+
if (request) request.destroy();
|
|
112
|
+
handleError(new Error('Download cancelled'), sigintHandler);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (signal) {
|
|
116
|
+
if (signal.aborted) {
|
|
117
|
+
handleError(new Error('Download cancelled'), sigintHandler);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
signal.addEventListener('abort', abortHandler, { once: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
request = https.get(url, { agent: new https.Agent({ keepAlive: false }) }, (response) => {
|
|
103
124
|
// Handle redirects (301, 302, 307, 308)
|
|
104
125
|
if (response.statusCode === 301 || response.statusCode === 302 ||
|
|
105
126
|
response.statusCode === 307 || response.statusCode === 308) {
|
|
106
127
|
const redirectUrl = response.headers.location;
|
|
107
128
|
if (redirectUrl) {
|
|
108
129
|
cleanup(sigintHandler);
|
|
130
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
109
131
|
// Wait for file to close before starting new download
|
|
110
132
|
file.close(() => {
|
|
111
133
|
fs.unlink(destPath, () => {
|
|
112
134
|
// Start recursive download only after cleanup is complete
|
|
113
|
-
this.downloadFile(redirectUrl, destPath, onProgress)
|
|
135
|
+
this.downloadFile(redirectUrl, destPath, onProgress, signal)
|
|
114
136
|
.then(resolve)
|
|
115
137
|
.catch(reject);
|
|
116
138
|
});
|
|
@@ -154,6 +176,7 @@ export class ModelDownloader {
|
|
|
154
176
|
// Use callback to ensure close completes before resolving
|
|
155
177
|
file.close((err) => {
|
|
156
178
|
cleanup(sigintHandler);
|
|
179
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
157
180
|
if (err) reject(err);
|
|
158
181
|
else resolve();
|
|
159
182
|
});
|
|
@@ -161,10 +184,12 @@ export class ModelDownloader {
|
|
|
161
184
|
});
|
|
162
185
|
|
|
163
186
|
request.on('error', (err) => {
|
|
187
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
164
188
|
handleError(err, sigintHandler);
|
|
165
189
|
});
|
|
166
190
|
|
|
167
191
|
file.on('error', (err) => {
|
|
192
|
+
if (signal) signal.removeEventListener('abort', abortHandler);
|
|
168
193
|
handleError(err, sigintHandler);
|
|
169
194
|
});
|
|
170
195
|
|
|
@@ -200,15 +225,21 @@ export class ModelDownloader {
|
|
|
200
225
|
repoId: string,
|
|
201
226
|
filename: string,
|
|
202
227
|
onProgress?: (progress: DownloadProgress) => void,
|
|
203
|
-
modelsDir?: string
|
|
228
|
+
modelsDir?: string,
|
|
229
|
+
options?: DownloadOptions
|
|
204
230
|
): Promise<string> {
|
|
231
|
+
const silent = options?.silent ?? false;
|
|
232
|
+
const signal = options?.signal;
|
|
233
|
+
|
|
205
234
|
// Use provided models directory or get from config
|
|
206
235
|
const targetDir = modelsDir || await this.getModelsDirectory();
|
|
207
236
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
237
|
+
if (!silent) {
|
|
238
|
+
console.log(chalk.blue(`📥 Downloading ${filename} from Hugging Face...`));
|
|
239
|
+
console.log(chalk.dim(`Repository: ${repoId}`));
|
|
240
|
+
console.log(chalk.dim(`Destination: ${targetDir}`));
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
212
243
|
|
|
213
244
|
// Build download URL
|
|
214
245
|
const url = this.buildDownloadUrl(repoId, filename);
|
|
@@ -216,8 +247,10 @@ export class ModelDownloader {
|
|
|
216
247
|
|
|
217
248
|
// Check if file already exists
|
|
218
249
|
if (fs.existsSync(destPath)) {
|
|
219
|
-
|
|
220
|
-
|
|
250
|
+
if (!silent) {
|
|
251
|
+
console.log(chalk.yellow(`⚠️ File already exists: ${filename}`));
|
|
252
|
+
console.log(chalk.dim(' Remove it first or choose a different filename'));
|
|
253
|
+
}
|
|
221
254
|
throw new Error('File already exists');
|
|
222
255
|
}
|
|
223
256
|
|
|
@@ -237,8 +270,10 @@ export class ModelDownloader {
|
|
|
237
270
|
lastTime = now;
|
|
238
271
|
lastDownloaded = downloaded;
|
|
239
272
|
|
|
240
|
-
// Display progress bar
|
|
241
|
-
|
|
273
|
+
// Display progress bar (only if not silent)
|
|
274
|
+
if (!silent) {
|
|
275
|
+
this.displayProgress(downloaded, total, filename);
|
|
276
|
+
}
|
|
242
277
|
|
|
243
278
|
// Call user progress callback if provided
|
|
244
279
|
if (onProgress) {
|
|
@@ -250,15 +285,17 @@ export class ModelDownloader {
|
|
|
250
285
|
speed: `${formatBytes(speed)}/s`,
|
|
251
286
|
});
|
|
252
287
|
}
|
|
253
|
-
});
|
|
288
|
+
}, signal);
|
|
254
289
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
290
|
+
if (!silent) {
|
|
291
|
+
// Clear progress line and show completion
|
|
292
|
+
process.stdout.write('\r\x1b[K');
|
|
293
|
+
console.log(chalk.green('✅ Download complete!'));
|
|
258
294
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
295
|
+
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
296
|
+
console.log(chalk.dim(` Time: ${totalTime}s`));
|
|
297
|
+
console.log(chalk.dim(` Location: ${destPath}`));
|
|
298
|
+
}
|
|
262
299
|
|
|
263
300
|
return destPath;
|
|
264
301
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { getLogsDir } from '../utils/file-utils';
|
|
4
|
+
|
|
5
|
+
export interface RouterLogEntry {
|
|
6
|
+
timestamp: string;
|
|
7
|
+
model: string;
|
|
8
|
+
endpoint: string;
|
|
9
|
+
method: string;
|
|
10
|
+
status: 'success' | 'error';
|
|
11
|
+
statusCode: number;
|
|
12
|
+
durationMs: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
backend?: string; // e.g., "localhost:9001"
|
|
15
|
+
prompt?: string; // First part of the prompt/message
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class RouterLogger {
|
|
19
|
+
private logFilePath: string;
|
|
20
|
+
private verbose: boolean;
|
|
21
|
+
|
|
22
|
+
constructor(verbose: boolean = false) {
|
|
23
|
+
this.verbose = verbose;
|
|
24
|
+
this.logFilePath = path.join(getLogsDir(), 'router.log');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Log a request with timing and outcome
|
|
29
|
+
*/
|
|
30
|
+
async logRequest(entry: RouterLogEntry): Promise<void> {
|
|
31
|
+
// Human-readable format for console
|
|
32
|
+
const humanLog = this.formatHumanReadable(entry);
|
|
33
|
+
|
|
34
|
+
// Output request activity to stdout (separate from system messages on stderr)
|
|
35
|
+
console.log(humanLog);
|
|
36
|
+
|
|
37
|
+
// Verbose mode: append detailed JSON to log file
|
|
38
|
+
if (this.verbose) {
|
|
39
|
+
const jsonLog = JSON.stringify(entry) + '\n';
|
|
40
|
+
try {
|
|
41
|
+
await fs.appendFile(this.logFilePath, jsonLog, 'utf-8');
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('[Router Logger] Failed to write to log file:', error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Format log entry for human reading (console output)
|
|
50
|
+
*/
|
|
51
|
+
private formatHumanReadable(entry: RouterLogEntry): string {
|
|
52
|
+
const { timestamp, model, endpoint, method, status, statusCode, durationMs, error, backend, prompt } = entry;
|
|
53
|
+
|
|
54
|
+
// Color coding based on status (using ANSI codes)
|
|
55
|
+
const statusColor = status === 'success' ? '\x1b[32m' : '\x1b[31m'; // Green or Red
|
|
56
|
+
const resetColor = '\x1b[0m';
|
|
57
|
+
|
|
58
|
+
// Base log format (no [Router] prefix, no icons)
|
|
59
|
+
let log = `${statusColor}${statusCode}${resetColor} ${method} ${endpoint} → ${model}`;
|
|
60
|
+
|
|
61
|
+
// Add backend if available
|
|
62
|
+
if (backend) {
|
|
63
|
+
log += ` (${backend})`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add duration
|
|
67
|
+
log += ` ${durationMs}ms`;
|
|
68
|
+
|
|
69
|
+
// Add prompt preview if available
|
|
70
|
+
if (prompt) {
|
|
71
|
+
log += ` | "${prompt}"`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add error if present
|
|
75
|
+
if (error) {
|
|
76
|
+
log += ` | Error: ${error}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return log;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format log entry for LLM parsing (verbose JSON format)
|
|
84
|
+
*/
|
|
85
|
+
static formatForLLM(entry: RouterLogEntry): string {
|
|
86
|
+
return JSON.stringify(entry, null, 2);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read log file and return all entries (for verbose mode)
|
|
91
|
+
*/
|
|
92
|
+
async readLogs(limit?: number): Promise<RouterLogEntry[]> {
|
|
93
|
+
try {
|
|
94
|
+
const content = await fs.readFile(this.logFilePath, 'utf-8');
|
|
95
|
+
const lines = content.trim().split('\n').filter(line => line);
|
|
96
|
+
|
|
97
|
+
// Parse JSON entries
|
|
98
|
+
const entries = lines
|
|
99
|
+
.map(line => {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(line) as RouterLogEntry;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.filter((entry): entry is RouterLogEntry => entry !== null);
|
|
107
|
+
|
|
108
|
+
// Apply limit if specified
|
|
109
|
+
if (limit && limit > 0) {
|
|
110
|
+
return entries.slice(-limit);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return entries;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
// Log file doesn't exist or can't be read
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clear the log file
|
|
122
|
+
*/
|
|
123
|
+
async clearLogs(): Promise<void> {
|
|
124
|
+
try {
|
|
125
|
+
await fs.writeFile(this.logFilePath, '', 'utf-8');
|
|
126
|
+
console.error('[Router Logger] Log file cleared');
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('[Router Logger] Failed to clear log file:', error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get log file size
|
|
134
|
+
*/
|
|
135
|
+
async getLogFileSize(): Promise<number> {
|
|
136
|
+
try {
|
|
137
|
+
const stats = await fs.stat(this.logFilePath);
|
|
138
|
+
return stats.size;
|
|
139
|
+
} catch {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Rotate log file if it exceeds threshold
|
|
146
|
+
*/
|
|
147
|
+
async rotateIfNeeded(thresholdMB: number = 100): Promise<boolean> {
|
|
148
|
+
const size = await this.getLogFileSize();
|
|
149
|
+
const thresholdBytes = thresholdMB * 1024 * 1024;
|
|
150
|
+
|
|
151
|
+
if (size > thresholdBytes) {
|
|
152
|
+
try {
|
|
153
|
+
// Generate timestamp
|
|
154
|
+
const timestamp = new Date()
|
|
155
|
+
.toISOString()
|
|
156
|
+
.replace(/T/, '-')
|
|
157
|
+
.replace(/:/g, '-')
|
|
158
|
+
.replace(/\..+/, '');
|
|
159
|
+
|
|
160
|
+
const logsDir = getLogsDir();
|
|
161
|
+
const archivedPath = path.join(logsDir, `router.${timestamp}.log`);
|
|
162
|
+
|
|
163
|
+
// Rename current log to archived version
|
|
164
|
+
await fs.rename(this.logFilePath, archivedPath);
|
|
165
|
+
|
|
166
|
+
console.error(`[Router Logger] Rotated log file to ${archivedPath}`);
|
|
167
|
+
return true;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('[Router Logger] Failed to rotate log file:', error);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Utility class for tracking request timing
|
|
180
|
+
*/
|
|
181
|
+
export class RequestTimer {
|
|
182
|
+
private startTime: number;
|
|
183
|
+
|
|
184
|
+
constructor() {
|
|
185
|
+
this.startTime = Date.now();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get elapsed time in milliseconds
|
|
190
|
+
*/
|
|
191
|
+
elapsed(): number {
|
|
192
|
+
return Date.now() - this.startTime;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get current ISO timestamp
|
|
197
|
+
*/
|
|
198
|
+
static now(): string {
|
|
199
|
+
return new Date().toISOString();
|
|
200
|
+
}
|
|
201
|
+
}
|