@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,161 +0,0 @@
1
- import chalk from 'chalk';
2
- import { stateManager } from '../lib/state-manager';
3
- import { statusChecker } from '../lib/status-checker';
4
- import { formatUptime, formatBytes, formatContextSize } from '../utils/format-utils';
5
- import { getProcessMemory } from '../utils/process-utils';
6
- import { getFileSize, formatFileSize, getArchivedLogInfo } from '../utils/log-utils';
7
- import { fileExists } from '../utils/file-utils';
8
-
9
- export async function serverShowCommand(identifier: string): Promise<void> {
10
- // Find the server
11
- const server = await stateManager.findServer(identifier);
12
-
13
- if (!server) {
14
- console.error(chalk.red(`❌ Server not found: ${identifier}`));
15
- console.log(chalk.dim('\nAvailable servers:'));
16
- const allServers = await stateManager.getAllServers();
17
- if (allServers.length === 0) {
18
- console.log(chalk.dim(' (none)'));
19
- console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
20
- } else {
21
- allServers.forEach(s => {
22
- console.log(chalk.dim(` - ${s.id} (port ${s.port})`));
23
- });
24
- }
25
- process.exit(1);
26
- }
27
-
28
- // Update status to get real-time info
29
- console.log(chalk.dim('Checking server status...\n'));
30
- const updatedServer = await statusChecker.updateServerStatus(server);
31
-
32
- // Display server information
33
- console.log(chalk.bold('Server Configuration:'));
34
- console.log('─'.repeat(70));
35
-
36
- // Basic info
37
- console.log(`${chalk.bold('Server ID:')} ${updatedServer.id}`);
38
- console.log(`${chalk.bold('Model Name:')} ${updatedServer.modelName}`);
39
- console.log(`${chalk.bold('Model Path:')} ${chalk.dim(updatedServer.modelPath)}`);
40
- console.log(`${chalk.bold('Host:')} ${updatedServer.host}`);
41
- console.log(`${chalk.bold('Port:')} http://${updatedServer.host}:${updatedServer.port}`);
42
-
43
- // Status with color
44
- let statusText: string;
45
- let statusColor: (text: string) => string;
46
- switch (updatedServer.status) {
47
- case 'running':
48
- statusText = '✅ RUNNING';
49
- statusColor = chalk.green;
50
- break;
51
- case 'crashed':
52
- statusText = '❌ CRASHED';
53
- statusColor = chalk.red;
54
- break;
55
- default:
56
- statusText = '⚠️ STOPPED';
57
- statusColor = chalk.yellow;
58
- }
59
- console.log(`${chalk.bold('Status:')} ${statusColor(statusText)}`);
60
-
61
- if (updatedServer.pid) {
62
- console.log(`${chalk.bold('PID:')} ${updatedServer.pid}`);
63
- }
64
-
65
- // Runtime info for running servers
66
- if (updatedServer.status === 'running') {
67
- if (updatedServer.lastStarted) {
68
- const uptime = formatUptime(updatedServer.lastStarted);
69
- console.log(`${chalk.bold('Uptime:')} ${uptime}`);
70
- }
71
-
72
- if (updatedServer.pid) {
73
- const cpuMemoryBytes = await getProcessMemory(updatedServer.pid);
74
- if (cpuMemoryBytes !== null) {
75
- const metalMemoryBytes = updatedServer.metalMemoryMB ? updatedServer.metalMemoryMB * 1024 * 1024 : 0;
76
- const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
77
-
78
- if (metalMemoryBytes > 0) {
79
- console.log(`${chalk.bold('Memory:')} ${formatBytes(totalMemoryBytes)} (CPU: ${formatBytes(cpuMemoryBytes)}, GPU: ${formatBytes(metalMemoryBytes)})`);
80
- } else {
81
- console.log(`${chalk.bold('Memory:')} ${formatBytes(cpuMemoryBytes)} (CPU only)`);
82
- }
83
- }
84
- }
85
- }
86
-
87
- // Configuration section
88
- console.log('\n' + '─'.repeat(70));
89
- console.log(chalk.bold('Configuration:'));
90
- console.log('─'.repeat(70));
91
- console.log(`${chalk.bold('Threads:')} ${updatedServer.threads}`);
92
- console.log(`${chalk.bold('Context Size:')} ${formatContextSize(updatedServer.ctxSize)} (total)`);
93
- console.log(`${chalk.bold('GPU Layers:')} ${updatedServer.gpuLayers}`);
94
- console.log(`${chalk.bold('Embeddings:')} ${updatedServer.embeddings ? 'enabled' : 'disabled'}`);
95
- console.log(`${chalk.bold('Jinja:')} ${updatedServer.jinja ? 'enabled' : 'disabled'}`);
96
- console.log(`${chalk.bold('Verbose Logs:')} ${updatedServer.verbose ? chalk.green('enabled') : chalk.dim('disabled')}`);
97
- if (updatedServer.customFlags && updatedServer.customFlags.length > 0) {
98
- console.log(`${chalk.bold('Custom Flags:')} ${updatedServer.customFlags.join(' ')}`);
99
- }
100
-
101
- // Logs section
102
- console.log('\n' + '─'.repeat(70));
103
- console.log(chalk.bold('Logs:'));
104
- console.log('─'.repeat(70));
105
-
106
- // Get current log sizes
107
- const stderrSize = (await fileExists(updatedServer.stderrPath))
108
- ? await getFileSize(updatedServer.stderrPath)
109
- : 0;
110
- const stdoutSize = (await fileExists(updatedServer.stdoutPath))
111
- ? await getFileSize(updatedServer.stdoutPath)
112
- : 0;
113
-
114
- // Get archived log info
115
- const archivedInfo = await getArchivedLogInfo(updatedServer.id);
116
-
117
- console.log(`${chalk.bold('Stderr:')} ${formatFileSize(stderrSize)} (current)`);
118
- console.log(`${chalk.bold('Stdout:')} ${formatFileSize(stdoutSize)} (current)`);
119
-
120
- if (archivedInfo.count > 0) {
121
- console.log(`${chalk.bold('Archived:')} ${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count} file${archivedInfo.count > 1 ? 's' : ''})`);
122
- }
123
-
124
- // Timestamps section
125
- console.log('\n' + '─'.repeat(70));
126
- console.log(chalk.bold('Timestamps:'));
127
- console.log('─'.repeat(70));
128
- console.log(`${chalk.bold('Created:')} ${new Date(updatedServer.createdAt).toLocaleString()}`);
129
- if (updatedServer.lastStarted) {
130
- console.log(`${chalk.bold('Last Started:')} ${new Date(updatedServer.lastStarted).toLocaleString()}`);
131
- }
132
- if (updatedServer.lastStopped) {
133
- console.log(`${chalk.bold('Last Stopped:')} ${new Date(updatedServer.lastStopped).toLocaleString()}`);
134
- }
135
-
136
- // System paths section
137
- console.log('\n' + '─'.repeat(70));
138
- console.log(chalk.bold('System Paths:'));
139
- console.log('─'.repeat(70));
140
- console.log(`${chalk.bold('Service Label:')} ${updatedServer.label}`);
141
- console.log(`${chalk.bold('Plist File:')} ${chalk.dim(updatedServer.plistPath)}`);
142
- console.log(`${chalk.bold('Stdout Log:')} ${chalk.dim(updatedServer.stdoutPath)}`);
143
- console.log(`${chalk.bold('Stderr Log:')} ${chalk.dim(updatedServer.stderrPath)}`);
144
-
145
- // Helpful commands
146
- console.log('\n' + '─'.repeat(70));
147
- console.log(chalk.bold('Quick Commands:'));
148
- console.log('─'.repeat(70));
149
-
150
- if (updatedServer.status === 'running') {
151
- console.log(chalk.dim(' View logs: ') + `llamacpp server logs ${updatedServer.id}`);
152
- console.log(chalk.dim(' Interactive chat: ') + `llamacpp server run ${updatedServer.id}`);
153
- console.log(chalk.dim(' Stop server: ') + `llamacpp server stop ${updatedServer.id}`);
154
- } else {
155
- console.log(chalk.dim(' Start server: ') + `llamacpp server start ${updatedServer.id}`);
156
- if (updatedServer.status === 'crashed') {
157
- console.log(chalk.dim(' View error logs: ') + `llamacpp server logs ${updatedServer.id} --errors`);
158
- }
159
- }
160
- console.log(chalk.dim(' Remove server: ') + `llamacpp server rm ${updatedServer.id}`);
161
- }
@@ -1,207 +0,0 @@
1
- import chalk from 'chalk';
2
- import Table from 'cli-table3';
3
- import { modelSearch } from '../lib/model-search';
4
- import { modelDownloader } from '../lib/model-downloader';
5
- import { formatBytes } from '../utils/format-utils';
6
- import * as https from 'https';
7
-
8
- interface ShowOptions {
9
- file?: string;
10
- }
11
-
12
- interface ModelDetails {
13
- modelId: string;
14
- author: string;
15
- modelName: string;
16
- downloads: number;
17
- likes: number;
18
- lastModified: string;
19
- tags: string[];
20
- library?: string;
21
- license?: string;
22
- }
23
-
24
- interface FileDetails {
25
- filename: string;
26
- size: number;
27
- lfs?: {
28
- oid: string;
29
- size: number;
30
- };
31
- }
32
-
33
- export async function showCommand(identifier: string, options: ShowOptions): Promise<void> {
34
- // Parse identifier
35
- const parsed = modelDownloader.parseHFIdentifier(identifier);
36
- const filename = options.file || parsed.file;
37
-
38
- console.log(chalk.blue('📋 Fetching model information...\n'));
39
-
40
- try {
41
- // Get model details
42
- const modelDetails = await getModelDetails(parsed.repo);
43
-
44
- // Display model information
45
- displayModelInfo(modelDetails);
46
-
47
- // If specific file requested, show file details
48
- if (filename) {
49
- console.log(chalk.blue('\n📄 File Details:\n'));
50
- await displayFileInfo(parsed.repo, filename);
51
- } else {
52
- // Show all GGUF files
53
- console.log(chalk.blue('\n📦 Available GGUF Files:\n'));
54
- await displayAllFiles(parsed.repo);
55
- }
56
- } catch (error) {
57
- throw new Error(`Failed to fetch model details: ${(error as Error).message}`);
58
- }
59
- }
60
-
61
- async function getModelDetails(modelId: string): Promise<ModelDetails> {
62
- return new Promise((resolve, reject) => {
63
- const url = `https://huggingface.co/api/models/${modelId}`;
64
-
65
- https.get(url, (response) => {
66
- let data = '';
67
-
68
- response.on('data', (chunk) => {
69
- data += chunk;
70
- });
71
-
72
- response.on('end', () => {
73
- try {
74
- const json = JSON.parse(data);
75
- const parts = modelId.split('/');
76
-
77
- resolve({
78
- modelId,
79
- author: parts[0] || '',
80
- modelName: parts.slice(1).join('/') || '',
81
- downloads: json.downloads || 0,
82
- likes: json.likes || 0,
83
- lastModified: json.lastModified || '',
84
- tags: json.tags || [],
85
- library: json.library_name,
86
- license: json.cardData?.license,
87
- });
88
- } catch (error) {
89
- reject(new Error(`Failed to parse model data: ${(error as Error).message}`));
90
- }
91
- });
92
- }).on('error', (error) => {
93
- reject(error);
94
- });
95
- });
96
- }
97
-
98
- function displayModelInfo(details: ModelDetails): void {
99
- console.log(chalk.bold('Model Information:'));
100
- console.log(chalk.dim('─'.repeat(60)));
101
- console.log(`${chalk.bold('ID:')} ${details.modelId}`);
102
- console.log(`${chalk.bold('Author:')} ${details.author}`);
103
- console.log(`${chalk.bold('Downloads:')} ${details.downloads.toLocaleString()}`);
104
- console.log(`${chalk.bold('Likes:')} ${details.likes.toLocaleString()}`);
105
- console.log(`${chalk.bold('Last Updated:')} ${new Date(details.lastModified).toLocaleDateString()}`);
106
-
107
- if (details.license) {
108
- console.log(`${chalk.bold('License:')} ${details.license}`);
109
- }
110
-
111
- if (details.tags.length > 0) {
112
- const relevantTags = details.tags
113
- .filter(tag => !tag.startsWith('arxiv:') && !tag.startsWith('dataset:'))
114
- .slice(0, 5);
115
- if (relevantTags.length > 0) {
116
- console.log(`${chalk.bold('Tags:')} ${relevantTags.join(', ')}`);
117
- }
118
- }
119
- }
120
-
121
- async function displayFileInfo(modelId: string, filename: string): Promise<void> {
122
- const files = await getModelFiles(modelId);
123
- const file = files.find(f => f.filename === filename);
124
-
125
- if (!file) {
126
- console.log(chalk.yellow(`⚠️ File not found: ${filename}`));
127
- console.log(chalk.dim('\nAvailable files:'));
128
- files.forEach(f => console.log(chalk.dim(` - ${f.filename}`)));
129
- return;
130
- }
131
-
132
- const size = file.lfs?.size || file.size || 0;
133
-
134
- console.log(`${chalk.bold('Filename:')} ${file.filename}`);
135
- console.log(`${chalk.bold('Size:')} ${formatBytes(size)}`);
136
-
137
- if (file.lfs) {
138
- console.log(`${chalk.bold('SHA256:')} ${file.lfs.oid.substring(0, 16)}...`);
139
- }
140
-
141
- console.log(chalk.dim('\nTo download:'));
142
- console.log(chalk.dim(` llamacpp pull ${modelId}/${filename}`));
143
- }
144
-
145
- async function displayAllFiles(modelId: string): Promise<void> {
146
- const files = await getModelFiles(modelId);
147
- const ggufFiles = files.filter(f => f.filename.toLowerCase().endsWith('.gguf'));
148
-
149
- if (ggufFiles.length === 0) {
150
- console.log(chalk.yellow('No GGUF files found in this model.'));
151
- return;
152
- }
153
-
154
- const table = new Table({
155
- head: ['FILENAME', 'SIZE'],
156
- colWidths: [55, 12],
157
- });
158
-
159
- for (const file of ggufFiles) {
160
- // LFS files store size in the lfs object, regular files in size field
161
- const size = (file.lfs && typeof file.lfs.size === 'number') ? file.lfs.size : file.size;
162
- table.push([
163
- file.filename,
164
- size > 0 ? formatBytes(size) : chalk.dim('Unknown'),
165
- ]);
166
- }
167
-
168
- console.log(table.toString());
169
-
170
- const totalSize = ggufFiles.reduce((sum, f) => {
171
- const size = (f.lfs && typeof f.lfs.size === 'number') ? f.lfs.size : f.size;
172
- return sum + size;
173
- }, 0);
174
- console.log(chalk.dim(`\nTotal: ${ggufFiles.length} files (${formatBytes(totalSize)})`));
175
- console.log(chalk.dim('\nTo download a specific file:'));
176
- console.log(chalk.dim(` llamacpp pull ${modelId}/<filename>`));
177
- }
178
-
179
- async function getModelFiles(modelId: string): Promise<FileDetails[]> {
180
- return new Promise((resolve, reject) => {
181
- const url = `https://huggingface.co/api/models/${modelId}`;
182
-
183
- https.get(url, (response) => {
184
- let data = '';
185
-
186
- response.on('data', (chunk) => {
187
- data += chunk;
188
- });
189
-
190
- response.on('end', () => {
191
- try {
192
- const json = JSON.parse(data);
193
- const files: FileDetails[] = (json.siblings || []).map((file: any) => ({
194
- filename: file.rfilename,
195
- size: file.size || 0,
196
- lfs: file.lfs,
197
- }));
198
- resolve(files);
199
- } catch (error) {
200
- reject(new Error(`Failed to parse file data: ${(error as Error).message}`));
201
- }
202
- });
203
- }).on('error', (error) => {
204
- reject(error);
205
- });
206
- });
207
- }
@@ -1,101 +0,0 @@
1
- import chalk from 'chalk';
2
- import { stateManager } from '../lib/state-manager';
3
- import { launchctlManager } from '../lib/launchctl-manager';
4
- import { statusChecker } from '../lib/status-checker';
5
- import { parseMetalMemoryFromLog } from '../utils/file-utils';
6
- import { autoRotateIfNeeded, formatFileSize } from '../utils/log-utils';
7
-
8
- export async function startCommand(identifier: string): Promise<void> {
9
- // Initialize state manager
10
- await stateManager.initialize();
11
-
12
- // 1. Find server by identifier
13
- const server = await stateManager.findServer(identifier);
14
- if (!server) {
15
- throw new Error(
16
- `Server not found: ${identifier}\n\n` +
17
- `Use: llamacpp ps\n` +
18
- `Or create a new server: llamacpp server create <model>`
19
- );
20
- }
21
-
22
- // 2. Check if already running
23
- if (server.status === 'running') {
24
- console.log(
25
- chalk.yellow(
26
- `⚠️ Server ${server.modelName} is already running on port ${server.port}`
27
- )
28
- );
29
- return;
30
- }
31
-
32
- console.log(chalk.blue(`▶️ Starting ${server.modelName} (port ${server.port})...`));
33
-
34
- // 3. Auto-rotate logs if they exceed 100MB
35
- try {
36
- const result = await autoRotateIfNeeded(server.stdoutPath, server.stderrPath, 100);
37
- if (result.rotated) {
38
- console.log(chalk.dim('Auto-rotated large log files:'));
39
- for (const file of result.files) {
40
- console.log(chalk.dim(` → ${file}`));
41
- }
42
- }
43
- } catch (error) {
44
- // Non-fatal, just warn
45
- console.log(chalk.yellow(`⚠️ Failed to rotate logs: ${(error as Error).message}`));
46
- }
47
-
48
- // 4. Ensure plist exists (recreate if missing)
49
- try {
50
- await launchctlManager.createPlist(server);
51
- } catch (error) {
52
- // May already exist, that's okay
53
- }
54
-
55
- // 5. Load service if needed
56
- try {
57
- await launchctlManager.loadService(server.plistPath);
58
- } catch (error) {
59
- // May already be loaded, that's okay
60
- }
61
-
62
- // 6. Start the service
63
- try {
64
- await launchctlManager.startService(server.label);
65
- } catch (error) {
66
- throw new Error(`Failed to start service: ${(error as Error).message}`);
67
- }
68
-
69
- // 7. Wait for startup
70
- console.log(chalk.dim('Waiting for server to start...'));
71
- const started = await launchctlManager.waitForServiceStart(server.label, 5000);
72
-
73
- if (!started) {
74
- throw new Error(
75
- `Server failed to start. Check logs with: llamacpp server logs ${server.id}`
76
- );
77
- }
78
-
79
- // 8. Update server status
80
- let updatedServer = await statusChecker.updateServerStatus(server);
81
-
82
- // 9. Parse Metal (GPU) memory allocation if not already captured
83
- if (!updatedServer.metalMemoryMB) {
84
- console.log(chalk.dim('Detecting Metal (GPU) memory allocation...'));
85
- await new Promise(resolve => setTimeout(resolve, 8000)); // 8 second delay
86
- const metalMemoryMB = await parseMetalMemoryFromLog(updatedServer.stderrPath);
87
- if (metalMemoryMB) {
88
- updatedServer = { ...updatedServer, metalMemoryMB };
89
- await stateManager.saveServerConfig(updatedServer);
90
- console.log(chalk.dim(`Metal memory: ${metalMemoryMB.toFixed(0)} MB`));
91
- }
92
- }
93
-
94
- // 10. Display success
95
- console.log();
96
- console.log(chalk.green('✅ Server started successfully!'));
97
- console.log();
98
- console.log(chalk.dim(`Connect: http://localhost:${server.port}`));
99
- console.log(chalk.dim(`View logs: llamacpp server logs ${server.id}`));
100
- console.log(chalk.dim(`Stop: llamacpp server stop ${server.id}`));
101
- }
@@ -1,39 +0,0 @@
1
- import chalk from 'chalk';
2
- import { stateManager } from '../lib/state-manager';
3
- import { launchctlManager } from '../lib/launchctl-manager';
4
- import { statusChecker } from '../lib/status-checker';
5
-
6
- export async function stopCommand(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
- // Check if already stopped
14
- if (server.status === 'stopped') {
15
- console.log(chalk.yellow(`⚠️ Server ${server.modelName} is already stopped`));
16
- return;
17
- }
18
-
19
- console.log(chalk.blue(`⏹️ Stopping ${server.modelName} (port ${server.port})...`));
20
-
21
- // Unload the service (removes from launchd management - won't auto-restart)
22
- try {
23
- await launchctlManager.unloadService(server.plistPath);
24
- } catch (error) {
25
- throw new Error(`Failed to unload service: ${(error as Error).message}`);
26
- }
27
-
28
- // Wait for clean shutdown
29
- const stopped = await launchctlManager.waitForServiceStop(server.label, 5000);
30
-
31
- if (!stopped) {
32
- console.log(chalk.yellow('⚠️ Server did not stop cleanly (timeout)'));
33
- }
34
-
35
- // Update server status
36
- await statusChecker.updateServerStatus(server);
37
-
38
- console.log(chalk.green('✅ Server stopped'));
39
- }
@@ -1,25 +0,0 @@
1
- import chalk from 'chalk';
2
- import blessed from 'blessed';
3
- import { stateManager } from '../lib/state-manager.js';
4
- import { statusChecker } from '../lib/status-checker.js';
5
- import { createRootNavigator } from '../tui/RootNavigator.js';
6
-
7
- export async function tuiCommand(): 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('\nCreate a server: llamacpp server create <model-filename>'));
13
- return;
14
- }
15
-
16
- const serversWithStatus = await statusChecker.updateAllServerStatuses();
17
-
18
- const screen = blessed.screen({
19
- smartCSR: true,
20
- title: 'llama.cpp Server Monitor',
21
- fullUnicode: true,
22
- });
23
-
24
- await createRootNavigator(screen, serversWithStatus);
25
- }
@@ -1,130 +0,0 @@
1
- import * as os from 'os';
2
- import * as path from 'path';
3
- import { ServerConfig, sanitizeModelName } from '../types/server-config';
4
- import { getLogsDir, getLaunchAgentsDir } from '../utils/file-utils';
5
- import { stateManager } from './state-manager';
6
-
7
- export interface ServerOptions {
8
- port?: number;
9
- host?: string;
10
- threads?: number;
11
- ctxSize?: number;
12
- gpuLayers?: number;
13
- embeddings?: boolean;
14
- jinja?: boolean;
15
- verbose?: boolean;
16
- customFlags?: string[];
17
- }
18
-
19
- export interface SmartDefaults {
20
- threads: number;
21
- ctxSize: number;
22
- gpuLayers: number;
23
- }
24
-
25
- export class ConfigGenerator {
26
- /**
27
- * Calculate smart defaults based on model size
28
- */
29
- calculateSmartDefaults(modelSizeBytes: number): SmartDefaults {
30
- const sizeGB = modelSizeBytes / (1024 ** 3);
31
-
32
- // Context size based on model size
33
- let ctxSize: number;
34
- if (sizeGB < 1) {
35
- ctxSize = 2048; // < 1GB: small context
36
- } else if (sizeGB < 3) {
37
- ctxSize = 4096; // 1-3GB: medium
38
- } else if (sizeGB < 6) {
39
- ctxSize = 8192; // 3-6GB: large
40
- } else {
41
- ctxSize = 16384; // 6GB+: very large
42
- }
43
-
44
- // GPU layers - always max for Metal (macOS)
45
- const gpuLayers = 60; // llama.cpp auto-detects optimal value
46
-
47
- // Threads - use half of available cores (better performance)
48
- const cpuCount = os.cpus().length;
49
- const threads = Math.max(4, Math.floor(cpuCount / 2));
50
-
51
- return { threads, ctxSize, gpuLayers };
52
- }
53
-
54
- /**
55
- * Generate server configuration
56
- */
57
- async generateConfig(
58
- modelPath: string,
59
- modelName: string,
60
- modelSize: number,
61
- port: number,
62
- options?: ServerOptions
63
- ): Promise<ServerConfig> {
64
- // Calculate smart defaults
65
- const smartDefaults = this.calculateSmartDefaults(modelSize);
66
-
67
- // Apply user overrides
68
- const host = options?.host ?? '127.0.0.1'; // Default to localhost (secure)
69
- const threads = options?.threads ?? smartDefaults.threads;
70
- const ctxSize = options?.ctxSize ?? smartDefaults.ctxSize;
71
- const gpuLayers = options?.gpuLayers ?? smartDefaults.gpuLayers;
72
- const embeddings = options?.embeddings ?? true;
73
- const jinja = options?.jinja ?? true;
74
- const verbose = options?.verbose ?? true; // Default to true (HTTP request logging)
75
- const customFlags = options?.customFlags; // Optional custom flags
76
-
77
- // Generate server ID
78
- const id = sanitizeModelName(modelName);
79
-
80
- // Generate paths
81
- const label = `com.llama.${id}`;
82
- const plistPath = path.join(getLaunchAgentsDir(), `${label}.plist`);
83
- const logsDir = getLogsDir();
84
- const stdoutPath = path.join(logsDir, `${id}.stdout`);
85
- const stderrPath = path.join(logsDir, `${id}.stderr`);
86
-
87
- const config: ServerConfig = {
88
- id,
89
- modelPath,
90
- modelName,
91
- port,
92
- host,
93
- threads,
94
- ctxSize,
95
- gpuLayers,
96
- embeddings,
97
- jinja,
98
- verbose,
99
- customFlags,
100
- status: 'stopped',
101
- createdAt: new Date().toISOString(),
102
- plistPath,
103
- label,
104
- stdoutPath,
105
- stderrPath,
106
- };
107
-
108
- return config;
109
- }
110
-
111
- /**
112
- * Merge global defaults with user options
113
- */
114
- async mergeWithGlobalDefaults(options?: ServerOptions): Promise<Partial<ServerOptions>> {
115
- const globalConfig = await stateManager.loadGlobalConfig();
116
-
117
- return {
118
- host: options?.host ?? '127.0.0.1',
119
- threads: options?.threads ?? globalConfig.defaults.threads,
120
- ctxSize: options?.ctxSize ?? globalConfig.defaults.ctxSize,
121
- gpuLayers: options?.gpuLayers ?? globalConfig.defaults.gpuLayers,
122
- embeddings: options?.embeddings ?? true,
123
- jinja: options?.jinja ?? true,
124
- verbose: options?.verbose ?? true,
125
- };
126
- }
127
- }
128
-
129
- // Export singleton instance
130
- export const configGenerator = new ConfigGenerator();