@appkit/llamacpp-cli 1.10.1 → 1.12.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 (114) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +356 -3
  3. package/dist/cli.js +99 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/admin/config.d.ts +10 -0
  6. package/dist/commands/admin/config.d.ts.map +1 -0
  7. package/dist/commands/admin/config.js +100 -0
  8. package/dist/commands/admin/config.js.map +1 -0
  9. package/dist/commands/admin/logs.d.ts +10 -0
  10. package/dist/commands/admin/logs.d.ts.map +1 -0
  11. package/dist/commands/admin/logs.js +114 -0
  12. package/dist/commands/admin/logs.js.map +1 -0
  13. package/dist/commands/admin/restart.d.ts +2 -0
  14. package/dist/commands/admin/restart.d.ts.map +1 -0
  15. package/dist/commands/admin/restart.js +29 -0
  16. package/dist/commands/admin/restart.js.map +1 -0
  17. package/dist/commands/admin/start.d.ts +2 -0
  18. package/dist/commands/admin/start.d.ts.map +1 -0
  19. package/dist/commands/admin/start.js +30 -0
  20. package/dist/commands/admin/start.js.map +1 -0
  21. package/dist/commands/admin/status.d.ts +2 -0
  22. package/dist/commands/admin/status.d.ts.map +1 -0
  23. package/dist/commands/admin/status.js +82 -0
  24. package/dist/commands/admin/status.js.map +1 -0
  25. package/dist/commands/admin/stop.d.ts +2 -0
  26. package/dist/commands/admin/stop.d.ts.map +1 -0
  27. package/dist/commands/admin/stop.js +21 -0
  28. package/dist/commands/admin/stop.js.map +1 -0
  29. package/dist/commands/logs.d.ts +1 -0
  30. package/dist/commands/logs.d.ts.map +1 -1
  31. package/dist/commands/logs.js +22 -0
  32. package/dist/commands/logs.js.map +1 -1
  33. package/dist/commands/server-show.js +1 -1
  34. package/dist/commands/server-show.js.map +1 -1
  35. package/dist/lib/admin-manager.d.ts +111 -0
  36. package/dist/lib/admin-manager.d.ts.map +1 -0
  37. package/dist/lib/admin-manager.js +413 -0
  38. package/dist/lib/admin-manager.js.map +1 -0
  39. package/dist/lib/admin-server.d.ts +148 -0
  40. package/dist/lib/admin-server.d.ts.map +1 -0
  41. package/dist/lib/admin-server.js +1161 -0
  42. package/dist/lib/admin-server.js.map +1 -0
  43. package/dist/lib/download-job-manager.d.ts +64 -0
  44. package/dist/lib/download-job-manager.d.ts.map +1 -0
  45. package/dist/lib/download-job-manager.js +164 -0
  46. package/dist/lib/download-job-manager.js.map +1 -0
  47. package/dist/tui/MultiServerMonitorApp.js +2 -2
  48. package/dist/tui/MultiServerMonitorApp.js.map +1 -1
  49. package/dist/types/admin-config.d.ts +19 -0
  50. package/dist/types/admin-config.d.ts.map +1 -0
  51. package/dist/types/admin-config.js +3 -0
  52. package/dist/types/admin-config.js.map +1 -0
  53. package/dist/utils/format-utils.d.ts +6 -0
  54. package/dist/utils/format-utils.d.ts.map +1 -1
  55. package/dist/utils/format-utils.js +18 -0
  56. package/dist/utils/format-utils.js.map +1 -1
  57. package/dist/utils/log-parser.d.ts +9 -0
  58. package/dist/utils/log-parser.d.ts.map +1 -1
  59. package/dist/utils/log-parser.js +11 -0
  60. package/dist/utils/log-parser.js.map +1 -1
  61. package/docs/images/web-ui-servers.png +0 -0
  62. package/package.json +1 -1
  63. package/src/cli.ts +100 -0
  64. package/src/commands/admin/config.ts +121 -0
  65. package/src/commands/admin/logs.ts +91 -0
  66. package/src/commands/admin/restart.ts +26 -0
  67. package/src/commands/admin/start.ts +27 -0
  68. package/src/commands/admin/status.ts +84 -0
  69. package/src/commands/admin/stop.ts +16 -0
  70. package/src/commands/logs.ts +24 -0
  71. package/src/commands/server-show.ts +2 -2
  72. package/src/lib/admin-manager.ts +435 -0
  73. package/src/lib/admin-server.ts +1243 -0
  74. package/src/lib/download-job-manager.ts +213 -0
  75. package/src/tui/MultiServerMonitorApp.ts +3 -3
  76. package/src/types/admin-config.ts +25 -0
  77. package/src/utils/format-utils.ts +18 -0
  78. package/src/utils/log-parser.ts +13 -0
  79. package/web/README.md +429 -0
  80. package/web/eslint.config.js +23 -0
  81. package/web/index.html +13 -0
  82. package/web/llamacpp-web-dist.tar.gz +0 -0
  83. package/web/package-lock.json +4017 -0
  84. package/web/package.json +38 -0
  85. package/web/postcss.config.js +6 -0
  86. package/web/public/vite.svg +1 -0
  87. package/web/src/App.css +42 -0
  88. package/web/src/App.tsx +86 -0
  89. package/web/src/assets/react.svg +1 -0
  90. package/web/src/components/ApiKeyPrompt.tsx +71 -0
  91. package/web/src/components/CreateServerModal.tsx +372 -0
  92. package/web/src/components/DownloadProgress.tsx +123 -0
  93. package/web/src/components/Nav.tsx +89 -0
  94. package/web/src/components/RouterConfigModal.tsx +240 -0
  95. package/web/src/components/SearchModal.tsx +306 -0
  96. package/web/src/components/ServerConfigModal.tsx +291 -0
  97. package/web/src/hooks/useApi.ts +259 -0
  98. package/web/src/index.css +42 -0
  99. package/web/src/lib/api.ts +226 -0
  100. package/web/src/main.tsx +10 -0
  101. package/web/src/pages/Dashboard.tsx +103 -0
  102. package/web/src/pages/Models.tsx +258 -0
  103. package/web/src/pages/Router.tsx +270 -0
  104. package/web/src/pages/RouterLogs.tsx +201 -0
  105. package/web/src/pages/ServerLogs.tsx +553 -0
  106. package/web/src/pages/Servers.tsx +358 -0
  107. package/web/src/types/api.ts +140 -0
  108. package/web/tailwind.config.js +31 -0
  109. package/web/tsconfig.app.json +28 -0
  110. package/web/tsconfig.json +7 -0
  111. package/web/tsconfig.node.json +26 -0
  112. package/web/vite.config.ts +25 -0
  113. package/MONITORING-ACCURACY-FIX.md +0 -199
  114. package/PER-PROCESS-METRICS.md +0 -190
@@ -0,0 +1,213 @@
1
+ import * as path from 'path';
2
+ import { modelDownloader, DownloadProgress } from './model-downloader';
3
+ import { stateManager } from './state-manager';
4
+
5
+ export type DownloadJobStatus = 'pending' | 'downloading' | 'completed' | 'failed' | 'cancelled';
6
+
7
+ export interface DownloadJob {
8
+ id: string;
9
+ repo: string;
10
+ filename: string;
11
+ status: DownloadJobStatus;
12
+ progress: {
13
+ downloaded: number;
14
+ total: number;
15
+ percentage: number;
16
+ speed: string;
17
+ } | null;
18
+ error?: string;
19
+ createdAt: string;
20
+ completedAt?: string;
21
+ }
22
+
23
+ interface InternalJob extends DownloadJob {
24
+ abortController: AbortController;
25
+ }
26
+
27
+ /**
28
+ * Manages download jobs with progress tracking and cancellation support
29
+ */
30
+ class DownloadJobManager {
31
+ private jobs: Map<string, InternalJob> = new Map();
32
+ private jobCounter = 0;
33
+ private cleanupInterval: NodeJS.Timeout | null = null;
34
+
35
+ constructor() {
36
+ // Auto-cleanup completed/failed jobs after 5 minutes
37
+ this.cleanupInterval = setInterval(() => this.cleanupOldJobs(), 60000);
38
+ }
39
+
40
+ /**
41
+ * Create a new download job
42
+ */
43
+ createJob(repo: string, filename: string): string {
44
+ const id = `download-${Date.now()}-${++this.jobCounter}`;
45
+ const abortController = new AbortController();
46
+
47
+ const job: InternalJob = {
48
+ id,
49
+ repo,
50
+ filename,
51
+ status: 'pending',
52
+ progress: null,
53
+ createdAt: new Date().toISOString(),
54
+ abortController,
55
+ };
56
+
57
+ this.jobs.set(id, job);
58
+
59
+ // Start download asynchronously
60
+ this.startDownload(job);
61
+
62
+ return id;
63
+ }
64
+
65
+ /**
66
+ * Get a job by ID
67
+ */
68
+ getJob(id: string): DownloadJob | null {
69
+ const job = this.jobs.get(id);
70
+ if (!job) return null;
71
+
72
+ // Return public job info (without abortController)
73
+ return this.toPublicJob(job);
74
+ }
75
+
76
+ /**
77
+ * List all jobs
78
+ */
79
+ listJobs(): DownloadJob[] {
80
+ return Array.from(this.jobs.values()).map(job => this.toPublicJob(job));
81
+ }
82
+
83
+ /**
84
+ * Cancel a download job
85
+ */
86
+ cancelJob(id: string): boolean {
87
+ const job = this.jobs.get(id);
88
+ if (!job) return false;
89
+
90
+ if (job.status === 'pending' || job.status === 'downloading') {
91
+ job.abortController.abort();
92
+ job.status = 'cancelled';
93
+ job.completedAt = new Date().toISOString();
94
+ return true;
95
+ }
96
+
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * Delete a job from the list
102
+ */
103
+ deleteJob(id: string): boolean {
104
+ const job = this.jobs.get(id);
105
+ if (!job) return false;
106
+
107
+ // Cancel if still running
108
+ if (job.status === 'pending' || job.status === 'downloading') {
109
+ job.abortController.abort();
110
+ }
111
+
112
+ this.jobs.delete(id);
113
+ return true;
114
+ }
115
+
116
+ /**
117
+ * Start the download process for a job
118
+ */
119
+ private async startDownload(job: InternalJob): Promise<void> {
120
+ job.status = 'downloading';
121
+
122
+ try {
123
+ const modelsDir = await stateManager.getModelsDirectory();
124
+
125
+ await modelDownloader.downloadModel(
126
+ job.repo,
127
+ job.filename,
128
+ (progress: DownloadProgress) => {
129
+ job.progress = {
130
+ downloaded: progress.downloaded,
131
+ total: progress.total,
132
+ percentage: progress.percentage,
133
+ speed: progress.speed,
134
+ };
135
+ },
136
+ modelsDir,
137
+ {
138
+ silent: true,
139
+ signal: job.abortController.signal,
140
+ }
141
+ );
142
+
143
+ // Only mark as completed if not cancelled
144
+ if (job.status === 'downloading') {
145
+ job.status = 'completed';
146
+ job.completedAt = new Date().toISOString();
147
+ // Ensure progress shows 100%
148
+ if (job.progress) {
149
+ job.progress.percentage = 100;
150
+ }
151
+ }
152
+ } catch (error) {
153
+ // Check if this was a cancellation (status may have been set by cancelJob)
154
+ const currentStatus = job.status as DownloadJobStatus;
155
+ if (currentStatus === 'cancelled') {
156
+ return;
157
+ }
158
+
159
+ const message = (error as Error).message;
160
+ if (message.includes('cancelled') || message.includes('interrupted')) {
161
+ job.status = 'cancelled';
162
+ } else {
163
+ job.status = 'failed';
164
+ job.error = message;
165
+ }
166
+ job.completedAt = new Date().toISOString();
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Convert internal job to public job (strips internal fields)
172
+ */
173
+ private toPublicJob(job: InternalJob): DownloadJob {
174
+ const { abortController, ...publicJob } = job;
175
+ return publicJob;
176
+ }
177
+
178
+ /**
179
+ * Clean up old completed/failed jobs
180
+ */
181
+ private cleanupOldJobs(): void {
182
+ const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
183
+
184
+ for (const [id, job] of this.jobs.entries()) {
185
+ if (
186
+ job.completedAt &&
187
+ new Date(job.completedAt).getTime() < fiveMinutesAgo
188
+ ) {
189
+ this.jobs.delete(id);
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Cleanup on shutdown
196
+ */
197
+ shutdown(): void {
198
+ if (this.cleanupInterval) {
199
+ clearInterval(this.cleanupInterval);
200
+ this.cleanupInterval = null;
201
+ }
202
+
203
+ // Cancel all active downloads
204
+ for (const job of this.jobs.values()) {
205
+ if (job.status === 'pending' || job.status === 'downloading') {
206
+ job.abortController.abort();
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ // Export singleton instance
213
+ export const downloadJobManager = new DownloadJobManager();
@@ -16,7 +16,7 @@ import { portManager } from '../lib/port-manager.js';
16
16
  import { configGenerator, ServerOptions } from '../lib/config-generator.js';
17
17
  import { ModelInfo } from '../types/model-info.js';
18
18
  import { getLogsDir, getLaunchAgentsDir, ensureDir, parseMetalMemoryFromLog } from '../utils/file-utils.js';
19
- import { formatBytes } from '../utils/format-utils.js';
19
+ import { formatBytes, formatContextSize } from '../utils/format-utils.js';
20
20
  import { isPortInUse } from '../utils/process-utils.js';
21
21
 
22
22
  type ViewMode = 'list' | 'detail';
@@ -41,7 +41,7 @@ export async function createMultiServerMonitorUI(
41
41
  onModels?: (controls: MonitorUIControls) => void,
42
42
  onFirstRender?: () => void
43
43
  ): Promise<MonitorUIControls> {
44
- let updateInterval = 2000;
44
+ let updateInterval = 5000;
45
45
  let intervalId: NodeJS.Timeout | null = null;
46
46
  let viewMode: ViewMode = directJumpIndex !== undefined ? 'detail' : 'list';
47
47
  let selectedServerIndex = directJumpIndex ?? 0;
@@ -467,7 +467,7 @@ export async function createMultiServerMonitorUI(
467
467
 
468
468
  content += `Model: ${server.modelName}`;
469
469
  if (data.server.contextSize) {
470
- content += ` Context: ${data.server.contextSize} tokens`;
470
+ content += ` Context: ${formatContextSize(data.server.contextSize)}/slot`;
471
471
  }
472
472
  content += '\n';
473
473
 
@@ -0,0 +1,25 @@
1
+ export type AdminStatus = 'running' | 'stopped' | 'crashed';
2
+
3
+ export interface AdminConfig {
4
+ id: 'admin';
5
+ port: number;
6
+ host: string;
7
+ apiKey: string; // Auto-generated on first start
8
+
9
+ // State tracking
10
+ status: AdminStatus;
11
+ pid?: number;
12
+ createdAt: string;
13
+ lastStarted?: string;
14
+ lastStopped?: string;
15
+
16
+ // launchctl metadata
17
+ plistPath: string;
18
+ label: 'com.llama.admin';
19
+ stdoutPath: string;
20
+ stderrPath: string;
21
+
22
+ // Admin settings
23
+ requestTimeout: number; // ms for API requests (default: 30000)
24
+ verbose: boolean; // Enable verbose logging to file (default: false)
25
+ }
@@ -78,3 +78,21 @@ export function truncate(str: string, maxLength: number): string {
78
78
  export function pad(str: string, length: number, char = ' '): string {
79
79
  return str.padEnd(length, char);
80
80
  }
81
+
82
+ /**
83
+ * Format context size to human-readable format
84
+ * Uses "k" suffix for clean multiples of 1024 (e.g., 32768 → "32k")
85
+ * Falls back to full number with "tokens" for non-standard sizes
86
+ */
87
+ export function formatContextSize(tokens: number): string {
88
+ // Check if it's a clean multiple of 1024
89
+ if (tokens % 1024 === 0) {
90
+ const k = tokens / 1024;
91
+ // Only use "k" format for reasonable sizes (1k to 1024k i.e., up to 1M)
92
+ if (k >= 1 && k <= 1024) {
93
+ return `${k}k`;
94
+ }
95
+ }
96
+ // For non-standard sizes or very large values, show full number
97
+ return `${tokens.toLocaleString()} tokens`;
98
+ }
@@ -18,6 +18,19 @@ export class LogParser {
18
18
  private buffer: string[] = [];
19
19
  private isBuffering = false;
20
20
 
21
+ /**
22
+ * Health check endpoints to filter out by default
23
+ * These are polled frequently by the TUI and generate excessive log noise
24
+ */
25
+ private static readonly HEALTH_CHECK_ENDPOINTS = ['/health', '/slots', '/props'];
26
+
27
+ /**
28
+ * Check if a log line represents a health check request
29
+ */
30
+ isHealthCheckRequest(line: string): boolean {
31
+ return LogParser.HEALTH_CHECK_ENDPOINTS.some(ep => line.includes(`GET ${ep} `));
32
+ }
33
+
21
34
  /**
22
35
  * Check if line is a request status line (contains method/endpoint/status, no JSON)
23
36
  * Handles both old and new formats: