@appkit/llamacpp-cli 1.8.0 → 1.10.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 (116) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +249 -40
  3. package/dist/cli.js +154 -10
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/completion.d.ts +9 -0
  6. package/dist/commands/completion.d.ts.map +1 -0
  7. package/dist/commands/completion.js +83 -0
  8. package/dist/commands/completion.js.map +1 -0
  9. package/dist/commands/monitor.js +1 -1
  10. package/dist/commands/monitor.js.map +1 -1
  11. package/dist/commands/ps.d.ts +1 -3
  12. package/dist/commands/ps.d.ts.map +1 -1
  13. package/dist/commands/ps.js +36 -115
  14. package/dist/commands/ps.js.map +1 -1
  15. package/dist/commands/router/config.d.ts +11 -0
  16. package/dist/commands/router/config.d.ts.map +1 -0
  17. package/dist/commands/router/config.js +100 -0
  18. package/dist/commands/router/config.js.map +1 -0
  19. package/dist/commands/router/logs.d.ts +12 -0
  20. package/dist/commands/router/logs.d.ts.map +1 -0
  21. package/dist/commands/router/logs.js +238 -0
  22. package/dist/commands/router/logs.js.map +1 -0
  23. package/dist/commands/router/restart.d.ts +2 -0
  24. package/dist/commands/router/restart.d.ts.map +1 -0
  25. package/dist/commands/router/restart.js +39 -0
  26. package/dist/commands/router/restart.js.map +1 -0
  27. package/dist/commands/router/start.d.ts +2 -0
  28. package/dist/commands/router/start.d.ts.map +1 -0
  29. package/dist/commands/router/start.js +60 -0
  30. package/dist/commands/router/start.js.map +1 -0
  31. package/dist/commands/router/status.d.ts +2 -0
  32. package/dist/commands/router/status.d.ts.map +1 -0
  33. package/dist/commands/router/status.js +116 -0
  34. package/dist/commands/router/status.js.map +1 -0
  35. package/dist/commands/router/stop.d.ts +2 -0
  36. package/dist/commands/router/stop.d.ts.map +1 -0
  37. package/dist/commands/router/stop.js +36 -0
  38. package/dist/commands/router/stop.js.map +1 -0
  39. package/dist/commands/tui.d.ts +2 -0
  40. package/dist/commands/tui.d.ts.map +1 -0
  41. package/dist/commands/tui.js +27 -0
  42. package/dist/commands/tui.js.map +1 -0
  43. package/dist/lib/completion.d.ts +5 -0
  44. package/dist/lib/completion.d.ts.map +1 -0
  45. package/dist/lib/completion.js +195 -0
  46. package/dist/lib/completion.js.map +1 -0
  47. package/dist/lib/model-downloader.d.ts +5 -1
  48. package/dist/lib/model-downloader.d.ts.map +1 -1
  49. package/dist/lib/model-downloader.js +53 -20
  50. package/dist/lib/model-downloader.js.map +1 -1
  51. package/dist/lib/router-logger.d.ts +61 -0
  52. package/dist/lib/router-logger.d.ts.map +1 -0
  53. package/dist/lib/router-logger.js +200 -0
  54. package/dist/lib/router-logger.js.map +1 -0
  55. package/dist/lib/router-manager.d.ts +103 -0
  56. package/dist/lib/router-manager.d.ts.map +1 -0
  57. package/dist/lib/router-manager.js +394 -0
  58. package/dist/lib/router-manager.js.map +1 -0
  59. package/dist/lib/router-server.d.ts +61 -0
  60. package/dist/lib/router-server.d.ts.map +1 -0
  61. package/dist/lib/router-server.js +485 -0
  62. package/dist/lib/router-server.js.map +1 -0
  63. package/dist/tui/ConfigApp.d.ts +7 -0
  64. package/dist/tui/ConfigApp.d.ts.map +1 -0
  65. package/dist/tui/ConfigApp.js +1002 -0
  66. package/dist/tui/ConfigApp.js.map +1 -0
  67. package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
  68. package/dist/tui/HistoricalMonitorApp.js +85 -49
  69. package/dist/tui/HistoricalMonitorApp.js.map +1 -1
  70. package/dist/tui/ModelsApp.d.ts +7 -0
  71. package/dist/tui/ModelsApp.d.ts.map +1 -0
  72. package/dist/tui/ModelsApp.js +362 -0
  73. package/dist/tui/ModelsApp.js.map +1 -0
  74. package/dist/tui/MultiServerMonitorApp.d.ts +6 -1
  75. package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
  76. package/dist/tui/MultiServerMonitorApp.js +1038 -122
  77. package/dist/tui/MultiServerMonitorApp.js.map +1 -1
  78. package/dist/tui/RootNavigator.d.ts +7 -0
  79. package/dist/tui/RootNavigator.d.ts.map +1 -0
  80. package/dist/tui/RootNavigator.js +55 -0
  81. package/dist/tui/RootNavigator.js.map +1 -0
  82. package/dist/tui/SearchApp.d.ts +6 -0
  83. package/dist/tui/SearchApp.d.ts.map +1 -0
  84. package/dist/tui/SearchApp.js +451 -0
  85. package/dist/tui/SearchApp.js.map +1 -0
  86. package/dist/tui/SplashScreen.d.ts +16 -0
  87. package/dist/tui/SplashScreen.d.ts.map +1 -0
  88. package/dist/tui/SplashScreen.js +129 -0
  89. package/dist/tui/SplashScreen.js.map +1 -0
  90. package/dist/types/router-config.d.ts +19 -0
  91. package/dist/types/router-config.d.ts.map +1 -0
  92. package/dist/types/router-config.js +3 -0
  93. package/dist/types/router-config.js.map +1 -0
  94. package/package.json +1 -1
  95. package/src/cli.ts +121 -10
  96. package/src/commands/monitor.ts +1 -1
  97. package/src/commands/ps.ts +44 -133
  98. package/src/commands/router/config.ts +116 -0
  99. package/src/commands/router/logs.ts +256 -0
  100. package/src/commands/router/restart.ts +36 -0
  101. package/src/commands/router/start.ts +60 -0
  102. package/src/commands/router/status.ts +119 -0
  103. package/src/commands/router/stop.ts +33 -0
  104. package/src/commands/tui.ts +25 -0
  105. package/src/lib/model-downloader.ts +57 -20
  106. package/src/lib/router-logger.ts +201 -0
  107. package/src/lib/router-manager.ts +414 -0
  108. package/src/lib/router-server.ts +538 -0
  109. package/src/tui/ConfigApp.ts +1085 -0
  110. package/src/tui/HistoricalMonitorApp.ts +88 -49
  111. package/src/tui/ModelsApp.ts +368 -0
  112. package/src/tui/MultiServerMonitorApp.ts +1163 -122
  113. package/src/tui/RootNavigator.ts +74 -0
  114. package/src/tui/SearchApp.ts +511 -0
  115. package/src/tui/SplashScreen.ts +149 -0
  116. package/src/types/router-config.ts +25 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appkit/llamacpp-cli",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "CLI tool to manage local llama.cpp servers on macOS",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -19,6 +19,12 @@ import { serverShowCommand } from './commands/server-show';
19
19
  import { serverConfigCommand } from './commands/config';
20
20
  import { configGlobalCommand } from './commands/config-global';
21
21
  import { monitorCommand } from './commands/monitor';
22
+ import { routerStartCommand } from './commands/router/start';
23
+ import { routerStopCommand } from './commands/router/stop';
24
+ import { routerStatusCommand } from './commands/router/status';
25
+ import { routerRestartCommand } from './commands/router/restart';
26
+ import { routerConfigCommand } from './commands/router/config';
27
+ import { routerLogsCommand } from './commands/router/logs';
22
28
  import packageJson from '../package.json';
23
29
 
24
30
  const program = new Command();
@@ -26,7 +32,17 @@ const program = new Command();
26
32
  program
27
33
  .name('llamacpp')
28
34
  .description('CLI tool to manage local llama.cpp servers on macOS')
29
- .version(packageJson.version, '-v, --version', 'Output the version number');
35
+ .version(packageJson.version, '-v, --version', 'Output the version number')
36
+ .action(async () => {
37
+ // Default action: launch TUI when no command provided
38
+ try {
39
+ const { tuiCommand } = await import('./commands/tui');
40
+ await tuiCommand();
41
+ } catch (error) {
42
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
43
+ process.exit(1);
44
+ }
45
+ });
30
46
 
31
47
  // List models
32
48
  program
@@ -41,14 +57,13 @@ program
41
57
  }
42
58
  });
43
59
 
44
- // List running servers
60
+ // List servers (static table)
45
61
  program
46
- .command('ps [identifier]')
47
- .description('Interactive server monitoring dashboard')
48
- .option('--table', 'Show static table instead of TUI (for scripting)')
49
- .action(async (identifier?: string, options?: { table?: boolean }) => {
62
+ .command('ps')
63
+ .description('List all servers with status (static table)')
64
+ .action(async () => {
50
65
  try {
51
- await psCommand(identifier, options);
66
+ await psCommand();
52
67
  } catch (error) {
53
68
  console.error(chalk.red('❌ Error:'), (error as Error).message);
54
69
  process.exit(1);
@@ -293,14 +308,14 @@ server
293
308
  }
294
309
  });
295
310
 
296
- // Monitor server (deprecated - redirects to ps)
311
+ // Monitor server (deprecated - redirects to TUI)
297
312
  server
298
313
  .command('monitor [identifier]')
299
- .description('Monitor server with real-time metrics TUI (deprecated: use "llamacpp ps" instead)')
314
+ .description('Monitor server with real-time metrics TUI (deprecated: use "llamacpp" instead)')
300
315
  .action(async (identifier?: string) => {
301
316
  try {
302
317
  console.log(chalk.yellow('⚠️ The "monitor" command is deprecated and will be removed in a future version.'));
303
- console.log(chalk.dim(' Please use "llamacpp ps" instead for the same functionality.\n'));
318
+ console.log(chalk.dim(' Please use "llamacpp" instead for the same functionality.\n'));
304
319
  await monitorCommand(identifier);
305
320
  } catch (error) {
306
321
  console.error(chalk.red('❌ Error:'), (error as Error).message);
@@ -308,5 +323,101 @@ server
308
323
  }
309
324
  });
310
325
 
326
+ // Router management commands
327
+ const router = program
328
+ .command('router')
329
+ .description('Manage the unified router endpoint');
330
+
331
+ // Start router
332
+ router
333
+ .command('start')
334
+ .description('Start the router service')
335
+ .action(async () => {
336
+ try {
337
+ await routerStartCommand();
338
+ } catch (error) {
339
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
340
+ process.exit(1);
341
+ }
342
+ });
343
+
344
+ // Stop router
345
+ router
346
+ .command('stop')
347
+ .description('Stop the router service')
348
+ .action(async () => {
349
+ try {
350
+ await routerStopCommand();
351
+ } catch (error) {
352
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
353
+ process.exit(1);
354
+ }
355
+ });
356
+
357
+ // Show router status
358
+ router
359
+ .command('status')
360
+ .description('Show router status and configuration')
361
+ .action(async () => {
362
+ try {
363
+ await routerStatusCommand();
364
+ } catch (error) {
365
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
366
+ process.exit(1);
367
+ }
368
+ });
369
+
370
+ // Restart router
371
+ router
372
+ .command('restart')
373
+ .description('Restart the router service')
374
+ .action(async () => {
375
+ try {
376
+ await routerRestartCommand();
377
+ } catch (error) {
378
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
379
+ process.exit(1);
380
+ }
381
+ });
382
+
383
+ // Configure router
384
+ router
385
+ .command('config')
386
+ .description('Update router configuration')
387
+ .option('-p, --port <number>', 'Update port number', parseInt)
388
+ .option('-h, --host <address>', 'Update bind address')
389
+ .option('--timeout <ms>', 'Update request timeout (milliseconds)', parseInt)
390
+ .option('--health-interval <ms>', 'Update health check interval (milliseconds)', parseInt)
391
+ .option('-v, --verbose [boolean]', 'Enable/disable verbose logging to file (true/false)', (val) => val === 'true' || val === '1')
392
+ .option('-r, --restart', 'Automatically restart router if running')
393
+ .action(async (options) => {
394
+ try {
395
+ await routerConfigCommand(options);
396
+ } catch (error) {
397
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
398
+ process.exit(1);
399
+ }
400
+ });
401
+
402
+ // Router logs
403
+ router
404
+ .command('logs')
405
+ .description('View router logs')
406
+ .option('-f, --follow', 'Follow logs in real-time (like tail -f)')
407
+ .option('-n, --lines <number>', 'Number of lines to show (default: 50)', parseInt)
408
+ .option('--stderr', 'Show system logs (stderr) instead of activity logs (stdout)')
409
+ .option('-v, --verbose', 'Show verbose JSON log file (if enabled)')
410
+ .option('--clear', 'Clear the log file')
411
+ .option('--rotate', 'Rotate the log file with timestamp')
412
+ .option('--clear-all', 'Clear all router logs (activity, system, verbose)')
413
+ .action(async (options) => {
414
+ try {
415
+ await routerLogsCommand(options);
416
+ } catch (error) {
417
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
418
+ process.exit(1);
419
+ }
420
+ });
421
+
311
422
  // Parse arguments
312
423
  program.parse();
@@ -31,7 +31,7 @@ export async function monitorCommand(identifier?: string): Promise<void> {
31
31
  screen.destroy();
32
32
  throw new Error(
33
33
  `Server not found: ${identifier}\n\n` +
34
- `Use: llamacpp ps\n` +
34
+ `Use: llamacpp ps (to list servers)\n` +
35
35
  `Or create a new server: llamacpp server create <model>`
36
36
  );
37
37
  }
@@ -1,14 +1,32 @@
1
1
  import chalk from 'chalk';
2
2
  import Table from 'cli-table3';
3
- import blessed from 'blessed';
4
- import { stateManager } from '../lib/state-manager';
5
- import { statusChecker } from '../lib/status-checker';
6
- import { formatUptime, formatBytes } from '../utils/format-utils';
7
- import { getProcessMemory } from '../utils/process-utils';
8
- import { createMultiServerMonitorUI } from '../tui/MultiServerMonitorApp.js';
3
+ import { stateManager } from '../lib/state-manager.js';
4
+ import { statusChecker } from '../lib/status-checker.js';
5
+ import { formatUptime, formatBytes } from '../utils/format-utils.js';
6
+ import { getProcessMemory } from '../utils/process-utils.js';
9
7
  import { ServerConfig } from '../types/server-config.js';
10
8
 
11
- async function showStaticTable(): Promise<void> {
9
+ const STATUS_CONFIG = {
10
+ running: { text: '✅ RUNNING', color: chalk.green },
11
+ crashed: { text: '❌ CRASHED', color: chalk.red },
12
+ stopped: { text: '⚠️ STOPPED', color: chalk.yellow },
13
+ } as const;
14
+
15
+ async function getServerMemory(server: ServerConfig): Promise<string> {
16
+ if (server.status !== 'running' || !server.pid) {
17
+ return '-';
18
+ }
19
+
20
+ const cpuMemoryBytes = await getProcessMemory(server.pid);
21
+ if (cpuMemoryBytes === null) {
22
+ return '-';
23
+ }
24
+
25
+ const metalMemoryBytes = server.metalMemoryMB ? server.metalMemoryMB * 1024 * 1024 : 0;
26
+ return formatBytes(cpuMemoryBytes + metalMemoryBytes);
27
+ }
28
+
29
+ export async function psCommand(): Promise<void> {
12
30
  const servers = await stateManager.getAllServers();
13
31
 
14
32
  if (servers.length === 0) {
@@ -17,60 +35,31 @@ async function showStaticTable(): Promise<void> {
17
35
  return;
18
36
  }
19
37
 
20
- // Update all server statuses
21
38
  console.log(chalk.dim('Checking server statuses...\n'));
22
- const updated = await statusChecker.updateAllServerStatuses();
39
+ const serversWithStatus = await statusChecker.updateAllServerStatuses();
23
40
 
24
41
  const table = new Table({
25
42
  head: ['SERVER ID', 'MODEL', 'PORT', 'STATUS', 'PID', 'MEMORY', 'UPTIME'],
26
43
  });
27
44
 
28
- let runningCount = 0;
29
- let stoppedCount = 0;
30
- let crashedCount = 0;
31
-
32
- for (const server of updated) {
33
- let statusText: string;
34
- let statusColor: (text: string) => string;
35
-
36
- switch (server.status) {
37
- case 'running':
38
- statusText = '✅ RUNNING';
39
- statusColor = chalk.green;
40
- runningCount++;
41
- break;
42
- case 'crashed':
43
- statusText = '❌ CRASHED';
44
- statusColor = chalk.red;
45
- crashedCount++;
46
- break;
47
- default:
48
- statusText = '⚠️ STOPPED';
49
- statusColor = chalk.yellow;
50
- stoppedCount++;
51
- }
52
-
53
- const uptime =
54
- server.status === 'running' && server.lastStarted
55
- ? formatUptime(server.lastStarted)
56
- : '-';
57
-
58
- // Get memory usage for running servers (CPU + Metal GPU memory)
59
- let memoryText = '-';
60
- if (server.status === 'running' && server.pid) {
61
- const cpuMemoryBytes = await getProcessMemory(server.pid);
62
- if (cpuMemoryBytes !== null) {
63
- const metalMemoryBytes = server.metalMemoryMB ? server.metalMemoryMB * 1024 * 1024 : 0;
64
- const totalMemoryBytes = cpuMemoryBytes + metalMemoryBytes;
65
- memoryText = formatBytes(totalMemoryBytes);
66
- }
67
- }
45
+ const counts = { running: 0, stopped: 0, crashed: 0 };
46
+
47
+ for (const server of serversWithStatus) {
48
+ const status = server.status || 'stopped';
49
+ const config = STATUS_CONFIG[status] || STATUS_CONFIG.stopped;
50
+ counts[status]++;
51
+
52
+ const uptime = server.status === 'running' && server.lastStarted
53
+ ? formatUptime(server.lastStarted)
54
+ : '-';
55
+
56
+ const memoryText = await getServerMemory(server);
68
57
 
69
58
  table.push([
70
59
  server.id,
71
60
  server.modelName,
72
61
  server.port.toString(),
73
- statusColor(statusText),
62
+ config.color(config.text),
74
63
  server.pid?.toString() || '-',
75
64
  memoryText,
76
65
  uptime,
@@ -80,94 +69,16 @@ async function showStaticTable(): Promise<void> {
80
69
  console.log(table.toString());
81
70
 
82
71
  const summary = [
83
- chalk.green(`${runningCount} running`),
84
- chalk.yellow(`${stoppedCount} stopped`),
72
+ chalk.green(`${counts.running} running`),
73
+ chalk.yellow(`${counts.stopped} stopped`),
85
74
  ];
86
- if (crashedCount > 0) {
87
- summary.push(chalk.red(`${crashedCount} crashed`));
75
+ if (counts.crashed > 0) {
76
+ summary.push(chalk.red(`${counts.crashed} crashed`));
88
77
  }
89
78
 
90
79
  console.log(chalk.dim(`\nTotal: ${servers.length} servers (${summary.join(', ')})`));
91
80
 
92
- if (crashedCount > 0) {
81
+ if (counts.crashed > 0) {
93
82
  console.log(chalk.red('\n⚠️ Some servers have crashed. Check logs with: llamacpp server logs <id> --errors'));
94
83
  }
95
84
  }
96
-
97
- export async function psCommand(identifier?: string, options?: { table?: boolean }): Promise<void> {
98
- // If --table flag is set, show static table (backward compatibility)
99
- if (options?.table) {
100
- await showStaticTable();
101
- return;
102
- }
103
-
104
- // Get all servers and update their statuses
105
- const servers = await stateManager.getAllServers();
106
-
107
- if (servers.length === 0) {
108
- console.log(chalk.yellow('No servers configured.'));
109
- console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
110
- return;
111
- }
112
-
113
- // Update all server statuses
114
- const updated = await statusChecker.updateAllServerStatuses();
115
-
116
- // If identifier is provided, find the server and jump to detail view
117
- if (identifier) {
118
- const server = await findServer(identifier, updated);
119
- if (!server) {
120
- console.log(chalk.red(`❌ Server not found: ${identifier}`));
121
- console.log(chalk.dim('\nAvailable servers:'));
122
- updated.forEach((s: ServerConfig) => {
123
- console.log(chalk.dim(` - ${s.id} (port ${s.port})`));
124
- });
125
- process.exit(1);
126
- }
127
-
128
- // Find the server index for direct jump
129
- const serverIndex = updated.findIndex(s => s.id === server.id);
130
-
131
- // Launch multi-server TUI with direct jump to detail view
132
- const screen = blessed.screen({
133
- smartCSR: true,
134
- title: 'llama.cpp Multi-Server Monitor',
135
- fullUnicode: true,
136
- });
137
-
138
- await createMultiServerMonitorUI(screen, updated, true, serverIndex); // fromPs = true, directJumpIndex
139
- return;
140
- }
141
-
142
- // No identifier - launch multi-server TUI
143
- const runningServers = updated.filter((s: ServerConfig) => s.status === 'running');
144
-
145
- // Launch multi-server TUI (shows all servers, not just running ones)
146
- const screen = blessed.screen({
147
- smartCSR: true,
148
- title: 'llama.cpp Multi-Server Monitor',
149
- fullUnicode: true,
150
- });
151
-
152
- await createMultiServerMonitorUI(screen, updated, true); // fromPs = true
153
- }
154
-
155
- // Helper function to find server by identifier
156
- async function findServer(identifier: string, servers: ServerConfig[]): Promise<ServerConfig | null> {
157
- // Try by port
158
- const port = parseInt(identifier);
159
- if (!isNaN(port)) {
160
- const server = servers.find(s => s.port === port);
161
- if (server) return server;
162
- }
163
-
164
- // Try by exact ID
165
- const byId = servers.find(s => s.id === identifier);
166
- if (byId) return byId;
167
-
168
- // Try by partial model name
169
- const byModel = servers.find(s => s.modelName.toLowerCase().includes(identifier.toLowerCase()));
170
- if (byModel) return byModel;
171
-
172
- return null;
173
- }
@@ -0,0 +1,116 @@
1
+ import chalk from 'chalk';
2
+ import { routerManager } from '../../lib/router-manager';
3
+
4
+ interface ConfigOptions {
5
+ port?: number;
6
+ host?: string;
7
+ timeout?: number;
8
+ healthInterval?: number;
9
+ verbose?: boolean;
10
+ restart?: boolean;
11
+ }
12
+
13
+ export async function routerConfigCommand(options: ConfigOptions): Promise<void> {
14
+ try {
15
+ // Check if router exists
16
+ const config = await routerManager.loadConfig();
17
+ if (!config) {
18
+ throw new Error('Router configuration not found. Use "llamacpp router start" to create it.');
19
+ }
20
+
21
+ // Check if any options were provided
22
+ const hasOptions = options.port || options.host || options.timeout || options.healthInterval || options.verbose !== undefined;
23
+ if (!hasOptions) {
24
+ throw new Error('No configuration options provided. Use --port, --host, --timeout, --health-interval, or --verbose');
25
+ }
26
+
27
+ const isRunning = config.status === 'running';
28
+
29
+ // Warn if running and no restart flag
30
+ if (isRunning && !options.restart) {
31
+ console.log(chalk.yellow('⚠️ Router is running. Changes will take effect after restart.'));
32
+ console.log(chalk.dim(' Use --restart flag to apply changes immediately.\n'));
33
+ }
34
+
35
+ // Prepare updates
36
+ const updates: any = {};
37
+ const changes: string[] = [];
38
+
39
+ if (options.port !== undefined) {
40
+ changes.push(`Port: ${config.port} → ${options.port}`);
41
+ updates.port = options.port;
42
+ }
43
+
44
+ if (options.host !== undefined) {
45
+ changes.push(`Host: ${config.host} → ${options.host}`);
46
+ updates.host = options.host;
47
+ }
48
+
49
+ if (options.timeout !== undefined) {
50
+ changes.push(`Request Timeout: ${config.requestTimeout}ms → ${options.timeout}ms`);
51
+ updates.requestTimeout = options.timeout;
52
+ }
53
+
54
+ if (options.healthInterval !== undefined) {
55
+ changes.push(`Health Check Interval: ${config.healthCheckInterval}ms → ${options.healthInterval}ms`);
56
+ updates.healthCheckInterval = options.healthInterval;
57
+ }
58
+
59
+ if (options.verbose !== undefined) {
60
+ const verboseStr = (val: boolean) => val ? 'enabled' : 'disabled';
61
+ changes.push(`Verbose Logging: ${verboseStr(config.verbose)} → ${verboseStr(options.verbose)}`);
62
+ updates.verbose = options.verbose;
63
+ }
64
+
65
+ // Display changes
66
+ console.log(chalk.blue('📝 Configuration changes:'));
67
+ console.log();
68
+ changes.forEach(change => {
69
+ console.log(chalk.dim(` ${change}`));
70
+ });
71
+ console.log();
72
+
73
+ // Apply changes
74
+ if (isRunning && options.restart) {
75
+ console.log(chalk.blue('⏹️ Stopping router...'));
76
+ await routerManager.stop();
77
+ }
78
+
79
+ // Update config
80
+ await routerManager.updateConfig(updates);
81
+
82
+ // Regenerate plist if port or host changed
83
+ if (options.port !== undefined || options.host !== undefined) {
84
+ const updatedConfig = await routerManager.loadConfig();
85
+ if (updatedConfig) {
86
+ await routerManager.createPlist(updatedConfig);
87
+ }
88
+ }
89
+
90
+ // Restart if requested
91
+ if (isRunning && options.restart) {
92
+ console.log(chalk.blue('▶️ Starting router...'));
93
+ await routerManager.start();
94
+
95
+ const finalConfig = await routerManager.loadConfig();
96
+ console.log();
97
+ console.log(chalk.green('✅ Router restarted with new configuration'));
98
+ console.log();
99
+ console.log(chalk.dim(`Endpoint: http://${finalConfig?.host}:${finalConfig?.port}`));
100
+ } else {
101
+ console.log(chalk.green('✅ Configuration updated'));
102
+
103
+ if (isRunning) {
104
+ console.log();
105
+ console.log(chalk.yellow('⚠️ Restart required to apply changes:'));
106
+ console.log(chalk.dim(' llamacpp router restart'));
107
+ } else {
108
+ console.log();
109
+ console.log(chalk.dim('Start router to use new configuration:'));
110
+ console.log(chalk.dim(' llamacpp router start'));
111
+ }
112
+ }
113
+ } catch (error) {
114
+ throw new Error(`Failed to update router configuration: ${(error as Error).message}`);
115
+ }
116
+ }