@appkit/llamacpp-cli 1.3.2 → 1.4.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 (50) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/cli.js +16 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/config-global.d.ts +6 -0
  5. package/dist/commands/config-global.d.ts.map +1 -0
  6. package/dist/commands/config-global.js +38 -0
  7. package/dist/commands/config-global.js.map +1 -0
  8. package/dist/commands/create.d.ts.map +1 -1
  9. package/dist/commands/create.js +15 -6
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/list.js +2 -2
  12. package/dist/commands/list.js.map +1 -1
  13. package/dist/commands/pull.d.ts.map +1 -1
  14. package/dist/commands/pull.js +4 -1
  15. package/dist/commands/pull.js.map +1 -1
  16. package/dist/lib/model-downloader.d.ts +9 -3
  17. package/dist/lib/model-downloader.d.ts.map +1 -1
  18. package/dist/lib/model-downloader.js +34 -7
  19. package/dist/lib/model-downloader.js.map +1 -1
  20. package/dist/lib/model-scanner.d.ts +8 -2
  21. package/dist/lib/model-scanner.d.ts.map +1 -1
  22. package/dist/lib/model-scanner.js +36 -8
  23. package/dist/lib/model-scanner.js.map +1 -1
  24. package/dist/lib/models-dir-setup.d.ts +6 -0
  25. package/dist/lib/models-dir-setup.d.ts.map +1 -0
  26. package/dist/lib/models-dir-setup.js +75 -0
  27. package/dist/lib/models-dir-setup.js.map +1 -0
  28. package/dist/lib/state-manager.d.ts +8 -0
  29. package/dist/lib/state-manager.d.ts.map +1 -1
  30. package/dist/lib/state-manager.js +15 -0
  31. package/dist/lib/state-manager.js.map +1 -1
  32. package/dist/utils/file-utils.d.ts +1 -1
  33. package/dist/utils/file-utils.js +2 -2
  34. package/dist/utils/file-utils.js.map +1 -1
  35. package/dist/utils/prompt-utils.d.ts +9 -0
  36. package/dist/utils/prompt-utils.d.ts.map +1 -0
  37. package/dist/utils/prompt-utils.js +79 -0
  38. package/dist/utils/prompt-utils.js.map +1 -0
  39. package/package.json +2 -2
  40. package/src/cli.ts +16 -1
  41. package/src/commands/config-global.ts +38 -0
  42. package/src/commands/create.ts +16 -6
  43. package/src/commands/list.ts +2 -2
  44. package/src/commands/pull.ts +5 -1
  45. package/src/lib/model-downloader.ts +40 -8
  46. package/src/lib/model-scanner.ts +41 -9
  47. package/src/lib/models-dir-setup.ts +46 -0
  48. package/src/lib/state-manager.ts +17 -0
  49. package/src/utils/file-utils.ts +2 -2
  50. package/src/utils/prompt-utils.ts +47 -0
@@ -5,18 +5,34 @@ import { getModelsDir } from '../utils/file-utils';
5
5
  import { formatBytes } from '../utils/format-utils';
6
6
 
7
7
  export class ModelScanner {
8
- private modelsDir: string;
8
+ private modelsDir?: string;
9
+ private getModelsDirFn?: () => Promise<string>;
9
10
 
10
- constructor(modelsDir?: string) {
11
- this.modelsDir = modelsDir || getModelsDir();
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();
12
27
  }
13
28
 
14
29
  /**
15
30
  * Scan models directory for GGUF files
16
31
  */
17
32
  async scanModels(): Promise<ModelInfo[]> {
33
+ const modelsDir = await this.getModelsDirectory();
18
34
  try {
19
- const files = await fs.readdir(this.modelsDir);
35
+ const files = await fs.readdir(modelsDir);
20
36
  const ggufFiles = files.filter((f) => f.toLowerCase().endsWith('.gguf'));
21
37
 
22
38
  const models: ModelInfo[] = [];
@@ -41,7 +57,8 @@ export class ModelScanner {
41
57
  * Get information about a specific model file
42
58
  */
43
59
  async getModelInfo(filename: string): Promise<ModelInfo | null> {
44
- const modelPath = path.join(this.modelsDir, filename);
60
+ const modelsDir = await this.getModelsDirectory();
61
+ const modelPath = path.join(modelsDir, filename);
45
62
 
46
63
  try {
47
64
  const stats = await fs.stat(modelPath);
@@ -84,8 +101,10 @@ export class ModelScanner {
84
101
  return filename;
85
102
  }
86
103
 
104
+ const modelsDir = await this.getModelsDirectory();
105
+
87
106
  // Try in models directory
88
- const modelPath = path.join(this.modelsDir, filename);
107
+ const modelPath = path.join(modelsDir, filename);
89
108
  const modelInfo = await this.getModelInfo(filename);
90
109
 
91
110
  if (modelInfo && modelInfo.exists) {
@@ -97,7 +116,7 @@ export class ModelScanner {
97
116
  const withExtension = `${filename}.gguf`;
98
117
  const modelInfoWithExt = await this.getModelInfo(withExtension);
99
118
  if (modelInfoWithExt && modelInfoWithExt.exists) {
100
- return path.join(this.modelsDir, withExtension);
119
+ return path.join(modelsDir, withExtension);
101
120
  }
102
121
  }
103
122
 
@@ -121,5 +140,18 @@ export class ModelScanner {
121
140
  }
122
141
  }
123
142
 
124
- // Export singleton instance
125
- export const modelScanner = new ModelScanner();
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();
@@ -0,0 +1,46 @@
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
+ }
@@ -183,6 +183,23 @@ export class StateManager {
183
183
  const servers = await this.getAllServers();
184
184
  return new Set(servers.map((s) => s.port));
185
185
  }
186
+
187
+ /**
188
+ * Get the configured models directory
189
+ */
190
+ async getModelsDirectory(): Promise<string> {
191
+ const config = await this.loadGlobalConfig();
192
+ return config.modelsDirectory;
193
+ }
194
+
195
+ /**
196
+ * Set the models directory
197
+ */
198
+ async setModelsDirectory(directory: string): Promise<void> {
199
+ const config = await this.loadGlobalConfig();
200
+ config.modelsDirectory = directory;
201
+ await this.saveGlobalConfig(config);
202
+ }
186
203
  }
187
204
 
188
205
  // Export singleton instance
@@ -82,10 +82,10 @@ export function getGlobalConfigPath(): string {
82
82
  }
83
83
 
84
84
  /**
85
- * Get the models directory (~/models)
85
+ * Get the default models directory (~/.llamacpp/models)
86
86
  */
87
87
  export function getModelsDir(): string {
88
- return path.join(os.homedir(), 'models');
88
+ return path.join(getConfigDir(), 'models');
89
89
  }
90
90
 
91
91
  /**
@@ -0,0 +1,47 @@
1
+ import * as readline from 'readline';
2
+
3
+ /**
4
+ * Prompt user for input
5
+ */
6
+ export function prompt(question: string, defaultValue?: string): Promise<string> {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ return new Promise((resolve) => {
13
+ const promptText = defaultValue
14
+ ? `${question} [${defaultValue}]: `
15
+ : `${question}: `;
16
+
17
+ rl.question(promptText, (answer) => {
18
+ rl.close();
19
+ resolve(answer.trim() || defaultValue || '');
20
+ });
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Prompt user for yes/no confirmation
26
+ */
27
+ export function confirm(question: string, defaultYes = true): Promise<boolean> {
28
+ const rl = readline.createInterface({
29
+ input: process.stdin,
30
+ output: process.stdout,
31
+ });
32
+
33
+ const suffix = defaultYes ? '[Y/n]' : '[y/N]';
34
+
35
+ return new Promise((resolve) => {
36
+ rl.question(`${question} ${suffix}: `, (answer) => {
37
+ rl.close();
38
+ const input = answer.trim().toLowerCase();
39
+
40
+ if (input === '') {
41
+ resolve(defaultYes);
42
+ } else {
43
+ resolve(input === 'y' || input === 'yes');
44
+ }
45
+ });
46
+ });
47
+ }