@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.
Files changed (116) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +249 -40
  3. package/dist/cli.js +154 -10
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/completion.d.ts +9 -0
  6. package/dist/commands/completion.d.ts.map +1 -0
  7. package/dist/commands/completion.js +83 -0
  8. package/dist/commands/completion.js.map +1 -0
  9. package/dist/commands/monitor.js +1 -1
  10. package/dist/commands/monitor.js.map +1 -1
  11. package/dist/commands/ps.d.ts +1 -3
  12. package/dist/commands/ps.d.ts.map +1 -1
  13. package/dist/commands/ps.js +36 -115
  14. package/dist/commands/ps.js.map +1 -1
  15. package/dist/commands/router/config.d.ts +11 -0
  16. package/dist/commands/router/config.d.ts.map +1 -0
  17. package/dist/commands/router/config.js +100 -0
  18. package/dist/commands/router/config.js.map +1 -0
  19. package/dist/commands/router/logs.d.ts +12 -0
  20. package/dist/commands/router/logs.d.ts.map +1 -0
  21. package/dist/commands/router/logs.js +238 -0
  22. package/dist/commands/router/logs.js.map +1 -0
  23. package/dist/commands/router/restart.d.ts +2 -0
  24. package/dist/commands/router/restart.d.ts.map +1 -0
  25. package/dist/commands/router/restart.js +39 -0
  26. package/dist/commands/router/restart.js.map +1 -0
  27. package/dist/commands/router/start.d.ts +2 -0
  28. package/dist/commands/router/start.d.ts.map +1 -0
  29. package/dist/commands/router/start.js +60 -0
  30. package/dist/commands/router/start.js.map +1 -0
  31. package/dist/commands/router/status.d.ts +2 -0
  32. package/dist/commands/router/status.d.ts.map +1 -0
  33. package/dist/commands/router/status.js +116 -0
  34. package/dist/commands/router/status.js.map +1 -0
  35. package/dist/commands/router/stop.d.ts +2 -0
  36. package/dist/commands/router/stop.d.ts.map +1 -0
  37. package/dist/commands/router/stop.js +36 -0
  38. package/dist/commands/router/stop.js.map +1 -0
  39. package/dist/commands/tui.d.ts +2 -0
  40. package/dist/commands/tui.d.ts.map +1 -0
  41. package/dist/commands/tui.js +27 -0
  42. package/dist/commands/tui.js.map +1 -0
  43. package/dist/lib/completion.d.ts +5 -0
  44. package/dist/lib/completion.d.ts.map +1 -0
  45. package/dist/lib/completion.js +195 -0
  46. package/dist/lib/completion.js.map +1 -0
  47. package/dist/lib/model-downloader.d.ts +5 -1
  48. package/dist/lib/model-downloader.d.ts.map +1 -1
  49. package/dist/lib/model-downloader.js +53 -20
  50. package/dist/lib/model-downloader.js.map +1 -1
  51. package/dist/lib/router-logger.d.ts +61 -0
  52. package/dist/lib/router-logger.d.ts.map +1 -0
  53. package/dist/lib/router-logger.js +200 -0
  54. package/dist/lib/router-logger.js.map +1 -0
  55. package/dist/lib/router-manager.d.ts +103 -0
  56. package/dist/lib/router-manager.d.ts.map +1 -0
  57. package/dist/lib/router-manager.js +394 -0
  58. package/dist/lib/router-manager.js.map +1 -0
  59. package/dist/lib/router-server.d.ts +61 -0
  60. package/dist/lib/router-server.d.ts.map +1 -0
  61. package/dist/lib/router-server.js +485 -0
  62. package/dist/lib/router-server.js.map +1 -0
  63. package/dist/tui/ConfigApp.d.ts +7 -0
  64. package/dist/tui/ConfigApp.d.ts.map +1 -0
  65. package/dist/tui/ConfigApp.js +1002 -0
  66. package/dist/tui/ConfigApp.js.map +1 -0
  67. package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
  68. package/dist/tui/HistoricalMonitorApp.js +85 -49
  69. package/dist/tui/HistoricalMonitorApp.js.map +1 -1
  70. package/dist/tui/ModelsApp.d.ts +7 -0
  71. package/dist/tui/ModelsApp.d.ts.map +1 -0
  72. package/dist/tui/ModelsApp.js +362 -0
  73. package/dist/tui/ModelsApp.js.map +1 -0
  74. package/dist/tui/MultiServerMonitorApp.d.ts +6 -1
  75. package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
  76. package/dist/tui/MultiServerMonitorApp.js +1038 -122
  77. package/dist/tui/MultiServerMonitorApp.js.map +1 -1
  78. package/dist/tui/RootNavigator.d.ts +7 -0
  79. package/dist/tui/RootNavigator.d.ts.map +1 -0
  80. package/dist/tui/RootNavigator.js +55 -0
  81. package/dist/tui/RootNavigator.js.map +1 -0
  82. package/dist/tui/SearchApp.d.ts +6 -0
  83. package/dist/tui/SearchApp.d.ts.map +1 -0
  84. package/dist/tui/SearchApp.js +451 -0
  85. package/dist/tui/SearchApp.js.map +1 -0
  86. package/dist/tui/SplashScreen.d.ts +16 -0
  87. package/dist/tui/SplashScreen.d.ts.map +1 -0
  88. package/dist/tui/SplashScreen.js +129 -0
  89. package/dist/tui/SplashScreen.js.map +1 -0
  90. package/dist/types/router-config.d.ts +19 -0
  91. package/dist/types/router-config.d.ts.map +1 -0
  92. package/dist/types/router-config.js +3 -0
  93. package/dist/types/router-config.js.map +1 -0
  94. package/package.json +1 -1
  95. package/src/cli.ts +121 -10
  96. package/src/commands/monitor.ts +1 -1
  97. package/src/commands/ps.ts +44 -133
  98. package/src/commands/router/config.ts +116 -0
  99. package/src/commands/router/logs.ts +256 -0
  100. package/src/commands/router/restart.ts +36 -0
  101. package/src/commands/router/start.ts +60 -0
  102. package/src/commands/router/status.ts +119 -0
  103. package/src/commands/router/stop.ts +33 -0
  104. package/src/commands/tui.ts +25 -0
  105. package/src/lib/model-downloader.ts +57 -20
  106. package/src/lib/router-logger.ts +201 -0
  107. package/src/lib/router-manager.ts +414 -0
  108. package/src/lib/router-server.ts +538 -0
  109. package/src/tui/ConfigApp.ts +1085 -0
  110. package/src/tui/HistoricalMonitorApp.ts +88 -49
  111. package/src/tui/ModelsApp.ts +368 -0
  112. package/src/tui/MultiServerMonitorApp.ts +1163 -122
  113. package/src/tui/RootNavigator.ts +74 -0
  114. package/src/tui/SearchApp.ts +511 -0
  115. package/src/tui/SplashScreen.ts +149 -0
  116. package/src/types/router-config.ts +25 -0
@@ -0,0 +1,414 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import { RouterConfig } from '../types/router-config';
4
+ import { execCommand, execAsync } from '../utils/process-utils';
5
+ import {
6
+ ensureDir,
7
+ writeJsonAtomic,
8
+ readJson,
9
+ fileExists,
10
+ getConfigDir,
11
+ getLogsDir,
12
+ getLaunchAgentsDir,
13
+ writeFileAtomic,
14
+ } from '../utils/file-utils';
15
+
16
+ export interface RouterServiceStatus {
17
+ isRunning: boolean;
18
+ pid: number | null;
19
+ exitCode: number | null;
20
+ lastExitReason?: string;
21
+ }
22
+
23
+ export class RouterManager {
24
+ private configDir: string;
25
+ private logsDir: string;
26
+ private configPath: string;
27
+ private launchAgentsDir: string;
28
+
29
+ constructor() {
30
+ this.configDir = getConfigDir();
31
+ this.logsDir = getLogsDir();
32
+ this.configPath = path.join(this.configDir, 'router.json');
33
+ this.launchAgentsDir = getLaunchAgentsDir();
34
+ }
35
+
36
+ /**
37
+ * Initialize router directories
38
+ */
39
+ async initialize(): Promise<void> {
40
+ await ensureDir(this.configDir);
41
+ await ensureDir(this.logsDir);
42
+ await ensureDir(this.launchAgentsDir);
43
+ }
44
+
45
+ /**
46
+ * Get default router configuration
47
+ */
48
+ getDefaultConfig(): RouterConfig {
49
+ return {
50
+ id: 'router',
51
+ port: 9100,
52
+ host: '127.0.0.1',
53
+ label: 'com.llama.router',
54
+ plistPath: path.join(this.launchAgentsDir, 'com.llama.router.plist'),
55
+ stdoutPath: path.join(this.logsDir, 'router.stdout'),
56
+ stderrPath: path.join(this.logsDir, 'router.stderr'),
57
+ healthCheckInterval: 5000,
58
+ requestTimeout: 120000,
59
+ verbose: false,
60
+ status: 'stopped',
61
+ createdAt: new Date().toISOString(),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Load router configuration
67
+ */
68
+ async loadConfig(): Promise<RouterConfig | null> {
69
+ if (!(await fileExists(this.configPath))) {
70
+ return null;
71
+ }
72
+ return await readJson<RouterConfig>(this.configPath);
73
+ }
74
+
75
+ /**
76
+ * Save router configuration
77
+ */
78
+ async saveConfig(config: RouterConfig): Promise<void> {
79
+ await writeJsonAtomic(this.configPath, config);
80
+ }
81
+
82
+ /**
83
+ * Update router configuration with partial changes
84
+ */
85
+ async updateConfig(updates: Partial<RouterConfig>): Promise<void> {
86
+ const existingConfig = await this.loadConfig();
87
+ if (!existingConfig) {
88
+ throw new Error('Router configuration not found');
89
+ }
90
+ const updatedConfig = { ...existingConfig, ...updates };
91
+ await this.saveConfig(updatedConfig);
92
+ }
93
+
94
+ /**
95
+ * Delete router configuration
96
+ */
97
+ async deleteConfig(): Promise<void> {
98
+ if (await fileExists(this.configPath)) {
99
+ await fs.unlink(this.configPath);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Generate plist XML content for the router
105
+ */
106
+ generatePlist(config: RouterConfig): string {
107
+ // Find the compiled router-server.js file
108
+ // In dev mode (tsx), __dirname is src/lib/
109
+ // In production, __dirname is dist/lib/
110
+ // Always use the compiled dist version for launchctl
111
+ let routerServerPath: string;
112
+ if (__dirname.includes('/src/')) {
113
+ // Dev mode - point to dist/lib/router-server.js
114
+ const projectRoot = path.resolve(__dirname, '../..');
115
+ routerServerPath = path.join(projectRoot, 'dist/lib/router-server.js');
116
+ } else {
117
+ // Production mode - already in dist/lib/
118
+ routerServerPath = path.join(__dirname, 'router-server.js');
119
+ }
120
+
121
+ // Use the current Node.js executable path (resolves symlinks)
122
+ const nodePath = process.execPath;
123
+
124
+ const args = [
125
+ nodePath,
126
+ routerServerPath,
127
+ '--config', this.configPath,
128
+ ];
129
+
130
+ const argsXml = args.map(arg => ` <string>${arg}</string>`).join('\n');
131
+
132
+ return `<?xml version="1.0" encoding="UTF-8"?>
133
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
134
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
135
+ <plist version="1.0">
136
+ <dict>
137
+ <key>Label</key>
138
+ <string>${config.label}</string>
139
+
140
+ <key>ProgramArguments</key>
141
+ <array>
142
+ ${argsXml}
143
+ </array>
144
+
145
+ <key>RunAtLoad</key>
146
+ <false/>
147
+
148
+ <key>KeepAlive</key>
149
+ <dict>
150
+ <key>Crashed</key>
151
+ <true/>
152
+ <key>SuccessfulExit</key>
153
+ <false/>
154
+ </dict>
155
+
156
+ <key>StandardOutPath</key>
157
+ <string>${config.stdoutPath}</string>
158
+
159
+ <key>StandardErrorPath</key>
160
+ <string>${config.stderrPath}</string>
161
+
162
+ <key>WorkingDirectory</key>
163
+ <string>/tmp</string>
164
+
165
+ <key>ThrottleInterval</key>
166
+ <integer>10</integer>
167
+ </dict>
168
+ </plist>
169
+ `;
170
+ }
171
+
172
+ /**
173
+ * Create and write plist file
174
+ */
175
+ async createPlist(config: RouterConfig): Promise<void> {
176
+ const plistContent = this.generatePlist(config);
177
+ await writeFileAtomic(config.plistPath, plistContent);
178
+ }
179
+
180
+ /**
181
+ * Delete plist file
182
+ */
183
+ async deletePlist(config: RouterConfig): Promise<void> {
184
+ if (await fileExists(config.plistPath)) {
185
+ await fs.unlink(config.plistPath);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Load service (register with launchctl)
191
+ */
192
+ async loadService(plistPath: string): Promise<void> {
193
+ await execCommand(`launchctl load "${plistPath}"`);
194
+ }
195
+
196
+ /**
197
+ * Unload service (unregister from launchctl)
198
+ */
199
+ async unloadService(plistPath: string): Promise<void> {
200
+ try {
201
+ await execCommand(`launchctl unload "${plistPath}"`);
202
+ } catch (error) {
203
+ // Ignore errors if service is not loaded
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Start service
209
+ */
210
+ async startService(label: string): Promise<void> {
211
+ await execCommand(`launchctl start ${label}`);
212
+ }
213
+
214
+ /**
215
+ * Stop service
216
+ */
217
+ async stopService(label: string): Promise<void> {
218
+ await execCommand(`launchctl stop ${label}`);
219
+ }
220
+
221
+ /**
222
+ * Get service status from launchctl
223
+ */
224
+ async getServiceStatus(label: string): Promise<RouterServiceStatus> {
225
+ try {
226
+ const { stdout } = await execAsync(`launchctl list | grep ${label}`);
227
+ const lines = stdout.trim().split('\n');
228
+
229
+ for (const line of lines) {
230
+ const parts = line.split(/\s+/);
231
+ if (parts.length >= 3) {
232
+ const pidStr = parts[0].trim();
233
+ const exitCodeStr = parts[1].trim();
234
+ const serviceLabel = parts[2].trim();
235
+
236
+ if (serviceLabel === label) {
237
+ const pid = pidStr !== '-' ? parseInt(pidStr, 10) : null;
238
+ const exitCode = exitCodeStr !== '-' ? parseInt(exitCodeStr, 10) : null;
239
+ const isRunning = pid !== null;
240
+
241
+ return {
242
+ isRunning,
243
+ pid,
244
+ exitCode,
245
+ lastExitReason: this.interpretExitCode(exitCode),
246
+ };
247
+ }
248
+ }
249
+ }
250
+
251
+ return {
252
+ isRunning: false,
253
+ pid: null,
254
+ exitCode: null,
255
+ };
256
+ } catch (error) {
257
+ return {
258
+ isRunning: false,
259
+ pid: null,
260
+ exitCode: null,
261
+ };
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Interpret exit code to human-readable reason
267
+ */
268
+ private interpretExitCode(code: number | null): string | undefined {
269
+ if (code === null || code === 0) return undefined;
270
+ if (code === -9) return 'Force killed (SIGKILL)';
271
+ if (code === -15) return 'Terminated (SIGTERM)';
272
+ return `Exit code: ${code}`;
273
+ }
274
+
275
+ /**
276
+ * Wait for service to start (with timeout)
277
+ */
278
+ async waitForServiceStart(label: string, timeoutMs = 5000): Promise<boolean> {
279
+ const startTime = Date.now();
280
+ while (Date.now() - startTime < timeoutMs) {
281
+ const status = await this.getServiceStatus(label);
282
+ if (status.isRunning) {
283
+ return true;
284
+ }
285
+ await new Promise((resolve) => setTimeout(resolve, 500));
286
+ }
287
+ return false;
288
+ }
289
+
290
+ /**
291
+ * Wait for service to stop (with timeout)
292
+ */
293
+ async waitForServiceStop(label: string, timeoutMs = 5000): Promise<boolean> {
294
+ const startTime = Date.now();
295
+ while (Date.now() - startTime < timeoutMs) {
296
+ const status = await this.getServiceStatus(label);
297
+ if (!status.isRunning) {
298
+ return true;
299
+ }
300
+ await new Promise((resolve) => setTimeout(resolve, 500));
301
+ }
302
+ return false;
303
+ }
304
+
305
+ /**
306
+ * Start router service
307
+ */
308
+ async start(): Promise<void> {
309
+ await this.initialize();
310
+
311
+ let config = await this.loadConfig();
312
+ if (!config) {
313
+ // Create default config
314
+ config = this.getDefaultConfig();
315
+ await this.saveConfig(config);
316
+ }
317
+
318
+ // Check if already running
319
+ if (config.status === 'running') {
320
+ throw new Error('Router is already running');
321
+ }
322
+
323
+ // Check for throttled state (exit code 78)
324
+ const currentStatus = await this.getServiceStatus(config.label);
325
+ if (currentStatus.exitCode === 78) {
326
+ // Service is throttled - clean up and start fresh
327
+ await this.unloadService(config.plistPath);
328
+ await this.deletePlist(config);
329
+ // Give launchd a moment to clean up
330
+ await new Promise((resolve) => setTimeout(resolve, 1000));
331
+ }
332
+
333
+ // Create plist
334
+ await this.createPlist(config);
335
+
336
+ // Load and start service
337
+ try {
338
+ await this.loadService(config.plistPath);
339
+ } catch (error) {
340
+ // May already be loaded
341
+ }
342
+
343
+ await this.startService(config.label);
344
+
345
+ // Wait for startup
346
+ const started = await this.waitForServiceStart(config.label, 5000);
347
+ if (!started) {
348
+ throw new Error('Router failed to start');
349
+ }
350
+
351
+ // Update config
352
+ const status = await this.getServiceStatus(config.label);
353
+ await this.updateConfig({
354
+ status: 'running',
355
+ pid: status.pid || undefined,
356
+ lastStarted: new Date().toISOString(),
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Stop router service
362
+ */
363
+ async stop(): Promise<void> {
364
+ const config = await this.loadConfig();
365
+ if (!config) {
366
+ throw new Error('Router configuration not found');
367
+ }
368
+
369
+ if (config.status !== 'running') {
370
+ throw new Error('Router is not running');
371
+ }
372
+
373
+ // Unload service
374
+ await this.unloadService(config.plistPath);
375
+
376
+ // Wait for shutdown
377
+ await this.waitForServiceStop(config.label, 5000);
378
+
379
+ // Update config
380
+ await this.updateConfig({
381
+ status: 'stopped',
382
+ pid: undefined,
383
+ lastStopped: new Date().toISOString(),
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Restart router service
389
+ */
390
+ async restart(): Promise<void> {
391
+ try {
392
+ await this.stop();
393
+ } catch (error) {
394
+ // May not be running
395
+ }
396
+ await this.start();
397
+ }
398
+
399
+ /**
400
+ * Get router status
401
+ */
402
+ async getStatus(): Promise<{ config: RouterConfig; status: RouterServiceStatus } | null> {
403
+ const config = await this.loadConfig();
404
+ if (!config) {
405
+ return null;
406
+ }
407
+
408
+ const status = await this.getServiceStatus(config.label);
409
+ return { config, status };
410
+ }
411
+ }
412
+
413
+ // Export singleton instance
414
+ export const routerManager = new RouterManager();