@appkit/llamacpp-cli 1.6.0 → 1.7.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 (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +67 -1
  3. package/dist/cli.js +23 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/config.d.ts.map +1 -1
  6. package/dist/commands/config.js +15 -1
  7. package/dist/commands/config.js.map +1 -1
  8. package/dist/commands/create.d.ts.map +1 -1
  9. package/dist/commands/create.js +0 -1
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/delete.js +12 -10
  12. package/dist/commands/delete.js.map +1 -1
  13. package/dist/commands/logs-all.d.ts +9 -0
  14. package/dist/commands/logs-all.d.ts.map +1 -0
  15. package/dist/commands/logs-all.js +209 -0
  16. package/dist/commands/logs-all.js.map +1 -0
  17. package/dist/commands/logs.d.ts +4 -0
  18. package/dist/commands/logs.d.ts.map +1 -1
  19. package/dist/commands/logs.js +108 -2
  20. package/dist/commands/logs.js.map +1 -1
  21. package/dist/commands/rm.d.ts.map +1 -1
  22. package/dist/commands/rm.js +5 -12
  23. package/dist/commands/rm.js.map +1 -1
  24. package/dist/commands/server-show.d.ts.map +1 -1
  25. package/dist/commands/server-show.js +20 -0
  26. package/dist/commands/server-show.js.map +1 -1
  27. package/dist/commands/start.d.ts.map +1 -1
  28. package/dist/commands/start.js +22 -7
  29. package/dist/commands/start.js.map +1 -1
  30. package/dist/commands/stop.js +3 -3
  31. package/dist/commands/stop.js.map +1 -1
  32. package/dist/utils/log-utils.d.ts +43 -0
  33. package/dist/utils/log-utils.d.ts.map +1 -0
  34. package/dist/utils/log-utils.js +190 -0
  35. package/dist/utils/log-utils.js.map +1 -0
  36. package/package.json +1 -1
  37. package/src/cli.ts +23 -1
  38. package/src/commands/config.ts +15 -1
  39. package/src/commands/create.ts +0 -1
  40. package/src/commands/delete.ts +10 -10
  41. package/src/commands/logs-all.ts +251 -0
  42. package/src/commands/logs.ts +138 -2
  43. package/src/commands/rm.ts +5 -12
  44. package/src/commands/server-show.ts +25 -0
  45. package/src/commands/start.ts +22 -7
  46. package/src/commands/stop.ts +3 -3
  47. package/src/utils/log-utils.ts +178 -0
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getFileSize = getFileSize;
37
+ exports.formatFileSize = formatFileSize;
38
+ exports.rotateLogFile = rotateLogFile;
39
+ exports.clearLogFile = clearLogFile;
40
+ exports.autoRotateIfNeeded = autoRotateIfNeeded;
41
+ exports.getArchivedLogInfo = getArchivedLogInfo;
42
+ exports.deleteArchivedLogs = deleteArchivedLogs;
43
+ const fs = __importStar(require("fs/promises"));
44
+ const path = __importStar(require("path"));
45
+ const file_utils_1 = require("./file-utils");
46
+ /**
47
+ * Get the size of a file in bytes
48
+ */
49
+ async function getFileSize(filePath) {
50
+ try {
51
+ const stats = await fs.stat(filePath);
52
+ return stats.size;
53
+ }
54
+ catch {
55
+ return 0;
56
+ }
57
+ }
58
+ /**
59
+ * Format bytes to human-readable size
60
+ */
61
+ function formatFileSize(bytes) {
62
+ if (bytes === 0)
63
+ return '0 B';
64
+ const k = 1024;
65
+ const sizes = ['B', 'KB', 'MB', 'GB'];
66
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
67
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
68
+ }
69
+ /**
70
+ * Rotate a log file with timestamp
71
+ * Renames current log to <name>.YYYY-MM-DD-HH-MM-SS.<ext>
72
+ * Returns the new archived filename
73
+ */
74
+ async function rotateLogFile(logPath) {
75
+ if (!(await (0, file_utils_1.fileExists)(logPath))) {
76
+ throw new Error(`Log file does not exist: ${logPath}`);
77
+ }
78
+ // Get file size before rotation
79
+ const size = await getFileSize(logPath);
80
+ if (size === 0) {
81
+ throw new Error('Log file is empty, nothing to rotate');
82
+ }
83
+ // Generate timestamp
84
+ const timestamp = new Date()
85
+ .toISOString()
86
+ .replace(/T/, '-')
87
+ .replace(/:/g, '-')
88
+ .replace(/\..+/, '');
89
+ // Parse path components
90
+ const dir = path.dirname(logPath);
91
+ const ext = path.extname(logPath);
92
+ const basename = path.basename(logPath, ext);
93
+ // New archived filename
94
+ const archivedPath = path.join(dir, `${basename}.${timestamp}${ext}`);
95
+ // Rename current log to archived version
96
+ await fs.rename(logPath, archivedPath);
97
+ return archivedPath;
98
+ }
99
+ /**
100
+ * Clear (truncate) a log file to zero bytes
101
+ */
102
+ async function clearLogFile(logPath) {
103
+ if (!(await (0, file_utils_1.fileExists)(logPath))) {
104
+ throw new Error(`Log file does not exist: ${logPath}`);
105
+ }
106
+ // Truncate file to 0 bytes
107
+ await fs.truncate(logPath, 0);
108
+ }
109
+ /**
110
+ * Auto-rotate log files if they exceed threshold
111
+ * Returns true if rotation occurred, false otherwise
112
+ */
113
+ async function autoRotateIfNeeded(stdoutPath, stderrPath, thresholdMB = 100) {
114
+ const thresholdBytes = thresholdMB * 1024 * 1024;
115
+ const rotatedFiles = [];
116
+ // Check stdout
117
+ if (await (0, file_utils_1.fileExists)(stdoutPath)) {
118
+ const stdoutSize = await getFileSize(stdoutPath);
119
+ if (stdoutSize > thresholdBytes) {
120
+ const archived = await rotateLogFile(stdoutPath);
121
+ rotatedFiles.push(archived);
122
+ }
123
+ }
124
+ // Check stderr
125
+ if (await (0, file_utils_1.fileExists)(stderrPath)) {
126
+ const stderrSize = await getFileSize(stderrPath);
127
+ if (stderrSize > thresholdBytes) {
128
+ const archived = await rotateLogFile(stderrPath);
129
+ rotatedFiles.push(archived);
130
+ }
131
+ }
132
+ return {
133
+ rotated: rotatedFiles.length > 0,
134
+ files: rotatedFiles,
135
+ };
136
+ }
137
+ /**
138
+ * Get information about archived log files for a server
139
+ * Returns count and total size of timestamped archived logs
140
+ */
141
+ async function getArchivedLogInfo(serverId) {
142
+ const logsDir = (0, file_utils_1.getLogsDir)();
143
+ let count = 0;
144
+ let totalSize = 0;
145
+ try {
146
+ const files = await fs.readdir(logsDir);
147
+ // Pattern matches: server-id.YYYY-MM-DD-HH-MM-SS.{stdout,stderr}
148
+ const pattern = new RegExp(`^${serverId}\\.(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})\\.(stdout|stderr)$`);
149
+ for (const file of files) {
150
+ if (pattern.test(file)) {
151
+ count++;
152
+ const filePath = path.join(logsDir, file);
153
+ totalSize += await getFileSize(filePath);
154
+ }
155
+ }
156
+ }
157
+ catch {
158
+ // Directory doesn't exist or can't be read
159
+ return { count: 0, totalSize: 0 };
160
+ }
161
+ return { count, totalSize };
162
+ }
163
+ /**
164
+ * Delete all archived log files for a server
165
+ * Returns count and total size of deleted files
166
+ */
167
+ async function deleteArchivedLogs(serverId) {
168
+ const logsDir = (0, file_utils_1.getLogsDir)();
169
+ let count = 0;
170
+ let totalSize = 0;
171
+ try {
172
+ const files = await fs.readdir(logsDir);
173
+ // Pattern matches: server-id.YYYY-MM-DD-HH-MM-SS.{stdout,stderr}
174
+ const pattern = new RegExp(`^${serverId}\\.(\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2})\\.(stdout|stderr)$`);
175
+ for (const file of files) {
176
+ if (pattern.test(file)) {
177
+ const filePath = path.join(logsDir, file);
178
+ const size = await getFileSize(filePath);
179
+ await fs.unlink(filePath);
180
+ count++;
181
+ totalSize += size;
182
+ }
183
+ }
184
+ }
185
+ catch (error) {
186
+ throw new Error(`Failed to delete archived logs: ${error.message}`);
187
+ }
188
+ return { count, totalSize };
189
+ }
190
+ //# sourceMappingURL=log-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-utils.js","sourceRoot":"","sources":["../../src/utils/log-utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appkit/llamacpp-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.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
@@ -12,6 +12,7 @@ import { deleteCommand } from './commands/delete';
12
12
  import { pullCommand } from './commands/pull';
13
13
  import { rmCommand } from './commands/rm';
14
14
  import { logsCommand } from './commands/logs';
15
+ import { logsAllCommand } from './commands/logs-all';
15
16
  import { searchCommand } from './commands/search';
16
17
  import { showCommand } from './commands/show';
17
18
  import { serverShowCommand } from './commands/server-show';
@@ -25,7 +26,7 @@ const program = new Command();
25
26
  program
26
27
  .name('llamacpp')
27
28
  .description('CLI tool to manage local llama.cpp servers on macOS')
28
- .version(packageJson.version);
29
+ .version(packageJson.version, '-v, --version', 'Output the version number');
29
30
 
30
31
  // List models
31
32
  program
@@ -54,6 +55,23 @@ program
54
55
  }
55
56
  });
56
57
 
58
+ // View all server logs
59
+ program
60
+ .command('logs')
61
+ .description('View log sizes for all servers (with batch operations)')
62
+ .option('--clear', 'Clear current logs for all servers')
63
+ .option('--clear-archived', 'Delete only archived logs for all servers')
64
+ .option('--clear-all', 'Clear current + delete archived logs for all servers')
65
+ .option('--rotate', 'Rotate logs for all servers with timestamps')
66
+ .action(async (options) => {
67
+ try {
68
+ await logsAllCommand(options);
69
+ } catch (error) {
70
+ console.error(chalk.red('❌ Error:'), (error as Error).message);
71
+ process.exit(1);
72
+ }
73
+ });
74
+
57
75
  // Search for models
58
76
  program
59
77
  .command('search')
@@ -261,6 +279,10 @@ server
261
279
  .option('--verbose', 'Show all messages including debug internals')
262
280
  .option('--filter <pattern>', 'Custom grep pattern for filtering')
263
281
  .option('--stdout', 'Show stdout instead of stderr (rarely needed)')
282
+ .option('--clear', 'Clear (truncate) log file to zero bytes')
283
+ .option('--clear-archived', 'Delete only archived logs (preserves current logs)')
284
+ .option('--clear-all', 'Clear current logs AND delete all archived logs')
285
+ .option('--rotate', 'Rotate log file with timestamp (preserves old logs)')
264
286
  .action(async (identifier: string, options) => {
265
287
  try {
266
288
  await logsCommand(identifier, options);
@@ -3,6 +3,7 @@ import { stateManager } from '../lib/state-manager';
3
3
  import { statusChecker } from '../lib/status-checker';
4
4
  import { launchctlManager } from '../lib/launchctl-manager';
5
5
  import { configGenerator } from '../lib/config-generator';
6
+ import { autoRotateIfNeeded } from '../utils/log-utils';
6
7
 
7
8
  export interface ConfigUpdateOptions {
8
9
  host?: string;
@@ -109,7 +110,6 @@ export async function serverConfigCommand(
109
110
  // Unload service if running and restart flag is set (forces plist re-read)
110
111
  if (wasRunning && options.restart) {
111
112
  console.log(chalk.dim('Stopping server...'));
112
- await launchctlManager.stopService(server.label);
113
113
  await launchctlManager.unloadService(server.plistPath);
114
114
 
115
115
  // Wait a moment for clean shutdown
@@ -146,6 +146,20 @@ export async function serverConfigCommand(
146
146
 
147
147
  // Restart server if it was running and restart flag is set
148
148
  if (wasRunning && options.restart) {
149
+ // Auto-rotate logs if they exceed 100MB
150
+ try {
151
+ const result = await autoRotateIfNeeded(updatedConfig.stdoutPath, updatedConfig.stderrPath, 100);
152
+ if (result.rotated) {
153
+ console.log(chalk.dim('Auto-rotated large log files:'));
154
+ for (const file of result.files) {
155
+ console.log(chalk.dim(` → ${file}`));
156
+ }
157
+ }
158
+ } catch (error) {
159
+ // Non-fatal, just warn
160
+ console.log(chalk.yellow(`⚠️ Failed to rotate logs: ${(error as Error).message}`));
161
+ }
162
+
149
163
  console.log(chalk.dim('Starting server with new configuration...'));
150
164
  await launchctlManager.loadService(updatedConfig.plistPath);
151
165
  await launchctlManager.startService(updatedConfig.label);
@@ -152,7 +152,6 @@ export async function createCommand(model: string, options: CreateOptions): Prom
152
152
 
153
153
  if (!started) {
154
154
  // Clean up if startup fails
155
- await launchctlManager.stopService(config.label);
156
155
  await launchctlManager.unloadService(config.plistPath);
157
156
  await launchctlManager.deletePlist(config.plistPath);
158
157
  throw new Error('Server failed to start. Check logs with: llamacpp server logs --errors');
@@ -24,21 +24,21 @@ export async function deleteCommand(identifier: string): Promise<void> {
24
24
  console.log();
25
25
  console.log(chalk.blue(`🗑️ Deleting server ${server.modelName}...`));
26
26
 
27
- // Stop server if running
27
+ // Unload service (stops and removes from launchd)
28
28
  if (server.status === 'running') {
29
- console.log(chalk.dim('Stopping server...'));
30
- try {
31
- await launchctlManager.stopService(server.label);
29
+ console.log(chalk.dim('Stopping and unloading service...'));
30
+ } else {
31
+ console.log(chalk.dim('Unloading service...'));
32
+ }
33
+ try {
34
+ await launchctlManager.unloadService(server.plistPath);
35
+ if (server.status === 'running') {
32
36
  await launchctlManager.waitForServiceStop(server.label, 5000);
33
- } catch (error) {
34
- console.log(chalk.yellow('⚠️ Failed to stop server gracefully'));
35
37
  }
38
+ } catch (error) {
39
+ console.log(chalk.yellow('⚠️ Failed to unload service gracefully'));
36
40
  }
37
41
 
38
- // Unload service
39
- console.log(chalk.dim('Unloading launchctl service...'));
40
- await launchctlManager.unloadService(server.plistPath);
41
-
42
42
  // Delete plist
43
43
  console.log(chalk.dim('Deleting plist file...'));
44
44
  await launchctlManager.deletePlist(server.plistPath);
@@ -0,0 +1,251 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { stateManager } from '../lib/state-manager';
4
+ import { fileExists } from '../utils/file-utils';
5
+ import {
6
+ getFileSize,
7
+ formatFileSize,
8
+ getArchivedLogInfo,
9
+ clearLogFile,
10
+ rotateLogFile,
11
+ deleteArchivedLogs,
12
+ } from '../utils/log-utils';
13
+
14
+ interface LogsAllOptions {
15
+ clear?: boolean;
16
+ clearArchived?: boolean;
17
+ clearAll?: boolean;
18
+ rotate?: boolean;
19
+ }
20
+
21
+ export async function logsAllCommand(options: LogsAllOptions): Promise<void> {
22
+ // Get all servers
23
+ const servers = await stateManager.getAllServers();
24
+
25
+ if (servers.length === 0) {
26
+ console.log(chalk.yellow('⚠️ No servers found'));
27
+ console.log(chalk.dim('\nCreate a server: llamacpp server create <model-filename>'));
28
+ return;
29
+ }
30
+
31
+ // Handle batch operations
32
+ if (options.clear || options.clearArchived || options.clearAll || options.rotate) {
33
+ await handleBatchOperation(servers, options);
34
+ return;
35
+ }
36
+
37
+ // Show table of log information
38
+ await showLogsTable(servers);
39
+ }
40
+
41
+ async function showLogsTable(servers: any[]): Promise<void> {
42
+ const table = new Table({
43
+ head: [
44
+ chalk.bold('Server ID'),
45
+ chalk.bold('Current Stderr'),
46
+ chalk.bold('Current Stdout'),
47
+ chalk.bold('Archived'),
48
+ chalk.bold('Total'),
49
+ ],
50
+ colWidths: [30, 18, 18, 18, 18],
51
+ });
52
+
53
+ let totalCurrent = 0;
54
+ let totalArchived = 0;
55
+
56
+ for (const server of servers) {
57
+ // Get current log sizes
58
+ const stderrSize = (await fileExists(server.stderrPath))
59
+ ? await getFileSize(server.stderrPath)
60
+ : 0;
61
+ const stdoutSize = (await fileExists(server.stdoutPath))
62
+ ? await getFileSize(server.stdoutPath)
63
+ : 0;
64
+
65
+ // Get archived info
66
+ const archivedInfo = await getArchivedLogInfo(server.id);
67
+
68
+ const currentTotal = stderrSize + stdoutSize;
69
+ const total = currentTotal + archivedInfo.totalSize;
70
+
71
+ totalCurrent += currentTotal;
72
+ totalArchived += archivedInfo.totalSize;
73
+
74
+ table.push([
75
+ server.id,
76
+ formatFileSize(stderrSize),
77
+ formatFileSize(stdoutSize),
78
+ archivedInfo.count > 0
79
+ ? `${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count})`
80
+ : formatFileSize(0),
81
+ formatFileSize(total),
82
+ ]);
83
+ }
84
+
85
+ console.log(chalk.bold('\nServer Logs Overview:'));
86
+ console.log(table.toString());
87
+
88
+ console.log(chalk.dim('\nTotals:'));
89
+ console.log(chalk.dim(` Current logs: ${formatFileSize(totalCurrent)}`));
90
+ console.log(chalk.dim(` Archived logs: ${formatFileSize(totalArchived)}`));
91
+ console.log(chalk.dim(` Grand total: ${formatFileSize(totalCurrent + totalArchived)}`));
92
+
93
+ console.log(chalk.dim('\nBatch operations:'));
94
+ console.log(chalk.dim(' llamacpp logs --clear Clear all current logs'));
95
+ console.log(chalk.dim(' llamacpp logs --clear-archived Delete only archived logs'));
96
+ console.log(chalk.dim(' llamacpp logs --clear-all Clear current + delete archives'));
97
+ console.log(chalk.dim(' llamacpp logs --rotate Rotate all logs with timestamps'));
98
+ }
99
+
100
+ async function handleBatchOperation(
101
+ servers: any[],
102
+ options: LogsAllOptions
103
+ ): Promise<void> {
104
+ if (options.clearArchived) {
105
+ console.log(chalk.blue('🗑️ Deleting archived logs for all servers...'));
106
+ console.log();
107
+
108
+ let totalFreed = 0;
109
+ let totalFiles = 0;
110
+ let serversProcessed = 0;
111
+
112
+ for (const server of servers) {
113
+ const archivedInfo = await deleteArchivedLogs(server.id);
114
+
115
+ if (archivedInfo.count > 0) {
116
+ console.log(chalk.dim(` ${server.id}: ${formatFileSize(archivedInfo.totalSize)} (${archivedInfo.count} file${archivedInfo.count !== 1 ? 's' : ''})`));
117
+ totalFreed += archivedInfo.totalSize;
118
+ totalFiles += archivedInfo.count;
119
+ serversProcessed++;
120
+ }
121
+ }
122
+
123
+ console.log();
124
+ if (serversProcessed === 0) {
125
+ console.log(chalk.yellow('⚠️ No archived logs found'));
126
+ console.log(chalk.dim(' Archived logs are created via --rotate or automatic rotation'));
127
+ } else {
128
+ console.log(chalk.green(`✅ Deleted archived logs for ${serversProcessed} server${serversProcessed !== 1 ? 's' : ''}`));
129
+ console.log(chalk.dim(` Files deleted: ${totalFiles}`));
130
+ console.log(chalk.dim(` Total freed: ${formatFileSize(totalFreed)}`));
131
+ console.log(chalk.dim(` Current logs preserved`));
132
+ }
133
+ } else if (options.clearAll) {
134
+ console.log(chalk.blue('🗑️ Clearing all logs (current + archived) for all servers...'));
135
+ console.log();
136
+
137
+ let totalFreed = 0;
138
+ let serversProcessed = 0;
139
+
140
+ for (const server of servers) {
141
+ let serverTotal = 0;
142
+
143
+ // Clear current stderr
144
+ if (await fileExists(server.stderrPath)) {
145
+ serverTotal += await getFileSize(server.stderrPath);
146
+ await clearLogFile(server.stderrPath);
147
+ }
148
+
149
+ // Clear current stdout
150
+ if (await fileExists(server.stdoutPath)) {
151
+ serverTotal += await getFileSize(server.stdoutPath);
152
+ await clearLogFile(server.stdoutPath);
153
+ }
154
+
155
+ // Delete archived logs
156
+ const archivedInfo = await deleteArchivedLogs(server.id);
157
+ serverTotal += archivedInfo.totalSize;
158
+
159
+ if (serverTotal > 0) {
160
+ console.log(chalk.dim(` ${server.id}: ${formatFileSize(serverTotal)}`));
161
+ totalFreed += serverTotal;
162
+ serversProcessed++;
163
+ }
164
+ }
165
+
166
+ console.log();
167
+ console.log(chalk.green(`✅ Cleared all logs for ${serversProcessed} server${serversProcessed !== 1 ? 's' : ''}`));
168
+ console.log(chalk.dim(` Total freed: ${formatFileSize(totalFreed)}`));
169
+ } else if (options.clear) {
170
+ console.log(chalk.blue('🗑️ Clearing current logs for all servers...'));
171
+ console.log();
172
+
173
+ let totalFreed = 0;
174
+ let serversProcessed = 0;
175
+
176
+ for (const server of servers) {
177
+ let serverTotal = 0;
178
+
179
+ // Clear current stderr
180
+ if (await fileExists(server.stderrPath)) {
181
+ serverTotal += await getFileSize(server.stderrPath);
182
+ await clearLogFile(server.stderrPath);
183
+ }
184
+
185
+ // Clear current stdout
186
+ if (await fileExists(server.stdoutPath)) {
187
+ serverTotal += await getFileSize(server.stdoutPath);
188
+ await clearLogFile(server.stdoutPath);
189
+ }
190
+
191
+ if (serverTotal > 0) {
192
+ console.log(chalk.dim(` ${server.id}: ${formatFileSize(serverTotal)}`));
193
+ totalFreed += serverTotal;
194
+ serversProcessed++;
195
+ }
196
+ }
197
+
198
+ console.log();
199
+ console.log(chalk.green(`✅ Cleared current logs for ${serversProcessed} server${serversProcessed !== 1 ? 's' : ''}`));
200
+ console.log(chalk.dim(` Total freed: ${formatFileSize(totalFreed)}`));
201
+ console.log(chalk.dim(` Archived logs preserved`));
202
+ } else if (options.rotate) {
203
+ console.log(chalk.blue('🔄 Rotating logs for all servers...'));
204
+ console.log();
205
+
206
+ let totalRotated = 0;
207
+ let filesRotated = 0;
208
+
209
+ for (const server of servers) {
210
+ const rotatedFiles: string[] = [];
211
+
212
+ // Rotate stderr if it has content
213
+ if (await fileExists(server.stderrPath)) {
214
+ const size = await getFileSize(server.stderrPath);
215
+ if (size > 0) {
216
+ try {
217
+ const archivedPath = await rotateLogFile(server.stderrPath);
218
+ rotatedFiles.push(archivedPath);
219
+ totalRotated += size;
220
+ filesRotated++;
221
+ } catch {
222
+ // Ignore empty files
223
+ }
224
+ }
225
+ }
226
+
227
+ // Rotate stdout if it has content
228
+ if (await fileExists(server.stdoutPath)) {
229
+ const size = await getFileSize(server.stdoutPath);
230
+ if (size > 0) {
231
+ try {
232
+ const archivedPath = await rotateLogFile(server.stdoutPath);
233
+ rotatedFiles.push(archivedPath);
234
+ totalRotated += size;
235
+ filesRotated++;
236
+ } catch {
237
+ // Ignore empty files
238
+ }
239
+ }
240
+ }
241
+
242
+ if (rotatedFiles.length > 0) {
243
+ console.log(chalk.dim(` ${server.id}: ${rotatedFiles.length} file${rotatedFiles.length !== 1 ? 's' : ''}`));
244
+ }
245
+ }
246
+
247
+ console.log();
248
+ console.log(chalk.green(`✅ Rotated ${filesRotated} log file${filesRotated !== 1 ? 's' : ''}`));
249
+ console.log(chalk.dim(` Total archived: ${formatFileSize(totalRotated)}`));
250
+ }
251
+ }