@appkit/llamacpp-cli 1.11.0 → 1.12.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.
Files changed (126) hide show
  1. package/README.md +572 -170
  2. package/dist/cli.js +99 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/admin/config.d.ts +10 -0
  5. package/dist/commands/admin/config.d.ts.map +1 -0
  6. package/dist/commands/admin/config.js +100 -0
  7. package/dist/commands/admin/config.js.map +1 -0
  8. package/dist/commands/admin/logs.d.ts +10 -0
  9. package/dist/commands/admin/logs.d.ts.map +1 -0
  10. package/dist/commands/admin/logs.js +114 -0
  11. package/dist/commands/admin/logs.js.map +1 -0
  12. package/dist/commands/admin/restart.d.ts +2 -0
  13. package/dist/commands/admin/restart.d.ts.map +1 -0
  14. package/dist/commands/admin/restart.js +29 -0
  15. package/dist/commands/admin/restart.js.map +1 -0
  16. package/dist/commands/admin/start.d.ts +2 -0
  17. package/dist/commands/admin/start.d.ts.map +1 -0
  18. package/dist/commands/admin/start.js +30 -0
  19. package/dist/commands/admin/start.js.map +1 -0
  20. package/dist/commands/admin/status.d.ts +2 -0
  21. package/dist/commands/admin/status.d.ts.map +1 -0
  22. package/dist/commands/admin/status.js +82 -0
  23. package/dist/commands/admin/status.js.map +1 -0
  24. package/dist/commands/admin/stop.d.ts +2 -0
  25. package/dist/commands/admin/stop.d.ts.map +1 -0
  26. package/dist/commands/admin/stop.js +21 -0
  27. package/dist/commands/admin/stop.js.map +1 -0
  28. package/dist/commands/logs.d.ts +1 -0
  29. package/dist/commands/logs.d.ts.map +1 -1
  30. package/dist/commands/logs.js +22 -0
  31. package/dist/commands/logs.js.map +1 -1
  32. package/dist/lib/admin-manager.d.ts +111 -0
  33. package/dist/lib/admin-manager.d.ts.map +1 -0
  34. package/dist/lib/admin-manager.js +413 -0
  35. package/dist/lib/admin-manager.js.map +1 -0
  36. package/dist/lib/admin-server.d.ts +148 -0
  37. package/dist/lib/admin-server.d.ts.map +1 -0
  38. package/dist/lib/admin-server.js +1161 -0
  39. package/dist/lib/admin-server.js.map +1 -0
  40. package/dist/lib/download-job-manager.d.ts +64 -0
  41. package/dist/lib/download-job-manager.d.ts.map +1 -0
  42. package/dist/lib/download-job-manager.js +164 -0
  43. package/dist/lib/download-job-manager.js.map +1 -0
  44. package/dist/tui/MultiServerMonitorApp.js +1 -1
  45. package/dist/types/admin-config.d.ts +19 -0
  46. package/dist/types/admin-config.d.ts.map +1 -0
  47. package/dist/types/admin-config.js +3 -0
  48. package/dist/types/admin-config.js.map +1 -0
  49. package/dist/utils/log-parser.d.ts +9 -0
  50. package/dist/utils/log-parser.d.ts.map +1 -1
  51. package/dist/utils/log-parser.js +11 -0
  52. package/dist/utils/log-parser.js.map +1 -1
  53. package/package.json +10 -2
  54. package/web/README.md +429 -0
  55. package/web/dist/assets/index-Bin89Lwr.css +1 -0
  56. package/web/dist/assets/index-CVmonw3T.js +17 -0
  57. package/web/dist/index.html +14 -0
  58. package/web/dist/vite.svg +1 -0
  59. package/.versionrc.json +0 -16
  60. package/CHANGELOG.md +0 -203
  61. package/MONITORING-ACCURACY-FIX.md +0 -199
  62. package/PER-PROCESS-METRICS.md +0 -190
  63. package/docs/images/.gitkeep +0 -1
  64. package/src/cli.ts +0 -423
  65. package/src/commands/config-global.ts +0 -38
  66. package/src/commands/config.ts +0 -323
  67. package/src/commands/create.ts +0 -183
  68. package/src/commands/delete.ts +0 -74
  69. package/src/commands/list.ts +0 -37
  70. package/src/commands/logs-all.ts +0 -251
  71. package/src/commands/logs.ts +0 -321
  72. package/src/commands/monitor.ts +0 -110
  73. package/src/commands/ps.ts +0 -84
  74. package/src/commands/pull.ts +0 -44
  75. package/src/commands/rm.ts +0 -107
  76. package/src/commands/router/config.ts +0 -116
  77. package/src/commands/router/logs.ts +0 -256
  78. package/src/commands/router/restart.ts +0 -36
  79. package/src/commands/router/start.ts +0 -60
  80. package/src/commands/router/status.ts +0 -119
  81. package/src/commands/router/stop.ts +0 -33
  82. package/src/commands/run.ts +0 -233
  83. package/src/commands/search.ts +0 -107
  84. package/src/commands/server-show.ts +0 -161
  85. package/src/commands/show.ts +0 -207
  86. package/src/commands/start.ts +0 -101
  87. package/src/commands/stop.ts +0 -39
  88. package/src/commands/tui.ts +0 -25
  89. package/src/lib/config-generator.ts +0 -130
  90. package/src/lib/history-manager.ts +0 -172
  91. package/src/lib/launchctl-manager.ts +0 -225
  92. package/src/lib/metrics-aggregator.ts +0 -257
  93. package/src/lib/model-downloader.ts +0 -328
  94. package/src/lib/model-scanner.ts +0 -157
  95. package/src/lib/model-search.ts +0 -114
  96. package/src/lib/models-dir-setup.ts +0 -46
  97. package/src/lib/port-manager.ts +0 -80
  98. package/src/lib/router-logger.ts +0 -201
  99. package/src/lib/router-manager.ts +0 -414
  100. package/src/lib/router-server.ts +0 -538
  101. package/src/lib/state-manager.ts +0 -206
  102. package/src/lib/status-checker.ts +0 -113
  103. package/src/lib/system-collector.ts +0 -315
  104. package/src/tui/ConfigApp.ts +0 -1085
  105. package/src/tui/HistoricalMonitorApp.ts +0 -587
  106. package/src/tui/ModelsApp.ts +0 -368
  107. package/src/tui/MonitorApp.ts +0 -386
  108. package/src/tui/MultiServerMonitorApp.ts +0 -1833
  109. package/src/tui/RootNavigator.ts +0 -74
  110. package/src/tui/SearchApp.ts +0 -511
  111. package/src/tui/SplashScreen.ts +0 -149
  112. package/src/types/global-config.ts +0 -26
  113. package/src/types/history-types.ts +0 -39
  114. package/src/types/model-info.ts +0 -8
  115. package/src/types/monitor-types.ts +0 -162
  116. package/src/types/router-config.ts +0 -25
  117. package/src/types/server-config.ts +0 -46
  118. package/src/utils/downsample-utils.ts +0 -128
  119. package/src/utils/file-utils.ts +0 -146
  120. package/src/utils/format-utils.ts +0 -98
  121. package/src/utils/log-parser.ts +0 -271
  122. package/src/utils/log-utils.ts +0 -178
  123. package/src/utils/process-utils.ts +0 -316
  124. package/src/utils/prompt-utils.ts +0 -47
  125. package/test-load.sh +0 -100
  126. package/tsconfig.json +0 -20
@@ -1,328 +0,0 @@
1
- import * as https from 'https';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import chalk from 'chalk';
5
- import { getModelsDir } from '../utils/file-utils';
6
- import { formatBytes } from '../utils/format-utils';
7
-
8
- export interface DownloadProgress {
9
- filename: string;
10
- downloaded: number;
11
- total: number;
12
- percentage: number;
13
- speed: string;
14
- }
15
-
16
- export interface DownloadOptions {
17
- silent?: boolean; // Suppress console output (for TUI)
18
- signal?: AbortSignal; // Abort signal for cancellation
19
- }
20
-
21
- export class ModelDownloader {
22
- private modelsDir?: string;
23
- private getModelsDirFn?: () => Promise<string>;
24
-
25
- constructor(modelsDir?: string, getModelsDirFn?: () => Promise<string>) {
26
- this.modelsDir = modelsDir;
27
- this.getModelsDirFn = getModelsDirFn;
28
- }
29
-
30
- /**
31
- * Get the models directory (either configured or default)
32
- */
33
- private async getModelsDirectory(): Promise<string> {
34
- if (this.modelsDir) {
35
- return this.modelsDir;
36
- }
37
- if (this.getModelsDirFn) {
38
- return await this.getModelsDirFn();
39
- }
40
- return getModelsDir();
41
- }
42
-
43
- /**
44
- * Parse Hugging Face identifier
45
- * Examples:
46
- * "bartowski/Llama-3.2-3B-Instruct-GGUF" → { repo: "...", file: undefined }
47
- * "bartowski/Llama-3.2-3B-Instruct-GGUF/file.gguf" → { repo: "...", file: "file.gguf" }
48
- */
49
- parseHFIdentifier(identifier: string): { repo: string; file?: string } {
50
- const parts = identifier.split('/');
51
- if (parts.length === 2) {
52
- return { repo: identifier };
53
- } else if (parts.length === 3) {
54
- return {
55
- repo: `${parts[0]}/${parts[1]}`,
56
- file: parts[2],
57
- };
58
- } else {
59
- throw new Error(`Invalid Hugging Face identifier: ${identifier}`);
60
- }
61
- }
62
-
63
- /**
64
- * Build Hugging Face download URL
65
- */
66
- buildDownloadUrl(repoId: string, filename: string, branch = 'main'): string {
67
- return `https://huggingface.co/${repoId}/resolve/${branch}/${filename}`;
68
- }
69
-
70
- /**
71
- * Download a file via HTTPS with progress tracking
72
- */
73
- private downloadFile(
74
- url: string,
75
- destPath: string,
76
- onProgress?: (downloaded: number, total: number) => void,
77
- signal?: AbortSignal
78
- ): Promise<void> {
79
- return new Promise((resolve, reject) => {
80
- const file = fs.createWriteStream(destPath);
81
- let downloadedBytes = 0;
82
- let totalBytes = 0;
83
- let lastUpdateTime = Date.now();
84
- let lastDownloadedBytes = 0;
85
- let completed = false;
86
- let request: ReturnType<typeof https.get> | null = null;
87
-
88
- const cleanup = (sigintHandler?: () => void) => {
89
- if (sigintHandler) {
90
- process.removeListener('SIGINT', sigintHandler);
91
- }
92
- };
93
-
94
- const handleError = (err: Error, sigintHandler?: () => void) => {
95
- if (completed) return;
96
- completed = true;
97
- cleanup(sigintHandler);
98
- file.close(() => {
99
- fs.unlink(destPath, () => {});
100
- });
101
- reject(err);
102
- };
103
-
104
- const sigintHandler = () => {
105
- if (request) request.destroy();
106
- handleError(new Error('Download interrupted by user'), sigintHandler);
107
- };
108
-
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) => {
124
- // Handle redirects (301, 302, 307, 308)
125
- if (response.statusCode === 301 || response.statusCode === 302 ||
126
- response.statusCode === 307 || response.statusCode === 308) {
127
- const redirectUrl = response.headers.location;
128
- if (redirectUrl) {
129
- cleanup(sigintHandler);
130
- if (signal) signal.removeEventListener('abort', abortHandler);
131
- // Wait for file to close before starting new download
132
- file.close(() => {
133
- fs.unlink(destPath, () => {
134
- // Start recursive download only after cleanup is complete
135
- this.downloadFile(redirectUrl, destPath, onProgress, signal)
136
- .then(resolve)
137
- .catch(reject);
138
- });
139
- });
140
- return;
141
- }
142
- }
143
-
144
- if (response.statusCode !== 200) {
145
- return handleError(
146
- new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`),
147
- sigintHandler
148
- );
149
- }
150
-
151
- totalBytes = parseInt(response.headers['content-length'] || '0', 10);
152
-
153
- response.on('data', (chunk: Buffer) => {
154
- downloadedBytes += chunk.length;
155
-
156
- // Update progress every 500ms
157
- const now = Date.now();
158
- if (onProgress && now - lastUpdateTime >= 500) {
159
- onProgress(downloadedBytes, totalBytes);
160
- lastUpdateTime = now;
161
- lastDownloadedBytes = downloadedBytes;
162
- }
163
- });
164
-
165
- response.pipe(file);
166
-
167
- file.on('finish', () => {
168
- if (completed) return;
169
- completed = true;
170
-
171
- // Final progress update
172
- if (onProgress) {
173
- onProgress(downloadedBytes, totalBytes);
174
- }
175
-
176
- // Use callback to ensure close completes before resolving
177
- file.close((err) => {
178
- cleanup(sigintHandler);
179
- if (signal) signal.removeEventListener('abort', abortHandler);
180
- if (err) reject(err);
181
- else resolve();
182
- });
183
- });
184
- });
185
-
186
- request.on('error', (err) => {
187
- if (signal) signal.removeEventListener('abort', abortHandler);
188
- handleError(err, sigintHandler);
189
- });
190
-
191
- file.on('error', (err) => {
192
- if (signal) signal.removeEventListener('abort', abortHandler);
193
- handleError(err, sigintHandler);
194
- });
195
-
196
- // Handle Ctrl+C gracefully
197
- process.on('SIGINT', sigintHandler);
198
- });
199
- }
200
-
201
- /**
202
- * Display progress bar
203
- */
204
- private displayProgress(downloaded: number, total: number, filename: string): void {
205
- const percentage = total > 0 ? (downloaded / total) * 100 : 0;
206
- const barLength = 40;
207
- const filledLength = Math.round((barLength * downloaded) / total);
208
- const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
209
-
210
- const downloadedFormatted = formatBytes(downloaded);
211
- const totalFormatted = formatBytes(total);
212
- const percentFormatted = percentage.toFixed(1);
213
-
214
- // Clear line and print progress
215
- process.stdout.write('\r\x1b[K');
216
- process.stdout.write(
217
- chalk.blue(`[${bar}] ${percentFormatted}% | ${downloadedFormatted} / ${totalFormatted}`)
218
- );
219
- }
220
-
221
- /**
222
- * Download a model from Hugging Face
223
- */
224
- async downloadModel(
225
- repoId: string,
226
- filename: string,
227
- onProgress?: (progress: DownloadProgress) => void,
228
- modelsDir?: string,
229
- options?: DownloadOptions
230
- ): Promise<string> {
231
- const silent = options?.silent ?? false;
232
- const signal = options?.signal;
233
-
234
- // Use provided models directory or get from config
235
- const targetDir = modelsDir || await this.getModelsDirectory();
236
-
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
- }
243
-
244
- // Build download URL
245
- const url = this.buildDownloadUrl(repoId, filename);
246
- const destPath = path.join(targetDir, filename);
247
-
248
- // Check if file already exists
249
- if (fs.existsSync(destPath)) {
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
- }
254
- throw new Error('File already exists');
255
- }
256
-
257
- // Download with progress
258
- const startTime = Date.now();
259
- let lastDownloaded = 0;
260
- let lastTime = startTime;
261
-
262
- await this.downloadFile(url, destPath, (downloaded, total) => {
263
- // Calculate speed
264
- const now = Date.now();
265
- const timeDiff = (now - lastTime) / 1000; // seconds
266
- const bytesDiff = downloaded - lastDownloaded;
267
- const speed = timeDiff > 0 ? bytesDiff / timeDiff : 0;
268
-
269
- // Update for next calculation
270
- lastTime = now;
271
- lastDownloaded = downloaded;
272
-
273
- // Display progress bar (only if not silent)
274
- if (!silent) {
275
- this.displayProgress(downloaded, total, filename);
276
- }
277
-
278
- // Call user progress callback if provided
279
- if (onProgress) {
280
- onProgress({
281
- filename,
282
- downloaded,
283
- total,
284
- percentage: total > 0 ? (downloaded / total) * 100 : 0,
285
- speed: `${formatBytes(speed)}/s`,
286
- });
287
- }
288
- }, signal);
289
-
290
- if (!silent) {
291
- // Clear progress line and show completion
292
- process.stdout.write('\r\x1b[K');
293
- console.log(chalk.green('✅ Download complete!'));
294
-
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
- }
299
-
300
- return destPath;
301
- }
302
-
303
- /**
304
- * List GGUF files in a Hugging Face repository
305
- * (This would require calling the HF API - simplified for now)
306
- */
307
- async listGGUFFiles(repoId: string): Promise<string[]> {
308
- console.log(chalk.yellow('Listing files is not yet implemented.'));
309
- console.log(chalk.dim('Please specify the file with --file <filename>'));
310
- return [];
311
- }
312
- }
313
-
314
- // Create singleton that uses configured models directory
315
- // Use lazy import to avoid circular dependency
316
- let _modelDownloader: ModelDownloader | null = null;
317
-
318
- export function getModelDownloader(): ModelDownloader {
319
- if (!_modelDownloader) {
320
- // Import stateManager dynamically to avoid circular dependency
321
- const { stateManager } = require('./state-manager');
322
- _modelDownloader = new ModelDownloader(undefined, () => stateManager.getModelsDirectory());
323
- }
324
- return _modelDownloader;
325
- }
326
-
327
- // Export singleton instance for backward compatibility
328
- export const modelDownloader = getModelDownloader();
@@ -1,157 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
- import { ModelInfo } from '../types/model-info';
4
- import { getModelsDir } from '../utils/file-utils';
5
- import { formatBytes } from '../utils/format-utils';
6
-
7
- export class ModelScanner {
8
- private modelsDir?: string;
9
- private getModelsDirFn?: () => Promise<string>;
10
-
11
- constructor(modelsDir?: string, getModelsDirFn?: () => Promise<string>) {
12
- this.modelsDir = modelsDir;
13
- this.getModelsDirFn = getModelsDirFn;
14
- }
15
-
16
- /**
17
- * Get the models directory (either configured or default)
18
- */
19
- private async getModelsDirectory(): Promise<string> {
20
- if (this.modelsDir) {
21
- return this.modelsDir;
22
- }
23
- if (this.getModelsDirFn) {
24
- return await this.getModelsDirFn();
25
- }
26
- return getModelsDir();
27
- }
28
-
29
- /**
30
- * Scan models directory for GGUF files
31
- */
32
- async scanModels(): Promise<ModelInfo[]> {
33
- const modelsDir = await this.getModelsDirectory();
34
- try {
35
- const files = await fs.readdir(modelsDir);
36
- const ggufFiles = files.filter((f) => f.toLowerCase().endsWith('.gguf'));
37
-
38
- const models: ModelInfo[] = [];
39
- for (const file of ggufFiles) {
40
- const modelInfo = await this.getModelInfo(file);
41
- if (modelInfo) {
42
- models.push(modelInfo);
43
- }
44
- }
45
-
46
- // Sort by modified date (newest first)
47
- models.sort((a, b) => b.modified.getTime() - a.modified.getTime());
48
-
49
- return models;
50
- } catch (error) {
51
- // Models directory doesn't exist or is not accessible
52
- return [];
53
- }
54
- }
55
-
56
- /**
57
- * Get information about a specific model file
58
- */
59
- async getModelInfo(filename: string): Promise<ModelInfo | null> {
60
- const modelsDir = await this.getModelsDirectory();
61
- const modelPath = path.join(modelsDir, filename);
62
-
63
- try {
64
- const stats = await fs.stat(modelPath);
65
-
66
- return {
67
- filename,
68
- path: modelPath,
69
- size: stats.size,
70
- sizeFormatted: formatBytes(stats.size),
71
- modified: stats.mtime,
72
- exists: true,
73
- };
74
- } catch (error) {
75
- // File doesn't exist or is not accessible
76
- return {
77
- filename,
78
- path: modelPath,
79
- size: 0,
80
- sizeFormatted: '0 B',
81
- modified: new Date(),
82
- exists: false,
83
- };
84
- }
85
- }
86
-
87
- /**
88
- * Validate that a model file exists and is readable
89
- */
90
- async validateModel(filename: string): Promise<boolean> {
91
- const modelInfo = await this.getModelInfo(filename);
92
- return modelInfo !== null && modelInfo.exists && modelInfo.size > 0;
93
- }
94
-
95
- /**
96
- * Resolve a model filename to full path
97
- */
98
- async resolveModelPath(filename: string): Promise<string | null> {
99
- // If already absolute path, return it
100
- if (path.isAbsolute(filename)) {
101
- return filename;
102
- }
103
-
104
- const modelsDir = await this.getModelsDirectory();
105
-
106
- // Try in models directory
107
- const modelPath = path.join(modelsDir, filename);
108
- const modelInfo = await this.getModelInfo(filename);
109
-
110
- if (modelInfo && modelInfo.exists) {
111
- return modelPath;
112
- }
113
-
114
- // Try adding .gguf extension
115
- if (!filename.toLowerCase().endsWith('.gguf')) {
116
- const withExtension = `${filename}.gguf`;
117
- const modelInfoWithExt = await this.getModelInfo(withExtension);
118
- if (modelInfoWithExt && modelInfoWithExt.exists) {
119
- return path.join(modelsDir, withExtension);
120
- }
121
- }
122
-
123
- return null;
124
- }
125
-
126
- /**
127
- * Get the size of a model file
128
- */
129
- async getModelSize(filename: string): Promise<number | null> {
130
- const modelInfo = await this.getModelInfo(filename);
131
- return modelInfo?.size || null;
132
- }
133
-
134
- /**
135
- * Get total size of all models
136
- */
137
- async getTotalSize(): Promise<number> {
138
- const models = await this.scanModels();
139
- return models.reduce((total, model) => total + model.size, 0);
140
- }
141
- }
142
-
143
- // Create singleton that uses configured models directory
144
- // Use lazy import to avoid circular dependency
145
- let _modelScanner: ModelScanner | null = null;
146
-
147
- export function getModelScanner(): ModelScanner {
148
- if (!_modelScanner) {
149
- // Import stateManager dynamically to avoid circular dependency
150
- const { stateManager } = require('./state-manager');
151
- _modelScanner = new ModelScanner(undefined, () => stateManager.getModelsDirectory());
152
- }
153
- return _modelScanner;
154
- }
155
-
156
- // Export singleton instance for backward compatibility
157
- export const modelScanner = getModelScanner();
@@ -1,114 +0,0 @@
1
- import * as https from 'https';
2
-
3
- export interface HFModelResult {
4
- modelId: string;
5
- author: string;
6
- modelName: string;
7
- downloads: number;
8
- likes: number;
9
- tags: string[];
10
- lastModified: string;
11
- }
12
-
13
- export class ModelSearch {
14
- /**
15
- * Search Hugging Face for GGUF models
16
- */
17
- async searchModels(query: string, limit = 20): Promise<HFModelResult[]> {
18
- const searchUrl = this.buildSearchUrl(query, limit);
19
-
20
- return new Promise((resolve, reject) => {
21
- https.get(searchUrl, (response) => {
22
- let data = '';
23
-
24
- response.on('data', (chunk) => {
25
- data += chunk;
26
- });
27
-
28
- response.on('end', () => {
29
- try {
30
- const results = JSON.parse(data);
31
- const models = this.parseResults(results);
32
- resolve(models);
33
- } catch (error) {
34
- reject(new Error(`Failed to parse search results: ${(error as Error).message}`));
35
- }
36
- });
37
- }).on('error', (error) => {
38
- reject(new Error(`Search request failed: ${error.message}`));
39
- });
40
- });
41
- }
42
-
43
- /**
44
- * Build Hugging Face search URL
45
- */
46
- private buildSearchUrl(query: string, limit: number): string {
47
- const params = new URLSearchParams({
48
- search: query,
49
- filter: 'gguf',
50
- sort: 'downloads',
51
- direction: '-1',
52
- limit: limit.toString(),
53
- });
54
-
55
- return `https://huggingface.co/api/models?${params.toString()}`;
56
- }
57
-
58
- /**
59
- * Parse API results into our model format
60
- */
61
- private parseResults(results: any[]): HFModelResult[] {
62
- return results.map((result) => {
63
- const modelId = result.id || result.modelId || '';
64
- const parts = modelId.split('/');
65
- const author = parts[0] || '';
66
- const modelName = parts.slice(1).join('/') || '';
67
-
68
- return {
69
- modelId,
70
- author,
71
- modelName,
72
- downloads: result.downloads || 0,
73
- likes: result.likes || 0,
74
- tags: result.tags || [],
75
- lastModified: result.lastModified || '',
76
- };
77
- });
78
- }
79
-
80
- /**
81
- * Get GGUF files for a specific model
82
- */
83
- async getModelFiles(modelId: string): Promise<string[]> {
84
- const apiUrl = `https://huggingface.co/api/models/${modelId}`;
85
-
86
- return new Promise((resolve, reject) => {
87
- https.get(apiUrl, (response) => {
88
- let data = '';
89
-
90
- response.on('data', (chunk) => {
91
- data += chunk;
92
- });
93
-
94
- response.on('end', () => {
95
- try {
96
- const modelInfo = JSON.parse(data);
97
- const files = modelInfo.siblings || [];
98
- const ggufFiles = files
99
- .filter((file: any) => file.rfilename?.toLowerCase().endsWith('.gguf'))
100
- .map((file: any) => file.rfilename);
101
- resolve(ggufFiles);
102
- } catch (error) {
103
- reject(new Error(`Failed to fetch model files: ${(error as Error).message}`));
104
- }
105
- });
106
- }).on('error', (error) => {
107
- reject(new Error(`API request failed: ${error.message}`));
108
- });
109
- });
110
- }
111
- }
112
-
113
- // Export singleton instance
114
- export const modelSearch = new ModelSearch();
@@ -1,46 +0,0 @@
1
- import * as fs from 'fs';
2
- import chalk from 'chalk';
3
- import { stateManager } from './state-manager';
4
- import { expandHome } from '../utils/file-utils';
5
- import { prompt } from '../utils/prompt-utils';
6
-
7
- /**
8
- * Ensure models directory exists, prompting user if needed
9
- * Returns the final models directory path
10
- */
11
- export async function ensureModelsDirectory(): Promise<string> {
12
- const configuredPath = await stateManager.getModelsDirectory();
13
-
14
- // If directory exists, we're good
15
- if (fs.existsSync(configuredPath)) {
16
- return configuredPath;
17
- }
18
-
19
- // Directory doesn't exist - prompt user
20
- console.log(chalk.yellow('⚠️ Models directory not found'));
21
- console.log();
22
- console.log(chalk.dim('The models directory is where GGUF model files are stored.'));
23
- console.log(chalk.dim(`Configured path: ${configuredPath}`));
24
- console.log();
25
-
26
- const answer = await prompt(
27
- 'Enter models directory path (press Enter to use default)',
28
- configuredPath
29
- );
30
-
31
- const finalPath = expandHome(answer);
32
-
33
- // If user changed the path, update config
34
- if (finalPath !== configuredPath) {
35
- console.log(chalk.dim(`Updating configuration to: ${finalPath}`));
36
- await stateManager.setModelsDirectory(finalPath);
37
- }
38
-
39
- // Create the directory
40
- console.log(chalk.dim(`Creating directory: ${finalPath}`));
41
- fs.mkdirSync(finalPath, { recursive: true, mode: 0o755 });
42
- console.log(chalk.green('✅ Models directory created'));
43
- console.log();
44
-
45
- return finalPath;
46
- }