@appkit/llamacpp-cli 1.12.0 → 1.13.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 (136) hide show
  1. package/README.md +294 -168
  2. package/dist/cli.js +35 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/launch/claude.d.ts +6 -0
  5. package/dist/commands/launch/claude.d.ts.map +1 -0
  6. package/dist/commands/launch/claude.js +277 -0
  7. package/dist/commands/launch/claude.js.map +1 -0
  8. package/dist/lib/integration-checker.d.ts +26 -0
  9. package/dist/lib/integration-checker.d.ts.map +1 -0
  10. package/dist/lib/integration-checker.js +77 -0
  11. package/dist/lib/integration-checker.js.map +1 -0
  12. package/dist/lib/router-manager.d.ts +4 -0
  13. package/dist/lib/router-manager.d.ts.map +1 -1
  14. package/dist/lib/router-manager.js +10 -0
  15. package/dist/lib/router-manager.js.map +1 -1
  16. package/dist/lib/router-server.d.ts +13 -0
  17. package/dist/lib/router-server.d.ts.map +1 -1
  18. package/dist/lib/router-server.js +267 -7
  19. package/dist/lib/router-server.js.map +1 -1
  20. package/dist/types/integration-config.d.ts +28 -0
  21. package/dist/types/integration-config.d.ts.map +1 -0
  22. package/dist/types/integration-config.js +3 -0
  23. package/dist/types/integration-config.js.map +1 -0
  24. package/package.json +10 -2
  25. package/web/dist/assets/index-Bin89Lwr.css +1 -0
  26. package/web/dist/assets/index-CVmonw3T.js +17 -0
  27. package/web/{index.html → dist/index.html} +2 -1
  28. package/.versionrc.json +0 -16
  29. package/CHANGELOG.md +0 -213
  30. package/docs/images/.gitkeep +0 -1
  31. package/docs/images/web-ui-servers.png +0 -0
  32. package/src/cli.ts +0 -523
  33. package/src/commands/admin/config.ts +0 -121
  34. package/src/commands/admin/logs.ts +0 -91
  35. package/src/commands/admin/restart.ts +0 -26
  36. package/src/commands/admin/start.ts +0 -27
  37. package/src/commands/admin/status.ts +0 -84
  38. package/src/commands/admin/stop.ts +0 -16
  39. package/src/commands/config-global.ts +0 -38
  40. package/src/commands/config.ts +0 -323
  41. package/src/commands/create.ts +0 -183
  42. package/src/commands/delete.ts +0 -74
  43. package/src/commands/list.ts +0 -37
  44. package/src/commands/logs-all.ts +0 -251
  45. package/src/commands/logs.ts +0 -345
  46. package/src/commands/monitor.ts +0 -110
  47. package/src/commands/ps.ts +0 -84
  48. package/src/commands/pull.ts +0 -44
  49. package/src/commands/rm.ts +0 -107
  50. package/src/commands/router/config.ts +0 -116
  51. package/src/commands/router/logs.ts +0 -256
  52. package/src/commands/router/restart.ts +0 -36
  53. package/src/commands/router/start.ts +0 -60
  54. package/src/commands/router/status.ts +0 -119
  55. package/src/commands/router/stop.ts +0 -33
  56. package/src/commands/run.ts +0 -233
  57. package/src/commands/search.ts +0 -107
  58. package/src/commands/server-show.ts +0 -161
  59. package/src/commands/show.ts +0 -207
  60. package/src/commands/start.ts +0 -101
  61. package/src/commands/stop.ts +0 -39
  62. package/src/commands/tui.ts +0 -25
  63. package/src/lib/admin-manager.ts +0 -435
  64. package/src/lib/admin-server.ts +0 -1243
  65. package/src/lib/config-generator.ts +0 -130
  66. package/src/lib/download-job-manager.ts +0 -213
  67. package/src/lib/history-manager.ts +0 -172
  68. package/src/lib/launchctl-manager.ts +0 -225
  69. package/src/lib/metrics-aggregator.ts +0 -257
  70. package/src/lib/model-downloader.ts +0 -328
  71. package/src/lib/model-scanner.ts +0 -157
  72. package/src/lib/model-search.ts +0 -114
  73. package/src/lib/models-dir-setup.ts +0 -46
  74. package/src/lib/port-manager.ts +0 -80
  75. package/src/lib/router-logger.ts +0 -201
  76. package/src/lib/router-manager.ts +0 -414
  77. package/src/lib/router-server.ts +0 -538
  78. package/src/lib/state-manager.ts +0 -206
  79. package/src/lib/status-checker.ts +0 -113
  80. package/src/lib/system-collector.ts +0 -315
  81. package/src/tui/ConfigApp.ts +0 -1085
  82. package/src/tui/HistoricalMonitorApp.ts +0 -587
  83. package/src/tui/ModelsApp.ts +0 -368
  84. package/src/tui/MonitorApp.ts +0 -386
  85. package/src/tui/MultiServerMonitorApp.ts +0 -1833
  86. package/src/tui/RootNavigator.ts +0 -74
  87. package/src/tui/SearchApp.ts +0 -511
  88. package/src/tui/SplashScreen.ts +0 -149
  89. package/src/types/admin-config.ts +0 -25
  90. package/src/types/global-config.ts +0 -26
  91. package/src/types/history-types.ts +0 -39
  92. package/src/types/model-info.ts +0 -8
  93. package/src/types/monitor-types.ts +0 -162
  94. package/src/types/router-config.ts +0 -25
  95. package/src/types/server-config.ts +0 -46
  96. package/src/utils/downsample-utils.ts +0 -128
  97. package/src/utils/file-utils.ts +0 -146
  98. package/src/utils/format-utils.ts +0 -98
  99. package/src/utils/log-parser.ts +0 -284
  100. package/src/utils/log-utils.ts +0 -178
  101. package/src/utils/process-utils.ts +0 -316
  102. package/src/utils/prompt-utils.ts +0 -47
  103. package/test-load.sh +0 -100
  104. package/tsconfig.json +0 -20
  105. package/web/eslint.config.js +0 -23
  106. package/web/llamacpp-web-dist.tar.gz +0 -0
  107. package/web/package-lock.json +0 -4017
  108. package/web/package.json +0 -38
  109. package/web/postcss.config.js +0 -6
  110. package/web/src/App.css +0 -42
  111. package/web/src/App.tsx +0 -86
  112. package/web/src/assets/react.svg +0 -1
  113. package/web/src/components/ApiKeyPrompt.tsx +0 -71
  114. package/web/src/components/CreateServerModal.tsx +0 -372
  115. package/web/src/components/DownloadProgress.tsx +0 -123
  116. package/web/src/components/Nav.tsx +0 -89
  117. package/web/src/components/RouterConfigModal.tsx +0 -240
  118. package/web/src/components/SearchModal.tsx +0 -306
  119. package/web/src/components/ServerConfigModal.tsx +0 -291
  120. package/web/src/hooks/useApi.ts +0 -259
  121. package/web/src/index.css +0 -42
  122. package/web/src/lib/api.ts +0 -226
  123. package/web/src/main.tsx +0 -10
  124. package/web/src/pages/Dashboard.tsx +0 -103
  125. package/web/src/pages/Models.tsx +0 -258
  126. package/web/src/pages/Router.tsx +0 -270
  127. package/web/src/pages/RouterLogs.tsx +0 -201
  128. package/web/src/pages/ServerLogs.tsx +0 -553
  129. package/web/src/pages/Servers.tsx +0 -358
  130. package/web/src/types/api.ts +0 -140
  131. package/web/tailwind.config.js +0 -31
  132. package/web/tsconfig.app.json +0 -28
  133. package/web/tsconfig.json +0 -7
  134. package/web/tsconfig.node.json +0 -26
  135. package/web/vite.config.ts +0 -25
  136. /package/web/{public → dist}/vite.svg +0 -0
@@ -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
- }