@appkit/llamacpp-cli 1.0.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/.versionrc.json +16 -0
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +474 -0
  4. package/bin/llamacpp +26 -0
  5. package/dist/cli.d.ts +3 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +196 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/delete.d.ts +2 -0
  10. package/dist/commands/delete.d.ts.map +1 -0
  11. package/dist/commands/delete.js +104 -0
  12. package/dist/commands/delete.js.map +1 -0
  13. package/dist/commands/list.d.ts +2 -0
  14. package/dist/commands/list.d.ts.map +1 -0
  15. package/dist/commands/list.js +37 -0
  16. package/dist/commands/list.js.map +1 -0
  17. package/dist/commands/logs.d.ts +8 -0
  18. package/dist/commands/logs.d.ts.map +1 -0
  19. package/dist/commands/logs.js +57 -0
  20. package/dist/commands/logs.js.map +1 -0
  21. package/dist/commands/ps.d.ts +2 -0
  22. package/dist/commands/ps.d.ts.map +1 -0
  23. package/dist/commands/ps.js +72 -0
  24. package/dist/commands/ps.js.map +1 -0
  25. package/dist/commands/pull.d.ts +6 -0
  26. package/dist/commands/pull.d.ts.map +1 -0
  27. package/dist/commands/pull.js +36 -0
  28. package/dist/commands/pull.js.map +1 -0
  29. package/dist/commands/rm.d.ts +2 -0
  30. package/dist/commands/rm.d.ts.map +1 -0
  31. package/dist/commands/rm.js +134 -0
  32. package/dist/commands/rm.js.map +1 -0
  33. package/dist/commands/run.d.ts +2 -0
  34. package/dist/commands/run.d.ts.map +1 -0
  35. package/dist/commands/run.js +198 -0
  36. package/dist/commands/run.js.map +1 -0
  37. package/dist/commands/search.d.ts +7 -0
  38. package/dist/commands/search.d.ts.map +1 -0
  39. package/dist/commands/search.js +93 -0
  40. package/dist/commands/search.js.map +1 -0
  41. package/dist/commands/show.d.ts +6 -0
  42. package/dist/commands/show.d.ts.map +1 -0
  43. package/dist/commands/show.js +196 -0
  44. package/dist/commands/show.js.map +1 -0
  45. package/dist/commands/start.d.ts +9 -0
  46. package/dist/commands/start.d.ts.map +1 -0
  47. package/dist/commands/start.js +150 -0
  48. package/dist/commands/start.js.map +1 -0
  49. package/dist/commands/stop.d.ts +2 -0
  50. package/dist/commands/stop.d.ts.map +1 -0
  51. package/dist/commands/stop.js +39 -0
  52. package/dist/commands/stop.js.map +1 -0
  53. package/dist/lib/config-generator.d.ts +30 -0
  54. package/dist/lib/config-generator.d.ts.map +1 -0
  55. package/dist/lib/config-generator.js +125 -0
  56. package/dist/lib/config-generator.js.map +1 -0
  57. package/dist/lib/launchctl-manager.d.ts +55 -0
  58. package/dist/lib/launchctl-manager.d.ts.map +1 -0
  59. package/dist/lib/launchctl-manager.js +227 -0
  60. package/dist/lib/launchctl-manager.js.map +1 -0
  61. package/dist/lib/model-downloader.d.ts +44 -0
  62. package/dist/lib/model-downloader.d.ts.map +1 -0
  63. package/dist/lib/model-downloader.js +248 -0
  64. package/dist/lib/model-downloader.js.map +1 -0
  65. package/dist/lib/model-scanner.d.ts +31 -0
  66. package/dist/lib/model-scanner.d.ts.map +1 -0
  67. package/dist/lib/model-scanner.js +145 -0
  68. package/dist/lib/model-scanner.js.map +1 -0
  69. package/dist/lib/model-search.d.ts +29 -0
  70. package/dist/lib/model-search.d.ts.map +1 -0
  71. package/dist/lib/model-search.js +131 -0
  72. package/dist/lib/model-search.js.map +1 -0
  73. package/dist/lib/port-manager.d.ts +26 -0
  74. package/dist/lib/port-manager.d.ts.map +1 -0
  75. package/dist/lib/port-manager.js +75 -0
  76. package/dist/lib/port-manager.js.map +1 -0
  77. package/dist/lib/state-manager.d.ts +59 -0
  78. package/dist/lib/state-manager.d.ts.map +1 -0
  79. package/dist/lib/state-manager.js +178 -0
  80. package/dist/lib/state-manager.js.map +1 -0
  81. package/dist/lib/status-checker.d.ts +28 -0
  82. package/dist/lib/status-checker.d.ts.map +1 -0
  83. package/dist/lib/status-checker.js +99 -0
  84. package/dist/lib/status-checker.js.map +1 -0
  85. package/dist/types/global-config.d.ts +16 -0
  86. package/dist/types/global-config.d.ts.map +1 -0
  87. package/dist/types/global-config.js +18 -0
  88. package/dist/types/global-config.js.map +1 -0
  89. package/dist/types/model-info.d.ts +9 -0
  90. package/dist/types/model-info.d.ts.map +1 -0
  91. package/dist/types/model-info.js +3 -0
  92. package/dist/types/model-info.js.map +1 -0
  93. package/dist/types/server-config.d.ts +27 -0
  94. package/dist/types/server-config.d.ts.map +1 -0
  95. package/dist/types/server-config.js +15 -0
  96. package/dist/types/server-config.js.map +1 -0
  97. package/dist/utils/file-utils.d.ts +49 -0
  98. package/dist/utils/file-utils.d.ts.map +1 -0
  99. package/dist/utils/file-utils.js +144 -0
  100. package/dist/utils/file-utils.js.map +1 -0
  101. package/dist/utils/format-utils.d.ts +29 -0
  102. package/dist/utils/format-utils.d.ts.map +1 -0
  103. package/dist/utils/format-utils.js +82 -0
  104. package/dist/utils/format-utils.js.map +1 -0
  105. package/dist/utils/process-utils.d.ts +27 -0
  106. package/dist/utils/process-utils.d.ts.map +1 -0
  107. package/dist/utils/process-utils.js +66 -0
  108. package/dist/utils/process-utils.js.map +1 -0
  109. package/package.json +56 -0
  110. package/src/cli.ts +195 -0
  111. package/src/commands/delete.ts +74 -0
  112. package/src/commands/list.ts +37 -0
  113. package/src/commands/logs.ts +61 -0
  114. package/src/commands/ps.ts +79 -0
  115. package/src/commands/pull.ts +40 -0
  116. package/src/commands/rm.ts +114 -0
  117. package/src/commands/run.ts +209 -0
  118. package/src/commands/search.ts +107 -0
  119. package/src/commands/show.ts +207 -0
  120. package/src/commands/start.ts +140 -0
  121. package/src/commands/stop.ts +39 -0
  122. package/src/lib/config-generator.ts +119 -0
  123. package/src/lib/launchctl-manager.ts +209 -0
  124. package/src/lib/model-downloader.ts +259 -0
  125. package/src/lib/model-scanner.ts +125 -0
  126. package/src/lib/model-search.ts +114 -0
  127. package/src/lib/port-manager.ts +80 -0
  128. package/src/lib/state-manager.ts +177 -0
  129. package/src/lib/status-checker.ts +113 -0
  130. package/src/types/global-config.ts +26 -0
  131. package/src/types/model-info.ts +8 -0
  132. package/src/types/server-config.ts +42 -0
  133. package/src/utils/file-utils.ts +106 -0
  134. package/src/utils/format-utils.ts +80 -0
  135. package/src/utils/process-utils.ts +60 -0
  136. package/tsconfig.json +20 -0
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.execAsync = void 0;
4
+ exports.execCommand = execCommand;
5
+ exports.execCommandFull = execCommandFull;
6
+ exports.commandExists = commandExists;
7
+ exports.isProcessRunning = isProcessRunning;
8
+ exports.isPortInUse = isPortInUse;
9
+ const child_process_1 = require("child_process");
10
+ const util_1 = require("util");
11
+ exports.execAsync = (0, util_1.promisify)(child_process_1.exec);
12
+ /**
13
+ * Execute a command and return stdout
14
+ * Throws on non-zero exit code
15
+ */
16
+ async function execCommand(command) {
17
+ const { stdout } = await (0, exports.execAsync)(command);
18
+ return stdout.trim();
19
+ }
20
+ /**
21
+ * Execute a command and return both stdout and stderr
22
+ */
23
+ async function execCommandFull(command) {
24
+ const { stdout, stderr } = await (0, exports.execAsync)(command);
25
+ return {
26
+ stdout: stdout.trim(),
27
+ stderr: stderr.trim(),
28
+ };
29
+ }
30
+ /**
31
+ * Check if a command exists in PATH
32
+ */
33
+ async function commandExists(command) {
34
+ try {
35
+ await (0, exports.execAsync)(`which ${command}`);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * Check if a process is running by PID
44
+ */
45
+ async function isProcessRunning(pid) {
46
+ try {
47
+ await (0, exports.execAsync)(`ps -p ${pid}`);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Check if a port is in use
56
+ */
57
+ async function isPortInUse(port) {
58
+ try {
59
+ await (0, exports.execAsync)(`lsof -iTCP:${port} -sTCP:LISTEN -t`);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ //# sourceMappingURL=process-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-utils.js","sourceRoot":"","sources":["../../src/utils/process-utils.ts"],"names":[],"mappings":";;;AASA,kCAGC;AAKD,0CAMC;AAKD,sCAOC;AAKD,4CAOC;AAKD,kCAOC;AA3DD,iDAAqC;AACrC,+BAAiC;AAEpB,QAAA,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAEzC;;;GAGG;AACI,KAAK,UAAU,WAAW,CAAC,OAAe;IAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAA,iBAAS,EAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,eAAe,CAAC,OAAe;IACnD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAA,iBAAS,EAAC,OAAO,CAAC,CAAC;IACpD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KACtB,CAAC;AACJ,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,SAAS,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,SAAS,GAAG,EAAE,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACI,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,MAAM,IAAA,iBAAS,EAAC,cAAc,IAAI,kBAAkB,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@appkit/llamacpp-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to manage local llama.cpp servers on macOS",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "llamacpp": "./bin/llamacpp"
8
+ },
9
+ "scripts": {
10
+ "dev": "tsx src/cli.ts",
11
+ "build": "tsc",
12
+ "start": "node dist/cli.js",
13
+ "clean": "rm -rf dist",
14
+ "prepublishOnly": "npm run build",
15
+ "release": "commit-and-tag-version",
16
+ "release:minor": "commit-and-tag-version --release-as minor",
17
+ "release:major": "commit-and-tag-version --release-as major",
18
+ "release:patch": "commit-and-tag-version --release-as patch",
19
+ "release:first": "commit-and-tag-version --first-release",
20
+ "postrelease": "git push --follow-tags origin main && npm publish --access public"
21
+ },
22
+ "keywords": [
23
+ "llama",
24
+ "llama.cpp",
25
+ "llm",
26
+ "cli",
27
+ "server-management",
28
+ "gguf",
29
+ "macos",
30
+ "launchctl"
31
+ ],
32
+ "author": "Appkit Studio",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/appkitstudio/llamacpp-cli.git"
37
+ },
38
+ "homepage": "https://github.com/appkitstudio/llamacpp-cli#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/appkitstudio/llamacpp-cli/issues"
41
+ },
42
+ "dependencies": {
43
+ "chalk": "^5.3.0",
44
+ "cli-table3": "^0.6.5",
45
+ "commander": "^13.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.12.7",
49
+ "commit-and-tag-version": "^12.6.1",
50
+ "tsx": "^4.7.2",
51
+ "typescript": "^5.4.5"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ }
56
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { listCommand } from './commands/list';
6
+ import { psCommand } from './commands/ps';
7
+ import { startCommand } from './commands/start';
8
+ import { runCommand } from './commands/run';
9
+ import { stopCommand } from './commands/stop';
10
+ import { deleteCommand } from './commands/delete';
11
+ import { pullCommand } from './commands/pull';
12
+ import { rmCommand } from './commands/rm';
13
+ import { logsCommand } from './commands/logs';
14
+ import { searchCommand } from './commands/search';
15
+ import { showCommand } from './commands/show';
16
+
17
+ const program = new Command();
18
+
19
+ program
20
+ .name('llamacpp')
21
+ .description('CLI tool to manage local llama.cpp servers on macOS')
22
+ .version('1.0.0');
23
+
24
+ // List models
25
+ program
26
+ .command('ls')
27
+ .description('List available GGUF models in ~/models')
28
+ .action(async () => {
29
+ try {
30
+ await listCommand();
31
+ } catch (error) {
32
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
33
+ process.exit(1);
34
+ }
35
+ });
36
+
37
+ // List running servers
38
+ program
39
+ .command('ps')
40
+ .description('List all servers with status')
41
+ .action(async () => {
42
+ try {
43
+ await psCommand();
44
+ } catch (error) {
45
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
46
+ process.exit(1);
47
+ }
48
+ });
49
+
50
+ // Search for models
51
+ program
52
+ .command('search')
53
+ .description('Search Hugging Face for GGUF models')
54
+ .argument('<query>', 'Search query (e.g., "llama 3b" or "qwen")')
55
+ .option('-l, --limit <number>', 'Max results to show (default: 20)', parseInt)
56
+ .option('--files [number]', 'Show available files for result number (e.g., --files 1)', (val) => {
57
+ return val ? parseInt(val) : true;
58
+ })
59
+ .action(async (query: string, options) => {
60
+ try {
61
+ await searchCommand(query, options);
62
+ } catch (error) {
63
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
64
+ process.exit(1);
65
+ }
66
+ });
67
+
68
+ // Show model details
69
+ program
70
+ .command('show')
71
+ .description('Show details about a model or file')
72
+ .argument('<identifier>', 'HuggingFace repo/file (e.g., owner/repo or owner/repo/file.gguf)')
73
+ .option('-f, --file <filename>', 'Specific GGUF file to show details for')
74
+ .action(async (identifier: string, options) => {
75
+ try {
76
+ await showCommand(identifier, options);
77
+ } catch (error) {
78
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
79
+ process.exit(1);
80
+ }
81
+ });
82
+
83
+ // Download a model
84
+ program
85
+ .command('pull')
86
+ .description('Download a GGUF model from Hugging Face')
87
+ .argument('<identifier>', 'HuggingFace repo/file (e.g., owner/repo/file.gguf or owner/repo)')
88
+ .option('-f, --file <filename>', 'Specific GGUF file (alternative to path in identifier)')
89
+ .action(async (identifier: string, options) => {
90
+ try {
91
+ await pullCommand(identifier, options);
92
+ } catch (error) {
93
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
94
+ process.exit(1);
95
+ }
96
+ });
97
+
98
+ // Delete a model
99
+ program
100
+ .command('rm')
101
+ .description('Delete a model file (and any associated servers)')
102
+ .argument('<model>', 'Model filename or partial name')
103
+ .action(async (model: string) => {
104
+ try {
105
+ await rmCommand(model);
106
+ } catch (error) {
107
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ // Server management commands
113
+ const server = program
114
+ .command('server')
115
+ .description('Manage llama-server instances');
116
+
117
+ // Start a server
118
+ server
119
+ .command('start')
120
+ .description('Start a llama-server instance')
121
+ .argument('<model>', 'Model filename or path')
122
+ .option('-p, --port <number>', 'Port number (default: auto-assign)', parseInt)
123
+ .option('-t, --threads <number>', 'Thread count (default: auto)', parseInt)
124
+ .option('-c, --ctx-size <number>', 'Context size (default: auto)', parseInt)
125
+ .option('-g, --gpu-layers <number>', 'GPU layers (default: 60)', parseInt)
126
+ .action(async (model: string, options) => {
127
+ try {
128
+ await startCommand(model, options);
129
+ } catch (error) {
130
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
131
+ process.exit(1);
132
+ }
133
+ });
134
+
135
+ // Run interactive chat with a model
136
+ server
137
+ .command('run')
138
+ .description('Run an interactive chat session with a model')
139
+ .argument('<model>', 'Model identifier: port (9000), server ID (llama-3-2-3b), partial name, or model filename')
140
+ .action(async (model: string) => {
141
+ try {
142
+ await runCommand(model);
143
+ } catch (error) {
144
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
145
+ process.exit(1);
146
+ }
147
+ });
148
+
149
+ // Stop a server
150
+ server
151
+ .command('stop')
152
+ .description('Stop a running server')
153
+ .argument('<identifier>', 'Server identifier: port (9000), server ID (llama-3-2-3b), or partial model name')
154
+ .action(async (identifier: string) => {
155
+ try {
156
+ await stopCommand(identifier);
157
+ } catch (error) {
158
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
159
+ process.exit(1);
160
+ }
161
+ });
162
+
163
+ // Delete a server
164
+ server
165
+ .command('rm')
166
+ .description('Remove a server configuration and launchctl service (preserves model file)')
167
+ .argument('<identifier>', 'Server identifier: port (9000), server ID (llama-3-2-3b), or partial model name')
168
+ .action(async (identifier: string) => {
169
+ try {
170
+ await deleteCommand(identifier);
171
+ } catch (error) {
172
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
173
+ process.exit(1);
174
+ }
175
+ });
176
+
177
+ // View logs
178
+ server
179
+ .command('logs')
180
+ .description('View server logs')
181
+ .argument('<identifier>', 'Server identifier: port (9000), server ID (llama-3-2-3b), or partial model name')
182
+ .option('-f, --follow', 'Follow log output in real-time')
183
+ .option('-n, --lines <number>', 'Number of lines to show (default: 50)', parseInt)
184
+ .option('--errors', 'Show stderr instead of stdout')
185
+ .action(async (identifier: string, options) => {
186
+ try {
187
+ await logsCommand(identifier, options);
188
+ } catch (error) {
189
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
190
+ process.exit(1);
191
+ }
192
+ });
193
+
194
+ // Parse arguments
195
+ program.parse();
@@ -0,0 +1,74 @@
1
+ import chalk from 'chalk';
2
+ import * as readline from 'readline';
3
+ import { stateManager } from '../lib/state-manager';
4
+ import { launchctlManager } from '../lib/launchctl-manager';
5
+
6
+ export async function deleteCommand(identifier: string): Promise<void> {
7
+ // Find server
8
+ const server = await stateManager.findServer(identifier);
9
+ if (!server) {
10
+ throw new Error(`Server not found: ${identifier}\n\nUse: llamacpp ps`);
11
+ }
12
+
13
+ // Confirm deletion
14
+ console.log(chalk.yellow(`⚠️ Delete server configuration for ${server.modelName}?`));
15
+ console.log(chalk.dim(' This will remove the launchd service but keep the model file.'));
16
+ console.log();
17
+
18
+ const confirmed = await confirmDeletion();
19
+ if (!confirmed) {
20
+ console.log(chalk.dim('Cancelled'));
21
+ return;
22
+ }
23
+
24
+ console.log();
25
+ console.log(chalk.blue(`🗑️ Deleting server ${server.modelName}...`));
26
+
27
+ // Stop server if running
28
+ if (server.status === 'running') {
29
+ console.log(chalk.dim('Stopping server...'));
30
+ try {
31
+ await launchctlManager.stopService(server.label);
32
+ await launchctlManager.waitForServiceStop(server.label, 5000);
33
+ } catch (error) {
34
+ console.log(chalk.yellow('⚠️ Failed to stop server gracefully'));
35
+ }
36
+ }
37
+
38
+ // Unload service
39
+ console.log(chalk.dim('Unloading launchctl service...'));
40
+ await launchctlManager.unloadService(server.plistPath);
41
+
42
+ // Delete plist
43
+ console.log(chalk.dim('Deleting plist file...'));
44
+ await launchctlManager.deletePlist(server.plistPath);
45
+
46
+ // Delete server config
47
+ console.log(chalk.dim('Deleting server configuration...'));
48
+ await stateManager.deleteServerConfig(server.id);
49
+
50
+ // Success
51
+ console.log();
52
+ console.log(chalk.green('✅ Server deleted'));
53
+ console.log(chalk.dim(` Plist removed: ${server.plistPath}`));
54
+ console.log(chalk.dim(` Config removed`));
55
+ console.log();
56
+ console.log(chalk.dim(` Model file preserved at: ${server.modelPath}`));
57
+ }
58
+
59
+ /**
60
+ * Prompt user for confirmation
61
+ */
62
+ function confirmDeletion(): Promise<boolean> {
63
+ return new Promise((resolve) => {
64
+ const rl = readline.createInterface({
65
+ input: process.stdin,
66
+ output: process.stdout,
67
+ });
68
+
69
+ rl.question(chalk.yellow(" Type 'yes' to confirm: "), (answer) => {
70
+ rl.close();
71
+ resolve(answer.toLowerCase() === 'yes');
72
+ });
73
+ });
74
+ }
@@ -0,0 +1,37 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { modelScanner } from '../lib/model-scanner';
4
+ import { formatBytes, formatDateShort } from '../utils/format-utils';
5
+ import { getModelsDir } from '../utils/file-utils';
6
+
7
+ export async function listCommand(): Promise<void> {
8
+ const modelsDir = getModelsDir();
9
+ console.log(chalk.blue(`📦 Available models in ${modelsDir}\n`));
10
+
11
+ const models = await modelScanner.scanModels();
12
+
13
+ if (models.length === 0) {
14
+ console.log(chalk.yellow('No GGUF models found.'));
15
+ console.log(chalk.dim(`\nDownload models with: llamacpp pull <repo> --file <filename>`));
16
+ return;
17
+ }
18
+
19
+ const table = new Table({
20
+ head: ['MODEL', 'SIZE', 'MODIFIED'],
21
+ colWidths: [50, 12, 15],
22
+ });
23
+
24
+ for (const model of models) {
25
+ table.push([
26
+ model.filename,
27
+ model.sizeFormatted,
28
+ formatDateShort(model.modified),
29
+ ]);
30
+ }
31
+
32
+ console.log(table.toString());
33
+
34
+ const totalSize = models.reduce((sum, m) => sum + m.size, 0);
35
+ console.log(chalk.dim(`\nTotal: ${models.length} models (${formatBytes(totalSize)})`));
36
+ console.log(chalk.dim(`\nStart a server: llamacpp start <model-filename>`));
37
+ }
@@ -0,0 +1,61 @@
1
+ import chalk from 'chalk';
2
+ import { spawn } from 'child_process';
3
+ import { stateManager } from '../lib/state-manager';
4
+ import { fileExists } from '../utils/file-utils';
5
+ import { execCommand } from '../utils/process-utils';
6
+
7
+ interface LogsOptions {
8
+ follow?: boolean;
9
+ lines?: number;
10
+ errors?: boolean;
11
+ }
12
+
13
+ export async function logsCommand(identifier: string, options: LogsOptions): Promise<void> {
14
+ // Find server
15
+ const server = await stateManager.findServer(identifier);
16
+ if (!server) {
17
+ throw new Error(`Server not found: ${identifier}\n\nUse: llamacpp ps`);
18
+ }
19
+
20
+ // Determine log file
21
+ const logPath = options.errors ? server.stderrPath : server.stdoutPath;
22
+ const logType = options.errors ? 'errors' : 'logs';
23
+
24
+ // Check if log file exists
25
+ if (!(await fileExists(logPath))) {
26
+ console.log(chalk.yellow(`⚠️ No ${logType} found for ${server.modelName}`));
27
+ console.log(chalk.dim(` Log file does not exist: ${logPath}`));
28
+ return;
29
+ }
30
+
31
+ console.log(chalk.blue(`📋 ${options.errors ? 'Errors' : 'Logs'} for ${server.modelName}`));
32
+ console.log(chalk.dim(` ${logPath}\n`));
33
+
34
+ if (options.follow) {
35
+ // Follow logs in real-time
36
+ const tail = spawn('tail', ['-f', logPath], {
37
+ stdio: 'inherit',
38
+ });
39
+
40
+ // Handle Ctrl+C gracefully
41
+ process.on('SIGINT', () => {
42
+ tail.kill();
43
+ console.log();
44
+ process.exit(0);
45
+ });
46
+
47
+ // Wait for tail to exit
48
+ tail.on('exit', () => {
49
+ process.exit(0);
50
+ });
51
+ } else {
52
+ // Show last N lines
53
+ const lines = options.lines || 50;
54
+ try {
55
+ const output = await execCommand(`tail -n ${lines} "${logPath}"`);
56
+ console.log(output);
57
+ } catch (error) {
58
+ throw new Error(`Failed to read logs: ${(error as Error).message}`);
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,79 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { stateManager } from '../lib/state-manager';
4
+ import { statusChecker } from '../lib/status-checker';
5
+ import { formatUptime } from '../utils/format-utils';
6
+
7
+ export async function psCommand(): Promise<void> {
8
+ const servers = await stateManager.getAllServers();
9
+
10
+ if (servers.length === 0) {
11
+ console.log(chalk.yellow('No servers configured.'));
12
+ console.log(chalk.dim('\nStart a server: llamacpp server start <model-filename>'));
13
+ return;
14
+ }
15
+
16
+ // Update all server statuses
17
+ console.log(chalk.dim('Checking server statuses...\n'));
18
+ const updated = await statusChecker.updateAllServerStatuses();
19
+
20
+ const table = new Table({
21
+ head: ['SERVER ID', 'MODEL', 'PORT', 'STATUS', 'PID', 'UPTIME'],
22
+ });
23
+
24
+ let runningCount = 0;
25
+ let stoppedCount = 0;
26
+ let crashedCount = 0;
27
+
28
+ for (const server of updated) {
29
+ let statusText: string;
30
+ let statusColor: (text: string) => string;
31
+
32
+ switch (server.status) {
33
+ case 'running':
34
+ statusText = '✅ RUNNING';
35
+ statusColor = chalk.green;
36
+ runningCount++;
37
+ break;
38
+ case 'crashed':
39
+ statusText = '❌ CRASHED';
40
+ statusColor = chalk.red;
41
+ crashedCount++;
42
+ break;
43
+ default:
44
+ statusText = '⚠️ STOPPED';
45
+ statusColor = chalk.yellow;
46
+ stoppedCount++;
47
+ }
48
+
49
+ const uptime =
50
+ server.status === 'running' && server.lastStarted
51
+ ? formatUptime(server.lastStarted)
52
+ : '-';
53
+
54
+ table.push([
55
+ server.id,
56
+ server.modelName,
57
+ server.port.toString(),
58
+ statusColor(statusText),
59
+ server.pid?.toString() || '-',
60
+ uptime,
61
+ ]);
62
+ }
63
+
64
+ console.log(table.toString());
65
+
66
+ const summary = [
67
+ chalk.green(`${runningCount} running`),
68
+ chalk.yellow(`${stoppedCount} stopped`),
69
+ ];
70
+ if (crashedCount > 0) {
71
+ summary.push(chalk.red(`${crashedCount} crashed`));
72
+ }
73
+
74
+ console.log(chalk.dim(`\nTotal: ${servers.length} servers (${summary.join(', ')})`));
75
+
76
+ if (crashedCount > 0) {
77
+ console.log(chalk.red('\n⚠️ Some servers have crashed. Check logs with: llamacpp server logs <id> --errors'));
78
+ }
79
+ }
@@ -0,0 +1,40 @@
1
+ import chalk from 'chalk';
2
+ import { modelDownloader } from '../lib/model-downloader';
3
+
4
+ interface PullOptions {
5
+ file?: string;
6
+ }
7
+
8
+ export async function pullCommand(identifier: string, options: PullOptions): Promise<void> {
9
+ // Parse repository identifier
10
+ const parsed = modelDownloader.parseHFIdentifier(identifier);
11
+
12
+ // Determine filename - from --file flag or from identifier path
13
+ let filename = options.file || parsed.file;
14
+
15
+ if (!filename) {
16
+ throw new Error(
17
+ 'Please specify a file to download:\n\n' +
18
+ 'Option 1: llamacpp pull owner/repo/filename.gguf\n' +
19
+ 'Option 2: llamacpp pull owner/repo --file filename.gguf'
20
+ );
21
+ }
22
+
23
+ // Ensure filename ends with .gguf
24
+ if (!filename.toLowerCase().endsWith('.gguf')) {
25
+ filename += '.gguf';
26
+ }
27
+
28
+ // Download the model
29
+ try {
30
+ const modelPath = await modelDownloader.downloadModel(parsed.repo, filename);
31
+
32
+ console.log();
33
+ console.log(chalk.dim(`Start server: llamacpp start ${filename}`));
34
+ } catch (error) {
35
+ if ((error as Error).message.includes('interrupted')) {
36
+ console.log(chalk.dim('\nDownload was interrupted. Run the same command again to retry.'));
37
+ }
38
+ throw error;
39
+ }
40
+ }