@oussema_mili/test-pkg-123 1.1.22

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.

Potentially problematic release.


This version of @oussema_mili/test-pkg-123 might be problematic. Click here for more details.

Files changed (49) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +220 -0
  3. package/auth-callback.html +97 -0
  4. package/auth.js +276 -0
  5. package/cli-commands.js +1923 -0
  6. package/containerManager.js +304 -0
  7. package/daemon/agentRunner.js +429 -0
  8. package/daemon/daemonEntry.js +64 -0
  9. package/daemon/daemonManager.js +271 -0
  10. package/daemon/logManager.js +227 -0
  11. package/dist/styles.css +504 -0
  12. package/docker-actions/apps.js +3938 -0
  13. package/docker-actions/config-transformer.js +380 -0
  14. package/docker-actions/containers.js +355 -0
  15. package/docker-actions/general.js +171 -0
  16. package/docker-actions/images.js +1128 -0
  17. package/docker-actions/logs.js +224 -0
  18. package/docker-actions/metrics.js +270 -0
  19. package/docker-actions/registry.js +1100 -0
  20. package/docker-actions/setup-tasks.js +859 -0
  21. package/docker-actions/terminal.js +247 -0
  22. package/docker-actions/volumes.js +696 -0
  23. package/helper-functions.js +193 -0
  24. package/index.html +83 -0
  25. package/index.js +341 -0
  26. package/package.json +82 -0
  27. package/postcss.config.mjs +5 -0
  28. package/scripts/release.sh +212 -0
  29. package/setup/setupWizard.js +403 -0
  30. package/store/agentSessionStore.js +51 -0
  31. package/store/agentStore.js +113 -0
  32. package/store/configStore.js +171 -0
  33. package/store/daemonStore.js +217 -0
  34. package/store/deviceCredentialStore.js +107 -0
  35. package/store/npmTokenStore.js +65 -0
  36. package/store/registryStore.js +329 -0
  37. package/store/setupState.js +147 -0
  38. package/styles.css +1 -0
  39. package/utils/appLogger.js +223 -0
  40. package/utils/deviceInfo.js +98 -0
  41. package/utils/ecrAuth.js +225 -0
  42. package/utils/encryption.js +112 -0
  43. package/utils/envSetup.js +44 -0
  44. package/utils/errorHandler.js +327 -0
  45. package/utils/portUtils.js +59 -0
  46. package/utils/prerequisites.js +323 -0
  47. package/utils/prompts.js +318 -0
  48. package/utils/ssl-certificates.js +256 -0
  49. package/websocket-server.js +415 -0
@@ -0,0 +1,329 @@
1
+ import os from "os";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import chalk from "chalk";
5
+ import dotenv from "dotenv";
6
+ dotenv.config();
7
+
8
+ const fsPromises = fs.promises;
9
+
10
+ const AGENT_ROOT_DIR = process.env.AGENT_ROOT_DIR || ".fenwave";
11
+ const REGISTRIES_DIR = process.env.REGISTRIES_DIR || "registries";
12
+ const REGISTRIES_FILE = process.env.REGISTRIES_FILE || "credentials.json";
13
+ const REGISTRY_FILE = path.join(
14
+ os.homedir(),
15
+ AGENT_ROOT_DIR,
16
+ REGISTRIES_DIR,
17
+ REGISTRIES_FILE
18
+ );
19
+
20
+ const SCHEMA_VERSION = 1;
21
+
22
+ /**
23
+ * Registry Store for managing registry credentials
24
+ */
25
+ class RegistryStore {
26
+ constructor() {
27
+ this.registries = new Map();
28
+ this.initialized = false;
29
+ }
30
+
31
+ /**
32
+ * Initialize the store - load registries
33
+ */
34
+ async initialize() {
35
+ if (this.initialized) return;
36
+
37
+ try {
38
+ // Ensure directory exists
39
+ await this.ensureDirectory();
40
+
41
+ // Load existing registries
42
+ await this.loadRegistries();
43
+
44
+ this.initialized = true;
45
+ } catch (error) {
46
+ console.error(
47
+ chalk.red("❌ Failed to initialize registry store:", error.message)
48
+ );
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Ensure the .fenwave/registries directory exists
55
+ */
56
+ async ensureDirectory() {
57
+ const dir = path.dirname(REGISTRY_FILE);
58
+ try {
59
+ await fsPromises.mkdir(dir, { recursive: true, mode: 0o700 });
60
+ } catch (error) {
61
+ if (error.code !== "EEXIST") {
62
+ throw error;
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Load registries from file
69
+ */
70
+ async loadRegistries() {
71
+ try {
72
+ const data = await fsPromises.readFile(REGISTRY_FILE, "utf8");
73
+ const parsed = JSON.parse(data);
74
+
75
+ if (parsed.version !== SCHEMA_VERSION) {
76
+ console.warn(
77
+ `⚠️ Registry file version mismatch. Expected ${SCHEMA_VERSION}, got ${parsed.version}`
78
+ );
79
+ }
80
+
81
+ // Load registries with plain text credentials
82
+ let hasActiveRegistry = false;
83
+
84
+ for (const registry of parsed.registries || []) {
85
+ try {
86
+ // Ensure active field exists (for backward compatibility)
87
+ if (registry.active === undefined) {
88
+ registry.active = false;
89
+ }
90
+
91
+ if (registry.active) {
92
+ hasActiveRegistry = true;
93
+ }
94
+
95
+ // Store registry with credentials directly
96
+ this.registries.set(registry.id, registry);
97
+ } catch (error) {
98
+ console.error(
99
+ chalk.red(`❌ Failed to load registry ${registry.id}:`),
100
+ error.message
101
+ );
102
+ // Skip this registry but continue with others
103
+ }
104
+ }
105
+
106
+ // If no registry is marked as active, make the first one active
107
+ if (!hasActiveRegistry && this.registries.size > 0) {
108
+ const firstRegistry = this.registries.values().next().value;
109
+ if (firstRegistry) {
110
+ firstRegistry.active = true;
111
+ firstRegistry.updatedAt = new Date().toISOString();
112
+ // Save the updated registries with the active flag
113
+ await this.saveRegistries();
114
+ }
115
+ }
116
+ } catch (error) {
117
+ if (error.code === "ENOENT") {
118
+ // File doesn't exist yet, start with empty registries
119
+ return;
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Save registries to file with plain text credentials
127
+ */
128
+ async saveRegistries() {
129
+ try {
130
+ const registriesToSave = [];
131
+
132
+ for (const [id, registry] of this.registries.entries()) {
133
+ // Save registry with credentials directly
134
+ registriesToSave.push(registry);
135
+ }
136
+
137
+ const data = {
138
+ version: SCHEMA_VERSION,
139
+ registries: registriesToSave,
140
+ updatedAt: new Date().toISOString(),
141
+ };
142
+
143
+ // Write atomically
144
+ const tempFile = REGISTRY_FILE + ".tmp";
145
+ await fsPromises.writeFile(tempFile, JSON.stringify(data, null, 2), {
146
+ mode: 0o600,
147
+ });
148
+ await fsPromises.rename(tempFile, REGISTRY_FILE);
149
+ } catch (error) {
150
+ console.error(chalk.red("❌ Failed to save registries:", error.message));
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Add or update a registry
157
+ */
158
+ async upsertRegistry(registryData) {
159
+ await this.initialize();
160
+
161
+ const { id, credentials, ...metadata } = registryData;
162
+
163
+ const registry = {
164
+ id,
165
+ ...metadata,
166
+ credentials,
167
+ updatedAt: new Date().toISOString(),
168
+ };
169
+
170
+ if (!this.registries.has(id)) {
171
+ registry.createdAt = registry.updatedAt;
172
+ // If this is the first registry, make it active by default
173
+ if (this.registries.size === 0) {
174
+ registry.active = true;
175
+ } else {
176
+ registry.active = false;
177
+ }
178
+ }
179
+
180
+ this.registries.set(id, registry);
181
+ await this.saveRegistries();
182
+
183
+ return registry;
184
+ }
185
+
186
+ /**
187
+ * Get all registries (returns metadata only, no credentials)
188
+ */
189
+ async getAllRegistries() {
190
+ await this.initialize();
191
+
192
+ const result = [];
193
+ for (const [id, registry] of this.registries.entries()) {
194
+ const { credentials, ...safeRegistry } = registry;
195
+
196
+ // Add type-specific safe metadata
197
+ const metadata = { ...safeRegistry };
198
+ if (registry.type === "ecr" && credentials) {
199
+ metadata.accessKeyId = credentials.accessKeyId;
200
+ metadata.region = registry.region;
201
+ } else if (registry.type === "gcr" && credentials?.serviceAccountJson) {
202
+ try {
203
+ const serviceAccount = JSON.parse(credentials.serviceAccountJson);
204
+ metadata.projectId = serviceAccount.project_id;
205
+ } catch (error) {
206
+ console.error("Error parsing service account JSON:", error);
207
+ }
208
+ } else if (credentials?.username) {
209
+ metadata.username = credentials.username;
210
+ }
211
+
212
+ // Ensure active field is included (default to false if not set)
213
+ metadata.active = registry.active || false;
214
+
215
+ result.push(metadata);
216
+ }
217
+
218
+ return result;
219
+ }
220
+
221
+ /**
222
+ * Get registry with credentials (for agent operations)
223
+ */
224
+ async getRegistryWithCredentials(id) {
225
+ await this.initialize();
226
+ return this.registries.get(id);
227
+ }
228
+
229
+ /**
230
+ * Remove a registry
231
+ */
232
+ async removeRegistry(id) {
233
+ await this.initialize();
234
+
235
+ const registryToRemove = this.registries.get(id);
236
+ const deleted = this.registries.delete(id);
237
+
238
+ if (deleted) {
239
+ // If we removed the active registry, set another one as active
240
+ if (
241
+ registryToRemove &&
242
+ registryToRemove.active &&
243
+ this.registries.size > 0
244
+ ) {
245
+ // Set the first remaining registry as active
246
+ const firstRegistry = this.registries.values().next().value;
247
+ if (firstRegistry) {
248
+ firstRegistry.active = true;
249
+ firstRegistry.updatedAt = new Date().toISOString();
250
+ }
251
+ }
252
+
253
+ await this.saveRegistries();
254
+ }
255
+
256
+ return deleted;
257
+ }
258
+
259
+ /**
260
+ * Get registry count
261
+ */
262
+ async getRegistryCount() {
263
+ await this.initialize();
264
+ return this.registries.size;
265
+ }
266
+
267
+ /**
268
+ * Clear all registries (for logout/cleanup)
269
+ */
270
+ async clearAll() {
271
+ await this.initialize();
272
+ this.registries.clear();
273
+ await this.saveRegistries();
274
+ console.log("🧹 All registries cleared");
275
+ }
276
+
277
+ /**
278
+ * Set active registry
279
+ */
280
+ async setActiveRegistry(id) {
281
+ await this.initialize();
282
+
283
+ // First, set all registries to inactive
284
+ for (const [registryId, registry] of this.registries.entries()) {
285
+ registry.active = false;
286
+ }
287
+
288
+ // Then set the specified registry as active
289
+ if (id && this.registries.has(id)) {
290
+ const registry = this.registries.get(id);
291
+ registry.active = true;
292
+ registry.updatedAt = new Date().toISOString();
293
+ await this.saveRegistries();
294
+ return registry;
295
+ }
296
+
297
+ // If no valid id provided, save anyway to clear all active states
298
+ await this.saveRegistries();
299
+ return null;
300
+ }
301
+
302
+ /**
303
+ * Get active registry
304
+ */
305
+ async getActiveRegistry() {
306
+ await this.initialize();
307
+
308
+ for (const [id, registry] of this.registries.entries()) {
309
+ if (registry.active) {
310
+ return registry;
311
+ }
312
+ }
313
+
314
+ return null;
315
+ }
316
+
317
+ /**
318
+ * Get active registry ID
319
+ */
320
+ async getActiveRegistryId() {
321
+ const activeRegistry = await this.getActiveRegistry();
322
+ return activeRegistry ? activeRegistry.id : null;
323
+ }
324
+ }
325
+
326
+ // Singleton instance
327
+ const registryStore = new RegistryStore();
328
+
329
+ export default registryStore;
@@ -0,0 +1,147 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ const FENWAVE_DIR = path.join(os.homedir(), '.fenwave');
6
+ const SETUP_DIR = path.join(FENWAVE_DIR, 'setup');
7
+ const STATE_FILE = path.join(SETUP_DIR, 'state.json');
8
+
9
+ /**
10
+ * Setup steps enum
11
+ */
12
+ const SetupStep = {
13
+ PREREQUISITES: 'prerequisites',
14
+ REGISTRATION: 'registration',
15
+ DOCKER_REGISTRY: 'docker_registry',
16
+ PULL_IMAGE: 'pull_image',
17
+ NPM_CONFIG: 'npm_config',
18
+ START_AGENT: 'start_agent',
19
+ COMPLETE: 'complete',
20
+ };
21
+
22
+ /**
23
+ * Ensure setup directory exists
24
+ */
25
+ function ensureSetupDir() {
26
+ if (!fs.existsSync(FENWAVE_DIR)) {
27
+ fs.mkdirSync(FENWAVE_DIR, { recursive: true, mode: 0o700 });
28
+ }
29
+ if (!fs.existsSync(SETUP_DIR)) {
30
+ fs.mkdirSync(SETUP_DIR, { recursive: true, mode: 0o700 });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Save setup progress
36
+ *
37
+ * @param {string} step - Current step
38
+ * @param {Object} data - Step data
39
+ */
40
+ function saveSetupProgress(step, data = {}) {
41
+ ensureSetupDir();
42
+
43
+ const state = loadSetupProgress() || {
44
+ startedAt: new Date().toISOString(),
45
+ steps: {},
46
+ };
47
+
48
+ state.steps[step] = {
49
+ completedAt: new Date().toISOString(),
50
+ data,
51
+ };
52
+
53
+ state.currentStep = step;
54
+ state.updatedAt = new Date().toISOString();
55
+
56
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), {
57
+ mode: 0o600,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Load setup progress
63
+ *
64
+ * @returns {Object|null} Setup state or null if not found
65
+ */
66
+ function loadSetupProgress() {
67
+ if (!fs.existsSync(STATE_FILE)) {
68
+ return null;
69
+ }
70
+
71
+ try {
72
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
73
+ } catch (error) {
74
+ console.error('Failed to load setup progress:', error.message);
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Mark step as complete
81
+ *
82
+ * @param {string} step - Step to mark as complete
83
+ */
84
+ function markStepComplete(step) {
85
+ saveSetupProgress(step);
86
+ }
87
+
88
+ /**
89
+ * Check if step is complete
90
+ *
91
+ * @param {string} step - Step to check
92
+ * @returns {boolean} True if step is complete
93
+ */
94
+ function isStepComplete(step) {
95
+ const state = loadSetupProgress();
96
+ return state?.steps?.[step] !== undefined;
97
+ }
98
+
99
+ /**
100
+ * Get current step
101
+ *
102
+ * @returns {string|null} Current step or null
103
+ */
104
+ function getCurrentStep() {
105
+ const state = loadSetupProgress();
106
+ return state?.currentStep || null;
107
+ }
108
+
109
+ /**
110
+ * Clear setup state
111
+ */
112
+ function clearSetupState() {
113
+ if (fs.existsSync(STATE_FILE)) {
114
+ fs.unlinkSync(STATE_FILE);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Check if setup is in progress
120
+ *
121
+ * @returns {boolean} True if setup state exists
122
+ */
123
+ function isSetupInProgress() {
124
+ return fs.existsSync(STATE_FILE);
125
+ }
126
+
127
+ /**
128
+ * Get completed steps
129
+ *
130
+ * @returns {Array<string>} List of completed step names
131
+ */
132
+ function getCompletedSteps() {
133
+ const state = loadSetupProgress();
134
+ return state?.steps ? Object.keys(state.steps) : [];
135
+ }
136
+
137
+ export {
138
+ SetupStep,
139
+ saveSetupProgress,
140
+ loadSetupProgress,
141
+ markStepComplete,
142
+ isStepComplete,
143
+ getCurrentStep,
144
+ clearSetupState,
145
+ isSetupInProgress,
146
+ getCompletedSteps,
147
+ };
package/styles.css ADDED
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,223 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import chalk from "chalk";
5
+
6
+ const LOGS_DIR = path.join(os.homedir(), ".fenwave", "logs");
7
+
8
+ /**
9
+ * Ensure logs directory exists
10
+ */
11
+ function ensureLogsDir() {
12
+ if (!fs.existsSync(LOGS_DIR)) {
13
+ fs.mkdirSync(LOGS_DIR, { recursive: true, mode: 0o700 });
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Generate a log filename for an app
19
+ * @param {string} appName - Application name
20
+ * @param {string} version - Application version
21
+ * @returns {string} Log filename
22
+ */
23
+ function generateLogFilename(appName, version) {
24
+ const sanitizedName = appName.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
25
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
26
+ return `${sanitizedName}-${version || "latest"}-${timestamp}.log`;
27
+ }
28
+
29
+ /**
30
+ * Create an app logger instance
31
+ * @param {string} appName - Application name
32
+ * @param {string} version - Application version
33
+ * @returns {Object} Logger instance with methods and logPath
34
+ */
35
+ export function createAppLogger(appName, version) {
36
+ ensureLogsDir();
37
+
38
+ const filename = generateLogFilename(appName, version);
39
+ const logPath = path.join(LOGS_DIR, filename);
40
+
41
+ // Create log file with header
42
+ const header = `
43
+ ================================================================================
44
+ Fenwave App Startup Log
45
+ ================================================================================
46
+ Application: ${appName}
47
+ Version: ${version || "latest"}
48
+ Started: ${new Date().toISOString()}
49
+ ================================================================================
50
+
51
+ `;
52
+
53
+ fs.writeFileSync(logPath, header, { mode: 0o600 });
54
+
55
+ return {
56
+ logPath,
57
+ filename,
58
+
59
+ /**
60
+ * Log an info message
61
+ * @param {string} message - Message to log
62
+ */
63
+ info(message) {
64
+ const timestamp = new Date().toISOString();
65
+ const line = `[${timestamp}] [INFO] ${message}\n`;
66
+ fs.appendFileSync(logPath, line);
67
+ console.log(chalk.blue(`ℹ️ ${message}`));
68
+ },
69
+
70
+ /**
71
+ * Log a success message
72
+ * @param {string} message - Message to log
73
+ */
74
+ success(message) {
75
+ const timestamp = new Date().toISOString();
76
+ const line = `[${timestamp}] [SUCCESS] ${message}\n`;
77
+ fs.appendFileSync(logPath, line);
78
+ console.log(chalk.green(`✅ ${message}`));
79
+ },
80
+
81
+ /**
82
+ * Log an error message
83
+ * @param {string} message - Message to log
84
+ */
85
+ error(message) {
86
+ const timestamp = new Date().toISOString();
87
+ const line = `[${timestamp}] [ERROR] ${message}\n`;
88
+ fs.appendFileSync(logPath, line);
89
+ console.error(chalk.red(`❌ ${message}`));
90
+ },
91
+
92
+ /**
93
+ * Log a warning message
94
+ * @param {string} message - Message to log
95
+ */
96
+ warn(message) {
97
+ const timestamp = new Date().toISOString();
98
+ const line = `[${timestamp}] [WARN] ${message}\n`;
99
+ fs.appendFileSync(logPath, line);
100
+ console.warn(chalk.yellow(`⚠️ ${message}`));
101
+ },
102
+
103
+ /**
104
+ * Log docker output (stdout/stderr)
105
+ * @param {string} output - Docker output
106
+ * @param {string} type - Output type ('stdout' or 'stderr')
107
+ */
108
+ docker(output, type = "stdout") {
109
+ if (!output || !output.trim()) return;
110
+
111
+ const timestamp = new Date().toISOString();
112
+ const prefix = type === "stderr" ? "[DOCKER STDERR]" : "[DOCKER]";
113
+ const lines = output.split("\n").filter(line => line.trim());
114
+
115
+ for (const line of lines) {
116
+ fs.appendFileSync(logPath, `[${timestamp}] ${prefix} ${line}\n`);
117
+ }
118
+ },
119
+
120
+ /**
121
+ * Log completion status
122
+ * @param {boolean} success - Whether the operation succeeded
123
+ * @param {string} message - Completion message
124
+ */
125
+ complete(success, message) {
126
+ const timestamp = new Date().toISOString();
127
+ const status = success ? "COMPLETED SUCCESSFULLY" : "FAILED";
128
+ const footer = `
129
+ ================================================================================
130
+ ${status}
131
+ ${message}
132
+ Finished: ${timestamp}
133
+ ================================================================================
134
+ `;
135
+ fs.appendFileSync(logPath, footer);
136
+ }
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Get recent logs for an app
142
+ * @param {string} appName - Application name (optional, filters by app)
143
+ * @param {number} limit - Number of logs to return
144
+ * @returns {Array} Array of log file info
145
+ */
146
+ export function getRecentLogs(appName = null, limit = 20) {
147
+ ensureLogsDir();
148
+
149
+ try {
150
+ let files = fs.readdirSync(LOGS_DIR)
151
+ .filter(f => f.endsWith(".log"))
152
+ .map(f => {
153
+ const filePath = path.join(LOGS_DIR, f);
154
+ const stats = fs.statSync(filePath);
155
+ return {
156
+ filename: f,
157
+ path: filePath,
158
+ createdAt: stats.birthtime,
159
+ size: stats.size,
160
+ };
161
+ })
162
+ .sort((a, b) => b.createdAt - a.createdAt);
163
+
164
+ if (appName) {
165
+ const sanitizedName = appName.replace(/[^a-z0-9-]/gi, "-").toLowerCase();
166
+ files = files.filter(f => f.filename.startsWith(sanitizedName));
167
+ }
168
+
169
+ return files.slice(0, limit);
170
+ } catch (error) {
171
+ console.error("Error reading logs directory:", error);
172
+ return [];
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Read a log file content
178
+ * @param {string} filename - Log filename
179
+ * @returns {string|null} Log content or null if not found
180
+ */
181
+ export function readLogFile(filename) {
182
+ const logPath = path.join(LOGS_DIR, filename);
183
+
184
+ if (!fs.existsSync(logPath)) {
185
+ return null;
186
+ }
187
+
188
+ try {
189
+ return fs.readFileSync(logPath, "utf8");
190
+ } catch (error) {
191
+ console.error("Error reading log file:", error);
192
+ return null;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Clean up old logs (keep last N days)
198
+ * @param {number} daysToKeep - Number of days to keep logs
199
+ */
200
+ export function cleanupOldLogs(daysToKeep = 7) {
201
+ ensureLogsDir();
202
+
203
+ const cutoffDate = new Date();
204
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
205
+
206
+ try {
207
+ const files = fs.readdirSync(LOGS_DIR);
208
+
209
+ for (const file of files) {
210
+ const filePath = path.join(LOGS_DIR, file);
211
+ const stats = fs.statSync(filePath);
212
+
213
+ if (stats.birthtime < cutoffDate) {
214
+ fs.unlinkSync(filePath);
215
+ console.log(chalk.gray(`🧹 Cleaned up old log: ${file}`));
216
+ }
217
+ }
218
+ } catch (error) {
219
+ console.error("Error cleaning up logs:", error);
220
+ }
221
+ }
222
+
223
+ export { LOGS_DIR };