aris-mac-cleaner 3.0.0 → 3.5.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 (3) hide show
  1. package/README.md +196 -63
  2. package/bin/cli.js +1588 -359
  3. package/package.json +28 -4
package/bin/cli.js CHANGED
@@ -1,54 +1,154 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ╔══════════════════════════════════════════════════════════════════════════════╗
4
- // ARIS MAC CLEANER v3.0 ║
5
- //por Salvador Reis
6
- // ╚══════════════════════════════════════════════════════════════════════════════╝
3
+ // ============================================================================
4
+ // ARIS MAC CLEANER v3.5 PREMIUM
5
+ // por Salvador Reis
6
+ // ============================================================================
7
7
 
8
- const { execSync, spawnSync } = require('child_process');
8
+ const { execSync, spawnSync, spawn } = require('child_process');
9
9
  const os = require('os');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
  const readline = require('readline');
13
13
  const crypto = require('crypto');
14
+ const https = require('https');
15
+
16
+ // Premium dependencies (with fallbacks)
17
+ let chalk, gradient, ora, cliProgress, asciichart, notifier, boxen, figures;
18
+
19
+ try {
20
+ chalk = require('chalk');
21
+ } catch (e) {
22
+ chalk = {
23
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
24
+ green: s => `\x1b[32m${s}\x1b[0m`,
25
+ red: s => `\x1b[31m${s}\x1b[0m`,
26
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
27
+ white: s => `\x1b[37m${s}\x1b[0m`,
28
+ gray: s => `\x1b[90m${s}\x1b[0m`,
29
+ dim: s => `\x1b[2m${s}\x1b[0m`,
30
+ bold: s => `\x1b[1m${s}\x1b[0m`,
31
+ magenta: s => `\x1b[35m${s}\x1b[0m`,
32
+ blue: s => `\x1b[34m${s}\x1b[0m`,
33
+ bgCyan: { black: s => `\x1b[46m\x1b[30m${s}\x1b[0m` },
34
+ bgGreen: { black: s => `\x1b[42m\x1b[30m${s}\x1b[0m` },
35
+ bgRed: { white: s => `\x1b[41m\x1b[37m${s}\x1b[0m` },
36
+ bgYellow: { black: s => `\x1b[43m\x1b[30m${s}\x1b[0m` }
37
+ };
38
+ }
39
+
40
+ try {
41
+ gradient = require('gradient-string');
42
+ } catch (e) {
43
+ gradient = {
44
+ pastel: { multiline: s => s },
45
+ cristal: { multiline: s => s },
46
+ rainbow: { multiline: s => s },
47
+ vice: { multiline: s => s },
48
+ mind: { multiline: s => s }
49
+ };
50
+ }
51
+
52
+ try {
53
+ ora = require('ora');
54
+ } catch (e) {
55
+ ora = (text) => ({
56
+ start: () => ({ succeed: () => {}, fail: () => {}, stop: () => {}, text: '' }),
57
+ succeed: () => {},
58
+ fail: () => {},
59
+ stop: () => {}
60
+ });
61
+ }
62
+
63
+ try {
64
+ cliProgress = require('cli-progress');
65
+ } catch (e) {
66
+ cliProgress = {
67
+ SingleBar: class {
68
+ constructor() {}
69
+ start() {}
70
+ update() {}
71
+ stop() {}
72
+ increment() {}
73
+ },
74
+ Presets: { shades_classic: {} }
75
+ };
76
+ }
77
+
78
+ try {
79
+ asciichart = require('asciichart');
80
+ } catch (e) {
81
+ asciichart = { plot: (data) => data.join(', ') };
82
+ }
83
+
84
+ try {
85
+ notifier = require('node-notifier');
86
+ } catch (e) {
87
+ notifier = { notify: () => {} };
88
+ }
89
+
90
+ try {
91
+ boxen = require('boxen');
92
+ } catch (e) {
93
+ boxen = (text) => text;
94
+ }
95
+
96
+ try {
97
+ figures = require('figures');
98
+ } catch (e) {
99
+ figures = {
100
+ tick: '\u2713',
101
+ cross: '\u2717',
102
+ bullet: '\u2022',
103
+ arrowRight: '\u2192',
104
+ warning: '\u26A0',
105
+ info: '\u2139',
106
+ star: '\u2605',
107
+ heart: '\u2665',
108
+ pointer: '\u25B6',
109
+ line: '\u2500',
110
+ radioOn: '\u25C9',
111
+ radioOff: '\u25EF',
112
+ checkboxOn: '\u2612',
113
+ checkboxOff: '\u2610'
114
+ };
115
+ }
14
116
 
15
- const VERSION = '3.0';
117
+ // ============================================================================
118
+ // CONSTANTS
119
+ // ============================================================================
120
+
121
+ const VERSION = '3.5.0';
122
+ const PACKAGE_NAME = 'aris-mac-cleaner';
16
123
  const HOME = os.homedir();
17
124
  const CONFIG_DIR = path.join(HOME, '.aris-mac-cleaner');
18
125
  const HISTORY_FILE = path.join(CONFIG_DIR, 'history.log');
19
126
  const EXCLUSIONS_FILE = path.join(CONFIG_DIR, 'exclusions.conf');
20
127
  const CUSTOM_PATHS_FILE = path.join(CONFIG_DIR, 'custom-paths.conf');
21
128
  const LANGUAGE_FILE = path.join(CONFIG_DIR, 'language');
129
+ const BACKUP_DIR = path.join(CONFIG_DIR, 'backups');
130
+ const REPORTS_DIR = path.join(CONFIG_DIR, 'reports');
131
+ const SCHEDULE_FILE = path.join(CONFIG_DIR, 'schedule.plist');
132
+ const BENCHMARK_FILE = path.join(CONFIG_DIR, 'benchmark.log');
133
+ const USAGE_FILE = path.join(CONFIG_DIR, 'usage.json');
134
+
135
+ // Ensure directories exist
136
+ [CONFIG_DIR, BACKUP_DIR, REPORTS_DIR].forEach(dir => {
137
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
138
+ });
139
+
140
+ // ============================================================================
141
+ // GLOBAL STATE
142
+ // ============================================================================
143
+
144
+ let DRY_RUN = false;
145
+ let SECURE_DELETE = false;
146
+ let systemStats = {};
147
+ let lastCleanupResults = null;
22
148
 
23
- // Create config directory
24
- if (!fs.existsSync(CONFIG_DIR)) {
25
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
- }
27
-
28
- // ══════════════════════════════════════════════════════════════════════════════
29
- // COLORS AND SYMBOLS
30
- // ══════════════════════════════════════════════════════════════════════════════
31
-
32
- const c = {
33
- reset: '\x1b[0m',
34
- red: '\x1b[0;31m',
35
- green: '\x1b[0;32m',
36
- yellow: '\x1b[1;33m',
37
- blue: '\x1b[0;34m',
38
- cyan: '\x1b[0;36m',
39
- white: '\x1b[1;37m',
40
- dim: '\x1b[2m',
41
- bold: '\x1b[1m'
42
- };
43
-
44
- const CHECK = '✓';
45
- const CROSS = '✗';
46
- const ARROW = '→';
47
- const BULLET = '•';
48
-
49
- // ══════════════════════════════════════════════════════════════════════════════
149
+ // ============================================================================
50
150
  // MULTI-LANGUAGE
51
- // ══════════════════════════════════════════════════════════════════════════════
151
+ // ============================================================================
52
152
 
53
153
  let LANG_CODE = 'pt';
54
154
  if (fs.existsSync(LANGUAGE_FILE)) {
@@ -58,18 +158,24 @@ if (fs.existsSync(LANGUAGE_FILE)) {
58
158
  const MSG = LANG_CODE === 'pt' ? {
59
159
  WELCOME: 'Bem-vindo ao Aris Mac Cleaner',
60
160
  MENU_TITLE: 'MENU PRINCIPAL',
61
- OPT_QUICK: 'Limpeza Rápida (caches e temporários)',
62
- OPT_LARGE: 'Encontrar Ficheiros Grandes (>100MB)',
63
- OPT_DUPLICATES: 'Encontrar Duplicados',
64
- OPT_APPS: 'Desinstalar Aplicações',
65
- OPT_STARTUP: 'Gerir Apps de Arranque',
161
+ OPT_QUICK: 'Limpeza Rapida',
162
+ OPT_LARGE: 'Ficheiros Grandes',
163
+ OPT_DUPLICATES: 'Duplicados',
164
+ OPT_APPS: 'Desinstalar Apps',
165
+ OPT_STARTUP: 'Apps de Arranque',
66
166
  OPT_BROWSERS: 'Limpar Browsers',
67
- OPT_PROCESSES: 'Matar Processos Pesados',
68
- OPT_STATS: 'Ver Estatísticas',
69
- OPT_SETTINGS: 'Configurações',
167
+ OPT_PROCESSES: 'Processos Pesados',
168
+ OPT_STATS: 'Estatisticas',
169
+ OPT_SETTINGS: 'Configuracoes',
170
+ OPT_DISK: 'Analisador Disco',
171
+ OPT_PRIVACY: 'Limpeza Privacidade',
172
+ OPT_MEMORY: 'Otimizar Memoria',
173
+ OPT_BENCHMARK: 'Benchmark',
174
+ OPT_SCHEDULER: 'Agendar Limpeza',
175
+ OPT_EXPORT: 'Exportar Relatorio',
70
176
  OPT_EXIT: 'Sair',
71
177
  CHOOSE: 'Escolha',
72
- INVALID: 'Opção inválida',
178
+ INVALID: 'Opcao invalida',
73
179
  PRESS_ENTER: 'Pressiona ENTER para continuar',
74
180
  SCANNING: 'A analisar...',
75
181
  FOUND: 'Encontrado',
@@ -79,13 +185,13 @@ const MSG = LANG_CODE === 'pt' ? {
79
185
  CANCEL: 'Cancelar',
80
186
  CONFIRM: 'Confirmar',
81
187
  FREED: 'Libertado',
82
- AVAILABLE: 'Disponível',
188
+ AVAILABLE: 'Disponivel',
83
189
  HEALTH_SCORE: 'Health Score',
84
190
  EXCELLENT: 'Excelente',
85
191
  GOOD: 'Bom',
86
- ATTENTION: 'Atenção',
87
- CRITICAL: 'Crítico',
88
- DIAGNOSE: 'DIAGNÓSTICO DO SISTEMA',
192
+ ATTENTION: 'Atencao',
193
+ CRITICAL: 'Critico',
194
+ DIAGNOSE: 'DIAGNOSTICO DO SISTEMA',
89
195
  DISK: 'Disco',
90
196
  RAM: 'RAM',
91
197
  SWAP: 'Swap',
@@ -97,9 +203,9 @@ const MSG = LANG_CODE === 'pt' ? {
97
203
  ALL: 'todos',
98
204
  NONE: 'nenhum',
99
205
  BACK: 'Voltar',
100
- FINAL_REPORT: 'RELATÓRIO FINAL',
206
+ FINAL_REPORT: 'RELATORIO FINAL',
101
207
  READY: 'Pronto para trabalhar!',
102
- RESTART_REC: 'Recomendação: Reinicia o Mac para limpar memória',
208
+ RESTART_REC: 'Recomendacao: Reinicia o Mac para limpar memoria',
103
209
  NO_ITEMS: 'Nada encontrado',
104
210
  CLEANING: 'A limpar...',
105
211
  CATEGORIES: 'CATEGORIAS',
@@ -107,8 +213,8 @@ const MSG = LANG_CODE === 'pt' ? {
107
213
  DEV_TOOLS: 'Ferramentas de Dev',
108
214
  BROWSERS_CAT: 'Browsers',
109
215
  DOWNLOADS_CAT: 'Downloads',
110
- APPS_CAT: 'Aplicações',
111
- PROCESS_MEM: 'Por Memória',
216
+ APPS_CAT: 'Aplicacoes',
217
+ PROCESS_MEM: 'Por Memoria',
112
218
  PROCESS_CPU: 'Por CPU',
113
219
  KILL: 'Matar',
114
220
  STARTUP_ITEMS: 'Items de Arranque',
@@ -117,52 +223,74 @@ const MSG = LANG_CODE === 'pt' ? {
117
223
  DISABLED: 'Desativado',
118
224
  TOGGLE: 'Alternar',
119
225
  DISABLE_ALL: 'Desativar todos',
120
- THIS_MONTH: 'Este mês',
226
+ THIS_MONTH: 'Este mes',
121
227
  CLEANINGS: 'Limpezas',
122
- AVG_SCORE: 'Score médio',
123
- HISTORY: 'Histórico',
124
- EXCLUSIONS: 'Gerir Exclusões',
228
+ AVG_SCORE: 'Score medio',
229
+ HISTORY: 'Historico',
230
+ EXCLUSIONS: 'Gerir Exclusoes',
125
231
  CUSTOM_PATHS: 'Caminhos Personalizados',
126
232
  CHANGE_LANG: 'Mudar Idioma',
127
- RESET_DEFAULTS: 'Repor Predefinições',
128
- PREVIEW_MODE: 'Modo Preview (não apaga nada)',
233
+ RESET_DEFAULTS: 'Repor Predefinicoes',
234
+ PREVIEW_MODE: 'Modo Preview (nao apaga nada)',
129
235
  LARGE_FILES: 'FICHEIROS GRANDES',
130
236
  DUPLICATES: 'FICHEIROS DUPLICADOS',
131
- UNINSTALL: 'DESINSTALAR APLICAÇÕES',
237
+ UNINSTALL: 'DESINSTALAR APLICACOES',
132
238
  APP_SIZE: 'app',
133
239
  DATA_SIZE: 'dados',
134
- CURRENT_EXCLUSIONS: 'Exclusões atuais',
240
+ CURRENT_EXCLUSIONS: 'Exclusoes atuais',
135
241
  CURRENT_PATHS: 'Caminhos personalizados atuais',
136
242
  ENTER_PATH: 'Introduz caminho (c=limpar, n=voltar)',
137
243
  PATH: 'Caminho',
138
244
  CLEARED: 'Limpo',
139
245
  ADDED: 'Adicionado',
140
246
  LANG_CHANGED_EN: 'Language changed to English',
141
- LANG_CHANGED_PT: 'Idioma alterado para Português',
142
- SETTINGS_RESET: 'Configurações repostas',
247
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
248
+ SETTINGS_RESET: 'Configuracoes repostas',
143
249
  REMOVED: 'Removido',
144
250
  NEED_SUDO: 'Precisa sudo para remover app. Executa:',
145
251
  DELETING: 'A apagar',
146
252
  KILLED: 'Morto',
147
253
  FAILED_KILL: 'Falhou ao matar',
148
- NO_HISTORY: 'Sem histórico. Faz algumas limpezas primeiro!',
254
+ NO_HISTORY: 'Sem historico. Faz algumas limpezas primeiro!',
149
255
  FEATURE_PROGRESS: 'Funcionalidade em progresso...',
150
256
  GROUP: 'Grupo',
151
257
  EACH: 'cada',
152
258
  IN_DUPLICATES: 'em duplicados',
153
- HASHING: 'A calcular hashes...'
259
+ HASHING: 'A calcular hashes...',
260
+ DRY_RUN_MODE: 'MODO DRY-RUN (nada sera apagado)',
261
+ SECURE_DELETE_MODE: 'MODO SEGURO (overwrite antes de apagar)',
262
+ BACKUP_CREATED: 'Backup criado',
263
+ SCHEDULE_CREATED: 'Agendamento criado',
264
+ SCHEDULE_REMOVED: 'Agendamento removido',
265
+ REPORT_EXPORTED: 'Relatorio exportado',
266
+ UPDATE_AVAILABLE: 'Atualizacao disponivel',
267
+ UP_TO_DATE: 'Ja tens a versao mais recente',
268
+ MEMORY_FREED: 'Memoria libertada',
269
+ PRIVACY_CLEANED: 'Privacidade limpa',
270
+ BENCHMARK_COMPLETE: 'Benchmark completo',
271
+ RECOMMENDATIONS: 'RECOMENDACOES',
272
+ DISK_ANALYSIS: 'ANALISE DE DISCO',
273
+ SCHEDULED_DAILY: 'Agendado diariamente',
274
+ SCHEDULED_WEEKLY: 'Agendado semanalmente',
275
+ CLEANUP_COMPLETE: 'Limpeza completa!'
154
276
  } : {
155
277
  WELCOME: 'Welcome to Aris Mac Cleaner',
156
278
  MENU_TITLE: 'MAIN MENU',
157
- OPT_QUICK: 'Quick Clean (caches and temp files)',
158
- OPT_LARGE: 'Find Large Files (>100MB)',
159
- OPT_DUPLICATES: 'Find Duplicates',
160
- OPT_APPS: 'Uninstall Applications',
161
- OPT_STARTUP: 'Manage Startup Apps',
279
+ OPT_QUICK: 'Quick Clean',
280
+ OPT_LARGE: 'Large Files',
281
+ OPT_DUPLICATES: 'Duplicates',
282
+ OPT_APPS: 'Uninstall Apps',
283
+ OPT_STARTUP: 'Startup Apps',
162
284
  OPT_BROWSERS: 'Clean Browsers',
163
- OPT_PROCESSES: 'Kill Heavy Processes',
164
- OPT_STATS: 'View Statistics',
285
+ OPT_PROCESSES: 'Heavy Processes',
286
+ OPT_STATS: 'Statistics',
165
287
  OPT_SETTINGS: 'Settings',
288
+ OPT_DISK: 'Disk Analyzer',
289
+ OPT_PRIVACY: 'Privacy Sweep',
290
+ OPT_MEMORY: 'Memory Optimizer',
291
+ OPT_BENCHMARK: 'Benchmark',
292
+ OPT_SCHEDULER: 'Schedule Cleanup',
293
+ OPT_EXPORT: 'Export Report',
166
294
  OPT_EXIT: 'Exit',
167
295
  CHOOSE: 'Choose',
168
296
  INVALID: 'Invalid option',
@@ -234,7 +362,7 @@ const MSG = LANG_CODE === 'pt' ? {
234
362
  CLEARED: 'Cleared',
235
363
  ADDED: 'Added',
236
364
  LANG_CHANGED_EN: 'Language changed to English',
237
- LANG_CHANGED_PT: 'Idioma alterado para Português',
365
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
238
366
  SETTINGS_RESET: 'Settings reset to defaults',
239
367
  REMOVED: 'Removed',
240
368
  NEED_SUDO: 'Need sudo to remove app. Run:',
@@ -246,12 +374,82 @@ const MSG = LANG_CODE === 'pt' ? {
246
374
  GROUP: 'Group',
247
375
  EACH: 'each',
248
376
  IN_DUPLICATES: 'in duplicates',
249
- HASHING: 'Calculating hashes...'
377
+ HASHING: 'Calculating hashes...',
378
+ DRY_RUN_MODE: 'DRY-RUN MODE (nothing will be deleted)',
379
+ SECURE_DELETE_MODE: 'SECURE MODE (overwrite before delete)',
380
+ BACKUP_CREATED: 'Backup created',
381
+ SCHEDULE_CREATED: 'Schedule created',
382
+ SCHEDULE_REMOVED: 'Schedule removed',
383
+ REPORT_EXPORTED: 'Report exported',
384
+ UPDATE_AVAILABLE: 'Update available',
385
+ UP_TO_DATE: 'You have the latest version',
386
+ MEMORY_FREED: 'Memory freed',
387
+ PRIVACY_CLEANED: 'Privacy cleaned',
388
+ BENCHMARK_COMPLETE: 'Benchmark complete',
389
+ RECOMMENDATIONS: 'RECOMMENDATIONS',
390
+ DISK_ANALYSIS: 'DISK ANALYSIS',
391
+ SCHEDULED_DAILY: 'Scheduled daily',
392
+ SCHEDULED_WEEKLY: 'Scheduled weekly',
393
+ CLEANUP_COMPLETE: 'Cleanup complete!'
250
394
  };
251
395
 
252
- // ══════════════════════════════════════════════════════════════════════════════
396
+ // ============================================================================
397
+ // ANIMATED ASCII LOGO
398
+ // ============================================================================
399
+
400
+ const ASCII_LOGO = `
401
+ _ ____ ___ ____
402
+ / \\ | _ \\|_ _/ ___|
403
+ / _ \\ | |_) || |\\___ \\
404
+ / ___ \\| _ < | | ___) |
405
+ /_/ \\_\\_| \\_\\___|____/
406
+
407
+ MAC CLEANER v${VERSION} PREMIUM
408
+ `;
409
+
410
+ const ASCII_LOGO_FRAMES = [
411
+ `
412
+ _
413
+ / \\
414
+ / _ \\
415
+ / ___ \\
416
+ /_/ \\_\\
417
+ `,
418
+ `
419
+ _ ____
420
+ / \\ | _ \\
421
+ / _ \\ | |_) |
422
+ / ___ \\| _ <
423
+ /_/ \\_\\_| \\_\\
424
+ `,
425
+ `
426
+ _ ____ ___ ____
427
+ / \\ | _ \\|_ _/ ___|
428
+ / _ \\ | |_) || |\\___ \\
429
+ / ___ \\| _ < | | ___) |
430
+ /_/ \\_\\_| \\_\\___|____/
431
+ `,
432
+ ASCII_LOGO
433
+ ];
434
+
435
+ async function animateLogo() {
436
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
437
+
438
+ for (const frame of ASCII_LOGO_FRAMES) {
439
+ process.stdout.write('\x1b[2J\x1b[H');
440
+ console.log(gradient.cristal.multiline(frame));
441
+ await sleep(150);
442
+ }
443
+
444
+ // Final sparkle effect
445
+ process.stdout.write('\x1b[2J\x1b[H');
446
+ console.log(gradient.pastel.multiline(ASCII_LOGO));
447
+ await sleep(300);
448
+ }
449
+
450
+ // ============================================================================
253
451
  // UTILITY FUNCTIONS
254
- // ══════════════════════════════════════════════════════════════════════════════
452
+ // ============================================================================
255
453
 
256
454
  function exec(cmd) {
257
455
  try {
@@ -294,6 +492,28 @@ function clearScreen() {
294
492
  process.stdout.write('\x1b[2J\x1b[H');
295
493
  }
296
494
 
495
+ function prompt(question) {
496
+ return new Promise((resolve) => {
497
+ const rl = readline.createInterface({
498
+ input: process.stdin,
499
+ output: process.stdout
500
+ });
501
+ rl.question(question, (answer) => {
502
+ rl.close();
503
+ resolve(answer.trim());
504
+ });
505
+ });
506
+ }
507
+
508
+ async function pressEnter() {
509
+ console.log();
510
+ await prompt(` ${chalk.dim(MSG.PRESS_ENTER + '...')} `);
511
+ }
512
+
513
+ // ============================================================================
514
+ // PREMIUM HEADER WITH GRADIENT
515
+ // ============================================================================
516
+
297
517
  function header() {
298
518
  clearScreen();
299
519
  const date = new Date().toLocaleDateString('en-GB', {
@@ -301,44 +521,189 @@ function header() {
301
521
  });
302
522
  const time = new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
303
523
 
304
- console.log();
305
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
306
- console.log(`${c.cyan}║${c.reset}${c.white} ARIS MAC CLEANER v${VERSION} ${c.reset}${c.cyan}║${c.reset}`);
307
- console.log(`${c.cyan}║${c.reset}${c.dim} ${date} ${BULLET} ${time} ${c.reset}${c.cyan}║${c.reset}`);
308
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
309
- console.log();
524
+ const titleBox = `
525
+ ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
526
+ ${chalk.cyan('|')}${chalk.white(' ARIS MAC CLEANER v' + VERSION + ' PREMIUM ')}${chalk.cyan('|')}
527
+ ${chalk.cyan('|')}${chalk.gray(' ' + date + ' ' + figures.bullet + ' ' + time + ' ')}${chalk.cyan('|')}
528
+ ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
529
+ `;
530
+
531
+ console.log(gradient.pastel.multiline(titleBox));
532
+
533
+ // Show mode indicators
534
+ if (DRY_RUN) {
535
+ console.log(chalk.bgYellow.black(` ${figures.warning} ${MSG.DRY_RUN_MODE} `));
536
+ console.log();
537
+ }
538
+ if (SECURE_DELETE) {
539
+ console.log(chalk.bgRed.white(` ${figures.warning} ${MSG.SECURE_DELETE_MODE} `));
540
+ console.log();
541
+ }
310
542
  }
311
543
 
312
544
  function sectionHeader(title) {
313
- console.log(`${c.white}┌─────────────────────────────────────────────────────────────────┐${c.reset}`);
314
- console.log(`${c.white}│${c.reset} ${c.blue}▶ ${title}${c.reset}`);
315
- console.log(`${c.white}└─────────────────────────────────────────────────────────────────┘${c.reset}`);
545
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
546
+ console.log(chalk.white('|') + ' ' + chalk.cyan(figures.pointer + ' ' + title));
547
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
316
548
  console.log();
317
549
  }
318
550
 
319
- function prompt(question) {
551
+ // ============================================================================
552
+ // AUTO-UPDATE CHECKER
553
+ // ============================================================================
554
+
555
+ async function checkForUpdates() {
320
556
  return new Promise((resolve) => {
321
- const rl = readline.createInterface({
322
- input: process.stdin,
323
- output: process.stdout
557
+ const req = https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5000 }, (res) => {
558
+ let data = '';
559
+ res.on('data', chunk => data += chunk);
560
+ res.on('end', () => {
561
+ try {
562
+ const json = JSON.parse(data);
563
+ const latestVersion = json.version;
564
+ if (latestVersion && latestVersion !== VERSION) {
565
+ resolve({ hasUpdate: true, version: latestVersion });
566
+ } else {
567
+ resolve({ hasUpdate: false });
568
+ }
569
+ } catch (e) {
570
+ resolve({ hasUpdate: false });
571
+ }
572
+ });
324
573
  });
325
- rl.question(question, (answer) => {
326
- rl.close();
327
- resolve(answer.trim());
574
+
575
+ req.on('error', () => resolve({ hasUpdate: false }));
576
+ req.on('timeout', () => {
577
+ req.destroy();
578
+ resolve({ hasUpdate: false });
328
579
  });
329
580
  });
330
581
  }
331
582
 
332
- async function pressEnter() {
333
- console.log();
334
- await prompt(` ${c.dim}${MSG.PRESS_ENTER}...${c.reset} `);
583
+ async function showUpdateNotification() {
584
+ const update = await checkForUpdates();
585
+ if (update.hasUpdate) {
586
+ console.log();
587
+ console.log(chalk.bgGreen.black(` ${figures.star} ${MSG.UPDATE_AVAILABLE}: v${update.version} `));
588
+ console.log(chalk.dim(` npm update -g ${PACKAGE_NAME}`));
589
+ console.log();
590
+ }
335
591
  }
336
592
 
337
- // ══════════════════════════════════════════════════════════════════════════════
338
- // SYSTEM DIAGNOSTICS
339
- // ══════════════════════════════════════════════════════════════════════════════
593
+ // ============================================================================
594
+ // BACKUP SYSTEM
595
+ // ============================================================================
596
+
597
+ function createBackup(items) {
598
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
599
+ const backupFile = path.join(BACKUP_DIR, `backup-${timestamp}.json`);
600
+
601
+ const backupData = {
602
+ timestamp: new Date().toISOString(),
603
+ version: VERSION,
604
+ items: items.map(item => ({
605
+ path: item.path,
606
+ name: item.name,
607
+ size: item.size,
608
+ category: item.category
609
+ }))
610
+ };
611
+
612
+ fs.writeFileSync(backupFile, JSON.stringify(backupData, null, 2));
613
+ return backupFile;
614
+ }
340
615
 
341
- let systemStats = {};
616
+ function listBackups() {
617
+ if (!fs.existsSync(BACKUP_DIR)) return [];
618
+ return fs.readdirSync(BACKUP_DIR)
619
+ .filter(f => f.endsWith('.json'))
620
+ .sort()
621
+ .reverse()
622
+ .slice(0, 10);
623
+ }
624
+
625
+ // ============================================================================
626
+ // SECURE DELETE
627
+ // ============================================================================
628
+
629
+ function secureDelete(filePath) {
630
+ if (!fs.existsSync(filePath)) return;
631
+
632
+ const stats = fs.statSync(filePath);
633
+
634
+ if (stats.isDirectory()) {
635
+ // For directories, just use rm -rf (secure delete of dirs is complex)
636
+ fs.rmSync(filePath, { recursive: true, force: true });
637
+ return;
638
+ }
639
+
640
+ // Overwrite file content 3 times before deletion
641
+ const size = stats.size;
642
+ const fd = fs.openSync(filePath, 'w');
643
+
644
+ // Pass 1: zeros
645
+ const zeros = Buffer.alloc(Math.min(size, 1024 * 1024), 0);
646
+ let written = 0;
647
+ while (written < size) {
648
+ fs.writeSync(fd, zeros, 0, Math.min(zeros.length, size - written));
649
+ written += zeros.length;
650
+ }
651
+
652
+ // Pass 2: ones
653
+ fs.lseekSync ? fs.lseekSync(fd, 0, 0) : null;
654
+ const ones = Buffer.alloc(Math.min(size, 1024 * 1024), 0xFF);
655
+ written = 0;
656
+ while (written < size) {
657
+ fs.writeSync(fd, ones, 0, Math.min(ones.length, size - written));
658
+ written += ones.length;
659
+ }
660
+
661
+ // Pass 3: random
662
+ const random = crypto.randomBytes(Math.min(size, 1024 * 1024));
663
+ written = 0;
664
+ while (written < size) {
665
+ fs.writeSync(fd, random, 0, Math.min(random.length, size - written));
666
+ written += random.length;
667
+ }
668
+
669
+ fs.closeSync(fd);
670
+ fs.unlinkSync(filePath);
671
+ }
672
+
673
+ function deleteItem(itemPath) {
674
+ if (DRY_RUN) {
675
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would delete: ${itemPath}`));
676
+ return true;
677
+ }
678
+
679
+ try {
680
+ if (SECURE_DELETE) {
681
+ secureDelete(itemPath);
682
+ } else {
683
+ fs.rmSync(itemPath, { recursive: true, force: true });
684
+ }
685
+ return true;
686
+ } catch (e) {
687
+ return false;
688
+ }
689
+ }
690
+
691
+ // ============================================================================
692
+ // PROGRESS BAR
693
+ // ============================================================================
694
+
695
+ function createProgressBar(total, format) {
696
+ return new cliProgress.SingleBar({
697
+ format: format || ' {bar} | {percentage}% | {value}/{total}',
698
+ barCompleteChar: '\u2588',
699
+ barIncompleteChar: '\u2591',
700
+ hideCursor: true
701
+ }, cliProgress.Presets.shades_classic);
702
+ }
703
+
704
+ // ============================================================================
705
+ // SYSTEM DIAGNOSTICS
706
+ // ============================================================================
342
707
 
343
708
  function getSystemStats() {
344
709
  // Disk
@@ -350,11 +715,11 @@ function getSystemStats() {
350
715
  systemStats.diskFreeBlocks = parseInt(diskRaw[3]) || 0;
351
716
 
352
717
  if (systemStats.diskPercent < 50) {
353
- systemStats.diskStatus = `${c.green}${CHECK} ${MSG.EXCELLENT}${c.reset}`;
718
+ systemStats.diskStatus = chalk.green(`${figures.tick} ${MSG.EXCELLENT}`);
354
719
  } else if (systemStats.diskPercent < 80) {
355
- systemStats.diskStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
720
+ systemStats.diskStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
356
721
  } else {
357
- systemStats.diskStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
722
+ systemStats.diskStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
358
723
  }
359
724
 
360
725
  // RAM
@@ -362,11 +727,11 @@ function getSystemStats() {
362
727
  systemStats.ramUsedPercent = 100 - ramFreePercent;
363
728
 
364
729
  if (systemStats.ramUsedPercent < 70) {
365
- systemStats.ramStatus = `${c.green}${CHECK} OK${c.reset}`;
730
+ systemStats.ramStatus = chalk.green(`${figures.tick} OK`);
366
731
  } else if (systemStats.ramUsedPercent < 90) {
367
- systemStats.ramStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
732
+ systemStats.ramStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
368
733
  } else {
369
- systemStats.ramStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
734
+ systemStats.ramStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
370
735
  }
371
736
 
372
737
  // Swap
@@ -374,11 +739,11 @@ function getSystemStats() {
374
739
  systemStats.swapUsed = parseInt(swapStr) || 0;
375
740
 
376
741
  if (systemStats.swapUsed < 2000) {
377
- systemStats.swapStatus = `${c.green}${CHECK} OK${c.reset}`;
742
+ systemStats.swapStatus = chalk.green(`${figures.tick} OK`);
378
743
  } else if (systemStats.swapUsed < 8000) {
379
- systemStats.swapStatus = `${c.yellow}! Alto${c.reset}`;
744
+ systemStats.swapStatus = chalk.yellow(`${figures.warning} High`);
380
745
  } else {
381
- systemStats.swapStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
746
+ systemStats.swapStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
382
747
  }
383
748
 
384
749
  // CPU
@@ -386,20 +751,20 @@ function getSystemStats() {
386
751
  systemStats.cpuUsed = Math.round(100 - cpuIdle);
387
752
 
388
753
  if (systemStats.cpuUsed < 50) {
389
- systemStats.cpuStatus = `${c.green}${CHECK} OK${c.reset}`;
754
+ systemStats.cpuStatus = chalk.green(`${figures.tick} OK`);
390
755
  } else if (systemStats.cpuUsed < 80) {
391
- systemStats.cpuStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
756
+ systemStats.cpuStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
392
757
  } else {
393
- systemStats.cpuStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
758
+ systemStats.cpuStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
394
759
  }
395
760
  }
396
761
 
397
762
  function showDiagnostics() {
398
763
  sectionHeader(MSG.DIAGNOSE);
399
- console.log(` ${BULLET} ${c.white}${MSG.DISK}${c.reset} ${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB ${systemStats.diskStatus}`);
400
- console.log(` ${BULLET} ${c.white}${MSG.RAM}${c.reset} ${systemStats.ramUsedPercent}% ${MSG.IN_USE} ${systemStats.ramStatus}`);
401
- console.log(` ${BULLET} ${c.white}${MSG.SWAP}${c.reset} ${systemStats.swapUsed}MB ${MSG.IN_USE} ${systemStats.swapStatus}`);
402
- console.log(` ${BULLET} ${c.white}${MSG.CPU}${c.reset} ${systemStats.cpuUsed}% ${MSG.IN_USE} ${systemStats.cpuStatus}`);
764
+ console.log(` ${figures.bullet} ${chalk.white(MSG.DISK)} ${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB ${systemStats.diskStatus}`);
765
+ console.log(` ${figures.bullet} ${chalk.white(MSG.RAM)} ${systemStats.ramUsedPercent}% ${MSG.IN_USE} ${systemStats.ramStatus}`);
766
+ console.log(` ${figures.bullet} ${chalk.white(MSG.SWAP)} ${systemStats.swapUsed}MB ${MSG.IN_USE} ${systemStats.swapStatus}`);
767
+ console.log(` ${figures.bullet} ${chalk.white(MSG.CPU)} ${systemStats.cpuUsed}% ${MSG.IN_USE} ${systemStats.cpuStatus}`);
403
768
  console.log();
404
769
  }
405
770
 
@@ -412,52 +777,834 @@ function calculateHealthScore() {
412
777
  if (systemStats.swapUsed > 8000) score -= 20;
413
778
  else if (systemStats.swapUsed > 4000) score -= 10;
414
779
 
415
- let color = c.green;
780
+ let color = chalk.green;
416
781
  let text = MSG.EXCELLENT;
417
- if (score < 60) { color = c.red; text = MSG.ATTENTION; }
418
- else if (score < 80) { color = c.yellow; text = MSG.GOOD; }
782
+ if (score < 60) { color = chalk.red; text = MSG.ATTENTION; }
783
+ else if (score < 80) { color = chalk.yellow; text = MSG.GOOD; }
419
784
 
420
785
  return { score, color, text };
421
786
  }
422
787
 
788
+ // ============================================================================
789
+ // SOUND NOTIFICATION
790
+ // ============================================================================
791
+
792
+ function playNotificationSound() {
793
+ // Use macOS native sound
794
+ exec('afplay /System/Library/Sounds/Glass.aiff &');
795
+
796
+ // Also show system notification
797
+ notifier.notify({
798
+ title: 'Aris Mac Cleaner',
799
+ message: MSG.CLEANUP_COMPLETE,
800
+ sound: true,
801
+ icon: path.join(__dirname, 'icon.png')
802
+ });
803
+ }
804
+
805
+ // ============================================================================
806
+ // SMART RECOMMENDATIONS
807
+ // ============================================================================
808
+
809
+ function getSmartRecommendations() {
810
+ const recommendations = [];
811
+
812
+ // Check disk usage
813
+ if (systemStats.diskPercent > 80) {
814
+ recommendations.push({
815
+ priority: 'high',
816
+ icon: figures.warning,
817
+ text: LANG_CODE === 'pt'
818
+ ? 'Disco quase cheio! Considera apagar ficheiros grandes ou desinstalar apps.'
819
+ : 'Disk almost full! Consider deleting large files or uninstalling apps.'
820
+ });
821
+ }
822
+
823
+ // Check RAM
824
+ if (systemStats.ramUsedPercent > 85) {
825
+ recommendations.push({
826
+ priority: 'high',
827
+ icon: figures.warning,
828
+ text: LANG_CODE === 'pt'
829
+ ? 'Memoria RAM muito usada. Usa o otimizador de memoria ou mata processos pesados.'
830
+ : 'RAM heavily used. Use memory optimizer or kill heavy processes.'
831
+ });
832
+ }
833
+
834
+ // Check swap
835
+ if (systemStats.swapUsed > 5000) {
836
+ recommendations.push({
837
+ priority: 'medium',
838
+ icon: figures.info,
839
+ text: LANG_CODE === 'pt'
840
+ ? 'Swap elevado. Considera reiniciar o Mac para limpar.'
841
+ : 'High swap usage. Consider restarting Mac to clear.'
842
+ });
843
+ }
844
+
845
+ // Check history for patterns
846
+ if (fs.existsSync(HISTORY_FILE)) {
847
+ const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
848
+ const lastWeek = history.filter(l => {
849
+ const date = new Date(l.split(' | ')[0]);
850
+ return (Date.now() - date.getTime()) < 7 * 24 * 60 * 60 * 1000;
851
+ });
852
+
853
+ if (lastWeek.length === 0) {
854
+ recommendations.push({
855
+ priority: 'low',
856
+ icon: figures.star,
857
+ text: LANG_CODE === 'pt'
858
+ ? 'Nao fizeste limpeza esta semana. Considera fazer uma limpeza rapida.'
859
+ : 'No cleanup this week. Consider running a quick clean.'
860
+ });
861
+ }
862
+ }
863
+
864
+ // Check for large caches
865
+ const chromeCache = getSize('~/Library/Caches/Google');
866
+ if (chromeCache > 500) {
867
+ recommendations.push({
868
+ priority: 'medium',
869
+ icon: figures.bullet,
870
+ text: LANG_CODE === 'pt'
871
+ ? `Cache do Chrome com ${formatSize(chromeCache)}. Limpa os browsers.`
872
+ : `Chrome cache at ${formatSize(chromeCache)}. Clean browsers.`
873
+ });
874
+ }
875
+
876
+ // Check Xcode
877
+ const xcodeData = getSize('~/Library/Developer/Xcode/DerivedData');
878
+ if (xcodeData > 1000) {
879
+ recommendations.push({
880
+ priority: 'medium',
881
+ icon: figures.bullet,
882
+ text: LANG_CODE === 'pt'
883
+ ? `Xcode DerivedData com ${formatSize(xcodeData)}. Pode ser limpo.`
884
+ : `Xcode DerivedData at ${formatSize(xcodeData)}. Can be cleaned.`
885
+ });
886
+ }
887
+
888
+ return recommendations;
889
+ }
890
+
891
+ function showRecommendations() {
892
+ const recs = getSmartRecommendations();
893
+
894
+ if (recs.length === 0) return;
895
+
896
+ sectionHeader(MSG.RECOMMENDATIONS);
897
+
898
+ recs.forEach(rec => {
899
+ const color = rec.priority === 'high' ? chalk.red :
900
+ rec.priority === 'medium' ? chalk.yellow : chalk.cyan;
901
+ console.log(` ${color(rec.icon)} ${rec.text}`);
902
+ });
903
+ console.log();
904
+ }
905
+
906
+ // ============================================================================
907
+ // FINAL REPORT
908
+ // ============================================================================
909
+
423
910
  function showFinalReport(freed) {
424
911
  const diskFreeAfter = exec("df -h / | tail -1").split(/\s+/)[3];
425
912
  const health = calculateHealthScore();
426
913
 
427
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
428
- console.log(`${c.cyan}║${c.reset}${c.white} ${MSG.FINAL_REPORT} ${c.reset}${c.cyan}║${c.reset}`);
429
- console.log(`${c.cyan}╠══════════════════════════════════════════════════════════════════╣${c.reset}`);
430
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
431
- console.log(`${c.cyan}║${c.reset} ${c.white}${MSG.DISK}${c.reset} ${c.cyan}║${c.reset}`);
432
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${MSG.AVAILABLE}: ${c.green}${diskFreeAfter}${c.reset} ${c.cyan}║${c.reset}`);
433
- if (freed > 0) {
434
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${MSG.FREED}: ${c.green}+${formatSize(freed)}${c.reset} ${c.cyan}║${c.reset}`);
435
- }
436
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
437
- console.log(`${c.cyan}║${c.reset} ${c.white}${MSG.HEALTH_SCORE}${c.reset} ${c.cyan}║${c.reset}`);
438
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${health.color}${health.score}/100 - ${health.text}${c.reset} ${c.cyan}║${c.reset}`);
439
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
440
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
441
- console.log();
914
+ const reportBox = `
915
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
916
+ ${chalk.cyan('|')}${chalk.white(' ' + MSG.FINAL_REPORT + ' ')}${chalk.cyan('|')}
917
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
918
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
919
+ ${chalk.cyan('|')} ${chalk.white(MSG.DISK)} ${chalk.cyan('|')}
920
+ ${chalk.cyan('|')} ${figures.arrowRight} ${MSG.AVAILABLE}: ${chalk.green(diskFreeAfter)} ${chalk.cyan('|')}
921
+ ${freed > 0 ? `${chalk.cyan('|')} ${figures.arrowRight} ${MSG.FREED}: ${chalk.green('+' + formatSize(freed))} ${chalk.cyan('|')}` : ''}
922
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
923
+ ${chalk.cyan('|')} ${chalk.white(MSG.HEALTH_SCORE)} ${chalk.cyan('|')}
924
+ ${chalk.cyan('|')} ${figures.arrowRight} ${health.color(health.score + '/100 - ' + health.text)} ${chalk.cyan('|')}
925
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
926
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
927
+ `;
928
+
929
+ console.log(gradient.pastel.multiline(reportBox));
442
930
 
443
931
  if (systemStats.swapUsed > 5000 || systemStats.ramUsedPercent > 85) {
444
- console.log(`${c.yellow} ${MSG.RESTART_REC}${c.reset}`);
932
+ console.log(chalk.yellow(`${figures.warning} ${MSG.RESTART_REC}`));
445
933
  console.log();
446
934
  }
447
935
 
448
- console.log(`${c.green}${CHECK} ${MSG.READY}${c.reset}`);
936
+ console.log(chalk.green(`${figures.tick} ${MSG.READY}`));
937
+
938
+ // Store results for export
939
+ lastCleanupResults = {
940
+ timestamp: new Date().toISOString(),
941
+ freed: freed,
942
+ diskFree: diskFreeAfter,
943
+ healthScore: health.score,
944
+ healthText: health.text
945
+ };
449
946
 
450
947
  logCleanup(freed, health.score);
948
+
949
+ // Play notification sound
950
+ playNotificationSound();
951
+ }
952
+
953
+ // ============================================================================
954
+ // DISK ANALYZER
955
+ // ============================================================================
956
+
957
+ async function diskAnalyzer() {
958
+ header();
959
+ sectionHeader(MSG.DISK_ANALYSIS);
960
+
961
+ const spinner = ora(MSG.SCANNING).start();
962
+
963
+ // Analyze key directories
964
+ const dirs = [
965
+ { path: HOME, name: 'Home' },
966
+ { path: path.join(HOME, 'Library'), name: 'Library' },
967
+ { path: path.join(HOME, 'Documents'), name: 'Documents' },
968
+ { path: path.join(HOME, 'Downloads'), name: 'Downloads' },
969
+ { path: path.join(HOME, 'Desktop'), name: 'Desktop' },
970
+ { path: path.join(HOME, 'Movies'), name: 'Movies' },
971
+ { path: path.join(HOME, 'Music'), name: 'Music' },
972
+ { path: path.join(HOME, 'Pictures'), name: 'Pictures' },
973
+ { path: '/Applications', name: 'Applications' }
974
+ ];
975
+
976
+ const sizes = [];
977
+
978
+ for (const dir of dirs) {
979
+ if (fs.existsSync(dir.path)) {
980
+ dir.size = getSize(dir.path);
981
+ sizes.push(dir);
982
+ }
983
+ }
984
+
985
+ // Sort by size
986
+ sizes.sort((a, b) => b.size - a.size);
987
+
988
+ spinner.stop();
989
+
990
+ // Calculate max for bar scaling
991
+ const maxSize = Math.max(...sizes.map(s => s.size));
992
+ const barWidth = 40;
993
+
994
+ console.log(chalk.white(' Directory Usage:'));
995
+ console.log();
996
+
997
+ sizes.forEach((dir, i) => {
998
+ const barLength = Math.round((dir.size / maxSize) * barWidth);
999
+ const bar = chalk.cyan('\u2588'.repeat(barLength)) + chalk.gray('\u2591'.repeat(barWidth - barLength));
1000
+ const sizeStr = formatSize(dir.size).padStart(10);
1001
+
1002
+ console.log(` ${chalk.white(dir.name.padEnd(15))} ${bar} ${chalk.green(sizeStr)}`);
1003
+ });
1004
+
1005
+ console.log();
1006
+
1007
+ // Show Library breakdown
1008
+ console.log(chalk.white(' Library Breakdown:'));
1009
+ console.log();
1010
+
1011
+ const libraryDirs = [
1012
+ { path: path.join(HOME, 'Library/Caches'), name: 'Caches' },
1013
+ { path: path.join(HOME, 'Library/Application Support'), name: 'App Support' },
1014
+ { path: path.join(HOME, 'Library/Containers'), name: 'Containers' },
1015
+ { path: path.join(HOME, 'Library/Developer'), name: 'Developer' },
1016
+ { path: path.join(HOME, 'Library/Logs'), name: 'Logs' }
1017
+ ];
1018
+
1019
+ for (const dir of libraryDirs) {
1020
+ if (fs.existsSync(dir.path)) {
1021
+ const size = getSize(dir.path);
1022
+ const sizeStr = formatSize(size).padStart(10);
1023
+ console.log(` ${figures.bullet} ${chalk.dim(dir.name.padEnd(20))} ${chalk.yellow(sizeStr)}`);
1024
+ }
1025
+ }
1026
+
1027
+ await pressEnter();
1028
+ }
1029
+
1030
+ // ============================================================================
1031
+ // PRIVACY SWEEP
1032
+ // ============================================================================
1033
+
1034
+ async function privacySweep() {
1035
+ header();
1036
+ sectionHeader(MSG.OPT_PRIVACY);
1037
+
1038
+ const items = [];
1039
+
1040
+ // Browser data
1041
+ const browserPaths = [
1042
+ { path: '~/Library/Cookies', name: 'System Cookies' },
1043
+ { path: '~/Library/Application Support/Google/Chrome/Default/Cookies', name: 'Chrome Cookies' },
1044
+ { path: '~/Library/Application Support/Google/Chrome/Default/History', name: 'Chrome History' },
1045
+ { path: '~/Library/Application Support/Google/Chrome/Default/Login Data', name: 'Chrome Saved Logins' },
1046
+ { path: '~/Library/Safari/History.db', name: 'Safari History' },
1047
+ { path: '~/Library/Safari/Downloads.plist', name: 'Safari Downloads' },
1048
+ { path: '~/Library/Safari/LocalStorage', name: 'Safari Local Storage' },
1049
+ { path: '~/Library/Application Support/Firefox/Profiles', name: 'Firefox Profile Data' },
1050
+ { path: '~/.recently-used', name: 'Recent Files' },
1051
+ { path: '~/Library/Application Support/com.apple.sharedfilelist', name: 'Recent Items' },
1052
+ { path: '~/Library/Preferences/com.apple.recentitems.plist', name: 'Recent Items Plist' }
1053
+ ];
1054
+
1055
+ console.log(chalk.white(' Privacy-sensitive items found:'));
1056
+ console.log();
1057
+
1058
+ browserPaths.forEach((item, i) => {
1059
+ const fullPath = item.path.replace(/^~/, HOME);
1060
+ if (fs.existsSync(fullPath)) {
1061
+ const size = getSize(item.path);
1062
+ items.push({ ...item, fullPath, size });
1063
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(formatSize(size))}`);
1064
+ }
1065
+ });
1066
+
1067
+ if (items.length === 0) {
1068
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1069
+ await pressEnter();
1070
+ return;
1071
+ }
1072
+
1073
+ console.log();
1074
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [a=${MSG.ALL}/n=${MSG.CANCEL}]:`));
1075
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1076
+
1077
+ if (choice.toLowerCase() === 'n' || choice === '') {
1078
+ await pressEnter();
1079
+ return;
1080
+ }
1081
+
1082
+ let cleaned = 0;
1083
+
1084
+ if (choice.toLowerCase() === 'a') {
1085
+ const spinner = ora(MSG.CLEANING).start();
1086
+
1087
+ for (const item of items) {
1088
+ if (deleteItem(item.fullPath)) {
1089
+ cleaned += item.size;
1090
+ }
1091
+ }
1092
+
1093
+ spinner.succeed(chalk.green(`${MSG.PRIVACY_CLEANED}: ${formatSize(cleaned)}`));
1094
+ }
1095
+
1096
+ await pressEnter();
1097
+ }
1098
+
1099
+ // ============================================================================
1100
+ // MEMORY OPTIMIZER
1101
+ // ============================================================================
1102
+
1103
+ async function memoryOptimizer() {
1104
+ header();
1105
+ sectionHeader(MSG.OPT_MEMORY);
1106
+
1107
+ console.log(chalk.white(' Current Memory Status:'));
1108
+ console.log();
1109
+ console.log(` ${figures.bullet} RAM: ${systemStats.ramUsedPercent}% ${MSG.IN_USE}`);
1110
+ console.log(` ${figures.bullet} Swap: ${systemStats.swapUsed} MB ${MSG.IN_USE}`);
1111
+ console.log();
1112
+
1113
+ console.log(chalk.yellow(` ${figures.warning} This will run 'sudo purge' to free inactive memory.`));
1114
+ console.log(chalk.dim(' You may need to enter your password.'));
1115
+ console.log();
1116
+
1117
+ const confirm = await prompt(` ${chalk.white('Continue? [y/n]:')} `);
1118
+
1119
+ if (confirm.toLowerCase() !== 'y') {
1120
+ await pressEnter();
1121
+ return;
1122
+ }
1123
+
1124
+ if (DRY_RUN) {
1125
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would run: sudo purge`));
1126
+ } else {
1127
+ const spinner = ora('Freeing memory...').start();
1128
+
1129
+ try {
1130
+ execSync('sudo purge', { stdio: 'inherit' });
1131
+ spinner.succeed(chalk.green(MSG.MEMORY_FREED));
1132
+ } catch (e) {
1133
+ spinner.fail(chalk.red('Failed to free memory'));
1134
+ }
1135
+ }
1136
+
1137
+ // Refresh stats
1138
+ getSystemStats();
1139
+ console.log();
1140
+ console.log(chalk.white(' Updated Memory Status:'));
1141
+ console.log(` ${figures.bullet} RAM: ${systemStats.ramUsedPercent}% ${MSG.IN_USE}`);
1142
+ console.log(` ${figures.bullet} Swap: ${systemStats.swapUsed} MB ${MSG.IN_USE}`);
1143
+
1144
+ await pressEnter();
451
1145
  }
452
1146
 
453
- // ══════════════════════════════════════════════════════════════════════════════
454
- // OPTION 1: QUICK CLEAN
455
- // ══════════════════════════════════════════════════════════════════════════════
1147
+ // ============================================================================
1148
+ // BENCHMARK MODE
1149
+ // ============================================================================
1150
+
1151
+ async function benchmarkMode() {
1152
+ header();
1153
+ sectionHeader(MSG.OPT_BENCHMARK);
1154
+
1155
+ console.log(chalk.white(' Running system benchmark...'));
1156
+ console.log();
1157
+
1158
+ const results = {
1159
+ timestamp: new Date().toISOString(),
1160
+ tests: []
1161
+ };
1162
+
1163
+ // Disk read speed
1164
+ const spinner1 = ora('Testing disk read speed...').start();
1165
+ const diskStart = Date.now();
1166
+ exec('dd if=/dev/zero of=/tmp/benchmark_test bs=1m count=100 2>/dev/null');
1167
+ const diskWrite = Date.now() - diskStart;
1168
+
1169
+ const diskReadStart = Date.now();
1170
+ exec('dd if=/tmp/benchmark_test of=/dev/null bs=1m 2>/dev/null');
1171
+ const diskRead = Date.now() - diskReadStart;
1172
+
1173
+ exec('rm /tmp/benchmark_test');
1174
+ spinner1.succeed(`Disk: Write ${Math.round(100000 / diskWrite)} MB/s, Read ${Math.round(100000 / diskRead)} MB/s`);
1175
+
1176
+ results.tests.push({
1177
+ name: 'Disk Write',
1178
+ value: Math.round(100000 / diskWrite),
1179
+ unit: 'MB/s'
1180
+ });
1181
+ results.tests.push({
1182
+ name: 'Disk Read',
1183
+ value: Math.round(100000 / diskRead),
1184
+ unit: 'MB/s'
1185
+ });
1186
+
1187
+ // CPU benchmark
1188
+ const spinner2 = ora('Testing CPU performance...').start();
1189
+ const cpuStart = Date.now();
1190
+ let cpuTest = 0;
1191
+ for (let i = 0; i < 10000000; i++) {
1192
+ cpuTest += Math.sqrt(i);
1193
+ }
1194
+ const cpuTime = Date.now() - cpuStart;
1195
+ spinner2.succeed(`CPU: ${cpuTime}ms for 10M sqrt operations`);
1196
+
1197
+ results.tests.push({
1198
+ name: 'CPU (10M sqrt)',
1199
+ value: cpuTime,
1200
+ unit: 'ms'
1201
+ });
1202
+
1203
+ // Memory benchmark
1204
+ const spinner3 = ora('Testing memory allocation...').start();
1205
+ const memStart = Date.now();
1206
+ const testArrays = [];
1207
+ for (let i = 0; i < 100; i++) {
1208
+ testArrays.push(new Array(1000000).fill(Math.random()));
1209
+ }
1210
+ const memTime = Date.now() - memStart;
1211
+ spinner3.succeed(`Memory: ${memTime}ms to allocate 100MB`);
1212
+
1213
+ results.tests.push({
1214
+ name: 'Memory (100MB alloc)',
1215
+ value: memTime,
1216
+ unit: 'ms'
1217
+ });
1218
+
1219
+ // Save results
1220
+ const benchmarkLine = `${results.timestamp} | Disk: W${Math.round(100000 / diskWrite)}MB/s R${Math.round(100000 / diskRead)}MB/s | CPU: ${cpuTime}ms | Mem: ${memTime}ms\n`;
1221
+ fs.appendFileSync(BENCHMARK_FILE, benchmarkLine);
1222
+
1223
+ console.log();
1224
+ console.log(chalk.green(`${figures.tick} ${MSG.BENCHMARK_COMPLETE}`));
1225
+
1226
+ // Show history if available
1227
+ if (fs.existsSync(BENCHMARK_FILE)) {
1228
+ const history = fs.readFileSync(BENCHMARK_FILE, 'utf8').split('\n').filter(l => l.trim());
1229
+ if (history.length > 1) {
1230
+ console.log();
1231
+ console.log(chalk.white(' Recent benchmarks:'));
1232
+ history.slice(-5).forEach(line => {
1233
+ console.log(chalk.dim(` ${line}`));
1234
+ });
1235
+ }
1236
+ }
1237
+
1238
+ await pressEnter();
1239
+ }
1240
+
1241
+ // ============================================================================
1242
+ // SCHEDULER (LAUNCHD)
1243
+ // ============================================================================
1244
+
1245
+ async function scheduler() {
1246
+ header();
1247
+ sectionHeader(MSG.OPT_SCHEDULER);
1248
+
1249
+ const plistPath = path.join(HOME, 'Library/LaunchAgents/com.aris.mac-cleaner.plist');
1250
+ const isScheduled = fs.existsSync(plistPath);
1251
+
1252
+ console.log(chalk.white(' Cleanup Scheduler'));
1253
+ console.log();
1254
+
1255
+ if (isScheduled) {
1256
+ console.log(chalk.green(` ${figures.tick} Scheduled cleanup is ACTIVE`));
1257
+ console.log();
1258
+ console.log(` ${chalk.cyan('[1]')} Remove schedule`);
1259
+ console.log(` ${chalk.cyan('[2]')} View current schedule`);
1260
+ } else {
1261
+ console.log(chalk.yellow(` ${figures.info} No scheduled cleanup configured`));
1262
+ console.log();
1263
+ console.log(` ${chalk.cyan('[1]')} Schedule daily cleanup (midnight)`);
1264
+ console.log(` ${chalk.cyan('[2]')} Schedule weekly cleanup (Sunday midnight)`);
1265
+ }
1266
+
1267
+ console.log();
1268
+ console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
1269
+ console.log();
1270
+
1271
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1272
+
1273
+ if (isScheduled) {
1274
+ if (choice === '1') {
1275
+ // Remove schedule
1276
+ try {
1277
+ exec(`launchctl unload "${plistPath}" 2>/dev/null`);
1278
+ fs.unlinkSync(plistPath);
1279
+ console.log(chalk.green(` ${figures.tick} ${MSG.SCHEDULE_REMOVED}`));
1280
+ } catch (e) {
1281
+ console.log(chalk.red(` ${figures.cross} Failed to remove schedule`));
1282
+ }
1283
+ } else if (choice === '2') {
1284
+ // View schedule
1285
+ const content = fs.readFileSync(plistPath, 'utf8');
1286
+ console.log();
1287
+ console.log(chalk.dim(content));
1288
+ }
1289
+ } else {
1290
+ if (choice === '1' || choice === '2') {
1291
+ const isDaily = choice === '1';
1292
+
1293
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1294
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1295
+ <plist version="1.0">
1296
+ <dict>
1297
+ <key>Label</key>
1298
+ <string>com.aris.mac-cleaner</string>
1299
+ <key>ProgramArguments</key>
1300
+ <array>
1301
+ <string>${process.execPath}</string>
1302
+ <string>${__filename}</string>
1303
+ <string>--quick</string>
1304
+ </array>
1305
+ <key>StartCalendarInterval</key>
1306
+ <dict>
1307
+ <key>Hour</key>
1308
+ <integer>0</integer>
1309
+ <key>Minute</key>
1310
+ <integer>0</integer>
1311
+ ${isDaily ? '' : '<key>Weekday</key>\n <integer>0</integer>'}
1312
+ </dict>
1313
+ <key>StandardOutPath</key>
1314
+ <string>${path.join(CONFIG_DIR, 'scheduled-cleanup.log')}</string>
1315
+ <key>StandardErrorPath</key>
1316
+ <string>${path.join(CONFIG_DIR, 'scheduled-cleanup-error.log')}</string>
1317
+ </dict>
1318
+ </plist>`;
1319
+
1320
+ try {
1321
+ fs.writeFileSync(plistPath, plistContent);
1322
+ exec(`launchctl load "${plistPath}"`);
1323
+ console.log(chalk.green(` ${figures.tick} ${isDaily ? MSG.SCHEDULED_DAILY : MSG.SCHEDULED_WEEKLY}`));
1324
+ } catch (e) {
1325
+ console.log(chalk.red(` ${figures.cross} Failed to create schedule`));
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ await pressEnter();
1331
+ }
1332
+
1333
+ // ============================================================================
1334
+ // EXPORT HTML REPORT
1335
+ // ============================================================================
1336
+
1337
+ async function exportHtmlReport() {
1338
+ header();
1339
+ sectionHeader(MSG.OPT_EXPORT);
1340
+
1341
+ getSystemStats();
1342
+ const health = calculateHealthScore();
1343
+ const recs = getSmartRecommendations();
1344
+
1345
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
1346
+ const reportPath = path.join(REPORTS_DIR, `report-${timestamp}.html`);
1347
+
1348
+ // Read history
1349
+ let historyData = [];
1350
+ if (fs.existsSync(HISTORY_FILE)) {
1351
+ historyData = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim()).slice(-30);
1352
+ }
1353
+
1354
+ const html = `<!DOCTYPE html>
1355
+ <html lang="en">
1356
+ <head>
1357
+ <meta charset="UTF-8">
1358
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1359
+ <title>Aris Mac Cleaner Report - ${new Date().toLocaleDateString()}</title>
1360
+ <style>
1361
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1362
+ body {
1363
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1364
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
1365
+ color: #fff;
1366
+ min-height: 100vh;
1367
+ padding: 40px;
1368
+ }
1369
+ .container { max-width: 900px; margin: 0 auto; }
1370
+ .header {
1371
+ text-align: center;
1372
+ margin-bottom: 40px;
1373
+ padding: 30px;
1374
+ background: rgba(255,255,255,0.1);
1375
+ border-radius: 20px;
1376
+ backdrop-filter: blur(10px);
1377
+ }
1378
+ .header h1 {
1379
+ font-size: 2.5em;
1380
+ background: linear-gradient(90deg, #00d2ff, #3a7bd5);
1381
+ -webkit-background-clip: text;
1382
+ -webkit-text-fill-color: transparent;
1383
+ }
1384
+ .header p { color: #888; margin-top: 10px; }
1385
+ .card {
1386
+ background: rgba(255,255,255,0.05);
1387
+ border-radius: 15px;
1388
+ padding: 25px;
1389
+ margin-bottom: 20px;
1390
+ border: 1px solid rgba(255,255,255,0.1);
1391
+ }
1392
+ .card h2 {
1393
+ color: #00d2ff;
1394
+ margin-bottom: 20px;
1395
+ font-size: 1.3em;
1396
+ }
1397
+ .stat-row {
1398
+ display: flex;
1399
+ justify-content: space-between;
1400
+ padding: 12px 0;
1401
+ border-bottom: 1px solid rgba(255,255,255,0.05);
1402
+ }
1403
+ .stat-row:last-child { border-bottom: none; }
1404
+ .stat-label { color: #888; }
1405
+ .stat-value { font-weight: bold; }
1406
+ .health-score {
1407
+ text-align: center;
1408
+ padding: 40px;
1409
+ }
1410
+ .health-score .score {
1411
+ font-size: 4em;
1412
+ font-weight: bold;
1413
+ background: linear-gradient(90deg, ${health.score >= 80 ? '#00ff87, #60efff' : health.score >= 60 ? '#ffcc00, #ff6600' : '#ff0000, #ff6666'});
1414
+ -webkit-background-clip: text;
1415
+ -webkit-text-fill-color: transparent;
1416
+ }
1417
+ .health-score .label {
1418
+ color: #888;
1419
+ font-size: 1.2em;
1420
+ margin-top: 10px;
1421
+ }
1422
+ .recommendation {
1423
+ padding: 15px;
1424
+ margin: 10px 0;
1425
+ border-radius: 10px;
1426
+ background: rgba(255,204,0,0.1);
1427
+ border-left: 3px solid #ffcc00;
1428
+ }
1429
+ .recommendation.high {
1430
+ background: rgba(255,0,0,0.1);
1431
+ border-left-color: #ff4444;
1432
+ }
1433
+ .history-item {
1434
+ font-family: monospace;
1435
+ font-size: 0.9em;
1436
+ padding: 8px;
1437
+ color: #888;
1438
+ }
1439
+ .footer {
1440
+ text-align: center;
1441
+ margin-top: 40px;
1442
+ color: #666;
1443
+ font-size: 0.9em;
1444
+ }
1445
+ </style>
1446
+ </head>
1447
+ <body>
1448
+ <div class="container">
1449
+ <div class="header">
1450
+ <h1>ARIS MAC CLEANER</h1>
1451
+ <p>System Report - ${new Date().toLocaleString()}</p>
1452
+ </div>
1453
+
1454
+ <div class="card health-score">
1455
+ <div class="score">${health.score}/100</div>
1456
+ <div class="label">${MSG.HEALTH_SCORE} - ${health.text}</div>
1457
+ </div>
1458
+
1459
+ <div class="card">
1460
+ <h2>${MSG.DIAGNOSE}</h2>
1461
+ <div class="stat-row">
1462
+ <span class="stat-label">${MSG.DISK}</span>
1463
+ <span class="stat-value">${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB (${systemStats.diskPercent}% ${MSG.IN_USE})</span>
1464
+ </div>
1465
+ <div class="stat-row">
1466
+ <span class="stat-label">${MSG.RAM}</span>
1467
+ <span class="stat-value">${systemStats.ramUsedPercent}% ${MSG.IN_USE}</span>
1468
+ </div>
1469
+ <div class="stat-row">
1470
+ <span class="stat-label">${MSG.SWAP}</span>
1471
+ <span class="stat-value">${systemStats.swapUsed} MB ${MSG.IN_USE}</span>
1472
+ </div>
1473
+ <div class="stat-row">
1474
+ <span class="stat-label">${MSG.CPU}</span>
1475
+ <span class="stat-value">${systemStats.cpuUsed}% ${MSG.IN_USE}</span>
1476
+ </div>
1477
+ </div>
1478
+
1479
+ ${recs.length > 0 ? `
1480
+ <div class="card">
1481
+ <h2>${MSG.RECOMMENDATIONS}</h2>
1482
+ ${recs.map(r => `<div class="recommendation ${r.priority}">${r.text}</div>`).join('')}
1483
+ </div>
1484
+ ` : ''}
1485
+
1486
+ ${historyData.length > 0 ? `
1487
+ <div class="card">
1488
+ <h2>${MSG.HISTORY}</h2>
1489
+ ${historyData.map(h => `<div class="history-item">${h}</div>`).join('')}
1490
+ </div>
1491
+ ` : ''}
1492
+
1493
+ ${lastCleanupResults ? `
1494
+ <div class="card">
1495
+ <h2>Last Cleanup</h2>
1496
+ <div class="stat-row">
1497
+ <span class="stat-label">${MSG.FREED}</span>
1498
+ <span class="stat-value">${formatSize(lastCleanupResults.freed)}</span>
1499
+ </div>
1500
+ </div>
1501
+ ` : ''}
1502
+
1503
+ <div class="footer">
1504
+ <p>Generated by Aris Mac Cleaner v${VERSION}</p>
1505
+ <p>por Salvador Reis</p>
1506
+ </div>
1507
+ </div>
1508
+ </body>
1509
+ </html>`;
1510
+
1511
+ fs.writeFileSync(reportPath, html);
1512
+
1513
+ console.log(chalk.green(` ${figures.tick} ${MSG.REPORT_EXPORTED}`));
1514
+ console.log(chalk.dim(` ${reportPath}`));
1515
+ console.log();
1516
+
1517
+ const open = await prompt(` Open in browser? [y/n]: `);
1518
+ if (open.toLowerCase() === 'y') {
1519
+ exec(`open "${reportPath}"`);
1520
+ }
1521
+
1522
+ await pressEnter();
1523
+ }
1524
+
1525
+ // ============================================================================
1526
+ // STATISTICS WITH ASCII CHART
1527
+ // ============================================================================
1528
+
1529
+ async function showStatistics() {
1530
+ header();
1531
+ sectionHeader(MSG.OPT_STATS);
1532
+
1533
+ if (!fs.existsSync(HISTORY_FILE)) {
1534
+ console.log(` ${chalk.dim(MSG.NO_HISTORY)}`);
1535
+ await pressEnter();
1536
+ return;
1537
+ }
1538
+
1539
+ const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
1540
+ const currentMonth = new Date().toISOString().slice(0, 7);
1541
+
1542
+ const monthLines = history.filter(l => l.startsWith(currentMonth));
1543
+ const monthCleanups = monthLines.length;
1544
+
1545
+ let monthFreed = 0;
1546
+ let monthScoreSum = 0;
1547
+ const freedValues = [];
1548
+
1549
+ monthLines.forEach(line => {
1550
+ const freedMatch = line.match(/(\d+)\s*MB/);
1551
+ const scoreMatch = line.match(/Score:\s*(\d+)/);
1552
+ if (freedMatch) {
1553
+ const freed = parseInt(freedMatch[1]);
1554
+ monthFreed += freed;
1555
+ freedValues.push(freed);
1556
+ }
1557
+ if (scoreMatch) monthScoreSum += parseInt(scoreMatch[1]);
1558
+ });
1559
+
1560
+ const monthAvg = monthCleanups > 0 ? Math.round(monthScoreSum / monthCleanups) : 0;
1561
+
1562
+ console.log(chalk.white(` ${MSG.THIS_MONTH}:`));
1563
+ console.log(` ${figures.bullet} ${MSG.CLEANINGS}: ${chalk.cyan(monthCleanups)}`);
1564
+ console.log(` ${figures.bullet} ${MSG.TOTAL} ${MSG.FREED}: ${chalk.green(formatSize(monthFreed))}`);
1565
+ console.log(` ${figures.bullet} ${MSG.AVG_SCORE}: ${chalk.yellow(monthAvg + '/100')}`);
1566
+ console.log();
1567
+
1568
+ // Show ASCII chart if we have enough data
1569
+ if (freedValues.length >= 3) {
1570
+ console.log(chalk.white(' Space Freed (MB) - Last cleanups:'));
1571
+ console.log();
1572
+
1573
+ try {
1574
+ const chartData = freedValues.slice(-15);
1575
+ const chart = asciichart.plot(chartData, {
1576
+ height: 8,
1577
+ colors: [asciichart.cyan],
1578
+ format: (x) => String(Math.round(x)).padStart(6)
1579
+ });
1580
+ chart.split('\n').forEach(line => console.log(' ' + line));
1581
+ } catch (e) {
1582
+ // Fallback if asciichart fails
1583
+ freedValues.slice(-10).forEach((v, i) => {
1584
+ const bar = chalk.cyan('\u2588'.repeat(Math.min(Math.round(v / 50), 40)));
1585
+ console.log(` ${bar} ${v} MB`);
1586
+ });
1587
+ }
1588
+ console.log();
1589
+ }
1590
+
1591
+ console.log(chalk.white(` ${MSG.HISTORY}:`));
1592
+ history.slice(-10).forEach(line => {
1593
+ console.log(chalk.dim(` ${line}`));
1594
+ });
1595
+
1596
+ await pressEnter();
1597
+ }
1598
+
1599
+ // ============================================================================
1600
+ // QUICK CLEAN (ENHANCED)
1601
+ // ============================================================================
456
1602
 
457
1603
  async function quickClean() {
458
1604
  header();
459
1605
  getSystemStats();
460
1606
  showDiagnostics();
1607
+ showRecommendations();
461
1608
  sectionHeader(MSG.CATEGORIES);
462
1609
 
463
1610
  const items = [];
@@ -474,6 +1621,8 @@ async function quickClean() {
474
1621
  }
475
1622
  }
476
1623
 
1624
+ const spinner = ora(MSG.SCANNING).start();
1625
+
477
1626
  // System Caches
478
1627
  addItem('~/Library/Caches/Google', 'Google/Chrome Cache', 'system');
479
1628
  addItem('~/Library/Caches/com.spotify.client', 'Spotify Cache', 'system');
@@ -545,8 +1694,10 @@ async function quickClean() {
545
1694
  });
546
1695
  }
547
1696
 
1697
+ spinner.stop();
1698
+
548
1699
  if (items.length === 0) {
549
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
1700
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
550
1701
  await pressEnter();
551
1702
  return;
552
1703
  }
@@ -557,11 +1708,11 @@ async function quickClean() {
557
1708
  if (catItems.length === 0) return;
558
1709
 
559
1710
  const catSize = catItems.reduce((sum, i) => sum + i.size, 0);
560
- console.log(` ${c.white}📁 ${catName}${c.reset} ${c.dim}(${formatSize(catSize)})${c.reset}`);
1711
+ console.log(` ${chalk.white(' ' + catName)} ${chalk.dim('(' + formatSize(catSize) + ')')}`);
561
1712
 
562
1713
  catItems.forEach(item => {
563
1714
  const idx = items.indexOf(item) + 1;
564
- console.log(` ${c.cyan}[${String(idx).padStart(2)}]${c.reset} ${item.name.padEnd(35)} ${c.dim}${String(item.size).padStart(6)} MB${c.reset}`);
1715
+ console.log(` ${chalk.cyan('[' + String(idx).padStart(2) + ']')} ${item.name.padEnd(35)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
565
1716
  });
566
1717
  console.log();
567
1718
  }
@@ -573,18 +1724,18 @@ async function quickClean() {
573
1724
  showCategory('apps', MSG.APPS_CAT);
574
1725
  showCategory('custom', 'Custom');
575
1726
 
576
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalSize)}${c.reset}`);
1727
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalSize))}`);
577
1728
  console.log();
578
1729
 
579
1730
  // Selection
580
1731
  sectionHeader(MSG.SELECT_DELETE);
581
- console.log(` ${c.dim}a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10${c.reset}`);
1732
+ console.log(chalk.dim(` a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10`));
582
1733
  console.log();
583
1734
 
584
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
1735
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
585
1736
 
586
1737
  if (choice.toLowerCase() === 'n' || choice === '') {
587
- console.log(` ${c.yellow}${MSG.CANCEL}${c.reset}`);
1738
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
588
1739
  await pressEnter();
589
1740
  return;
590
1741
  }
@@ -608,42 +1759,68 @@ async function quickClean() {
608
1759
  });
609
1760
  }
610
1761
 
611
- // Clean
1762
+ // Create backup
1763
+ const selectedItems = items.filter(i => i.selected);
1764
+ if (selectedItems.length > 0 && !DRY_RUN) {
1765
+ const backupFile = createBackup(selectedItems);
1766
+ console.log(chalk.dim(` ${figures.info} ${MSG.BACKUP_CREATED}: ${path.basename(backupFile)}`));
1767
+ console.log();
1768
+ }
1769
+
1770
+ // Clean with progress bar
612
1771
  console.log();
613
1772
  sectionHeader(MSG.CLEANING);
614
1773
 
1774
+ const progressBar = createProgressBar(selectedItems.length);
1775
+ progressBar.start(selectedItems.length, 0);
1776
+
615
1777
  let cleanedSize = 0;
1778
+ const claudeVersionsPathLocal = path.join(HOME, '.local/share/claude/versions');
616
1779
 
617
- for (const item of items) {
1780
+ for (let i = 0; i < items.length; i++) {
1781
+ const item = items[i];
618
1782
  if (!item.selected) continue;
619
1783
 
620
1784
  if (item.path === 'CLAUDE_VERSIONS') {
621
- const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
622
- versions.slice(1).forEach(v => {
623
- try {
624
- fs.rmSync(path.join(claudeVersionsPath, v), { recursive: true, force: true });
625
- } catch (e) {}
626
- });
627
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
1785
+ if (!DRY_RUN) {
1786
+ const versions = fs.readdirSync(claudeVersionsPathLocal).sort().reverse();
1787
+ versions.slice(1).forEach(v => {
1788
+ try {
1789
+ deleteItem(path.join(claudeVersionsPathLocal, v));
1790
+ } catch (e) {}
1791
+ });
1792
+ }
628
1793
  cleanedSize += item.size;
629
1794
  } else if (item.path === 'OLD_DOWNLOADS') {
630
- exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
631
- exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
632
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
1795
+ if (!DRY_RUN) {
1796
+ exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
1797
+ exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
1798
+ }
633
1799
  cleanedSize += item.size;
634
1800
  } else if (fs.existsSync(item.path)) {
635
- try {
636
- fs.rmSync(item.path, { recursive: true, force: true });
637
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
1801
+ if (deleteItem(item.path)) {
638
1802
  cleanedSize += item.size;
639
- } catch (e) {}
1803
+ }
640
1804
  }
1805
+
1806
+ progressBar.increment();
641
1807
  }
642
1808
 
1809
+ progressBar.stop();
1810
+
643
1811
  // npm cache
644
- try {
645
- execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
646
- } catch (e) {}
1812
+ if (!DRY_RUN) {
1813
+ try {
1814
+ execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
1815
+ } catch (e) {}
1816
+ }
1817
+
1818
+ console.log();
1819
+
1820
+ // Show cleaned items
1821
+ selectedItems.forEach(item => {
1822
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
1823
+ });
647
1824
 
648
1825
  console.log();
649
1826
  getSystemStats();
@@ -651,16 +1828,15 @@ async function quickClean() {
651
1828
  await pressEnter();
652
1829
  }
653
1830
 
654
- // ══════════════════════════════════════════════════════════════════════════════
655
- // OPTION 2: LARGE FILES
656
- // ══════════════════════════════════════════════════════════════════════════════
1831
+ // ============================================================================
1832
+ // FIND LARGE FILES
1833
+ // ============================================================================
657
1834
 
658
1835
  async function findLargeFiles() {
659
1836
  header();
660
1837
  sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
661
1838
 
662
- console.log(` ${c.dim}${MSG.SCANNING}${c.reset}`);
663
- console.log();
1839
+ const spinner = ora(MSG.SCANNING).start();
664
1840
 
665
1841
  const files = [];
666
1842
 
@@ -681,8 +1857,10 @@ async function findLargeFiles() {
681
1857
  files.push({ path: file, size });
682
1858
  });
683
1859
 
1860
+ spinner.stop();
1861
+
684
1862
  if (files.length === 0) {
685
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
1863
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
686
1864
  await pressEnter();
687
1865
  return;
688
1866
  }
@@ -697,15 +1875,15 @@ async function findLargeFiles() {
697
1875
  if (displayFile.length > 50) {
698
1876
  displayFile = '...' + displayFile.slice(-47);
699
1877
  }
700
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${c.dim}${String(file.size).padStart(6)} MB${c.reset} ${displayFile}`);
1878
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${chalk.dim(String(file.size).padStart(6) + ' MB')} ${displayFile}`);
701
1879
  });
702
1880
 
703
1881
  console.log();
704
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalLarge)}${c.reset} ${c.dim}(${files.length} files)${c.reset}`);
1882
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalLarge))} ${chalk.dim('(' + files.length + ' files)')}`);
705
1883
  console.log();
706
1884
 
707
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/a/n]:${c.reset}`);
708
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
1885
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
1886
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
709
1887
 
710
1888
  if (choice.toLowerCase() === 'n' || choice === '') {
711
1889
  await pressEnter();
@@ -738,28 +1916,28 @@ async function findLargeFiles() {
738
1916
  for (let i = 0; i < files.length; i++) {
739
1917
  if (!selected[i]) continue;
740
1918
 
741
- try {
742
- fs.unlinkSync(files[i].path);
743
- console.log(` ${c.green}${CHECK}${c.reset} Deleted: ${path.basename(files[i].path)}`);
1919
+ if (deleteItem(files[i].path)) {
1920
+ console.log(` ${chalk.green(figures.tick)} Deleted: ${path.basename(files[i].path)}`);
744
1921
  deletedSize += files[i].size;
745
- } catch (e) {}
1922
+ }
746
1923
  }
747
1924
 
748
1925
  console.log();
749
- console.log(` ${c.green}${MSG.FREED}: ${formatSize(deletedSize)}${c.reset}`);
1926
+ console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(deletedSize))}`);
1927
+
1928
+ playNotificationSound();
750
1929
  await pressEnter();
751
1930
  }
752
1931
 
753
- // ══════════════════════════════════════════════════════════════════════════════
754
- // OPTION 3: FIND DUPLICATES
755
- // ══════════════════════════════════════════════════════════════════════════════
1932
+ // ============================================================================
1933
+ // FIND DUPLICATES
1934
+ // ============================================================================
756
1935
 
757
1936
  async function findDuplicates() {
758
1937
  header();
759
1938
  sectionHeader(MSG.DUPLICATES);
760
1939
 
761
- console.log(` ${c.dim}${MSG.SCANNING} (this may take a while)...${c.reset}`);
762
- console.log();
1940
+ const spinner = ora(`${MSG.SCANNING} (this may take a while)...`).start();
763
1941
 
764
1942
  // Find files > 1MB, group by size
765
1943
  const sizeMap = new Map();
@@ -776,7 +1954,7 @@ async function findDuplicates() {
776
1954
  } catch (e) {}
777
1955
  });
778
1956
 
779
- console.log(` ${c.dim}${MSG.HASHING}${c.reset}`);
1957
+ spinner.text = MSG.HASHING;
780
1958
 
781
1959
  // Find duplicates by hash for files with same size
782
1960
  const hashGroups = new Map();
@@ -795,6 +1973,8 @@ async function findDuplicates() {
795
1973
  }
796
1974
  }
797
1975
 
1976
+ spinner.stop();
1977
+
798
1978
  // Display duplicate groups
799
1979
  header();
800
1980
  sectionHeader(MSG.DUPLICATES);
@@ -811,12 +1991,12 @@ async function findDuplicates() {
811
1991
  const dupSpace = (fileList.length - 1) * fileSize;
812
1992
  totalDupSize += dupSpace;
813
1993
 
814
- console.log(` ${c.white}${MSG.GROUP} ${groupNum}${c.reset} ${c.dim}(${fileSize} MB ${MSG.EACH})${c.reset}`);
1994
+ console.log(` ${chalk.white(MSG.GROUP + ' ' + groupNum)} ${chalk.dim('(' + fileSize + ' MB ' + MSG.EACH + ')')}`);
815
1995
 
816
1996
  fileList.forEach((file, idx) => {
817
1997
  let display = file.replace(HOME, '~');
818
1998
  if (display.length > 50) display = '...' + display.slice(-47);
819
- console.log(` ${c.cyan}[${idx + 1}]${c.reset} ${display}`);
1999
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${display}`);
820
2000
  allDups.push(file);
821
2001
  });
822
2002
  console.log();
@@ -825,29 +2005,29 @@ async function findDuplicates() {
825
2005
  }
826
2006
 
827
2007
  if (groupNum === 0) {
828
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
2008
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
829
2009
  await pressEnter();
830
2010
  return;
831
2011
  }
832
2012
 
833
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalDupSize)}${c.reset} ${MSG.IN_DUPLICATES}`);
2013
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalDupSize))} ${MSG.IN_DUPLICATES}`);
834
2014
  console.log();
835
- console.log(` ${c.dim}${MSG.SELECT_KEEP}${c.reset}`);
836
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2015
+ console.log(chalk.dim(` ${MSG.SELECT_KEEP}`));
2016
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
837
2017
 
838
- console.log(` ${c.yellow}${MSG.FEATURE_PROGRESS}${c.reset}`);
2018
+ console.log(` ${chalk.yellow(MSG.FEATURE_PROGRESS)}`);
839
2019
  await pressEnter();
840
2020
  }
841
2021
 
842
- // ══════════════════════════════════════════════════════════════════════════════
843
- // OPTION 4: UNINSTALL APPS
844
- // ══════════════════════════════════════════════════════════════════════════════
2022
+ // ============================================================================
2023
+ // UNINSTALL APPS
2024
+ // ============================================================================
845
2025
 
846
2026
  async function uninstallApps() {
847
2027
  header();
848
2028
  sectionHeader(MSG.UNINSTALL);
849
2029
 
850
- console.log(` ${c.dim}${MSG.SCANNING}${c.reset}`);
2030
+ const spinner = ora(MSG.SCANNING).start();
851
2031
 
852
2032
  const apps = [];
853
2033
 
@@ -893,22 +2073,24 @@ async function uninstallApps() {
893
2073
  if (apps.length >= 25) break;
894
2074
  }
895
2075
 
2076
+ spinner.stop();
2077
+
896
2078
  header();
897
2079
  sectionHeader(MSG.UNINSTALL);
898
2080
 
899
2081
  if (apps.length === 0) {
900
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
2082
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
901
2083
  await pressEnter();
902
2084
  return;
903
2085
  }
904
2086
 
905
2087
  apps.forEach((app, i) => {
906
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${app.name.padEnd(25)} ${c.dim}(${String(app.appSize).padStart(3)} MB ${MSG.APP_SIZE} + ${String(app.dataSize).padStart(3)} MB ${MSG.DATA_SIZE})${c.reset}`);
2088
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${app.name.padEnd(25)} ${chalk.dim('(' + String(app.appSize).padStart(3) + ' MB ' + MSG.APP_SIZE + ' + ' + String(app.dataSize).padStart(3) + ' MB ' + MSG.DATA_SIZE + ')')}`);
907
2089
  });
908
2090
 
909
2091
  console.log();
910
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/n]:${c.reset}`);
911
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2092
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/n]:`));
2093
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
912
2094
 
913
2095
  if (choice.toLowerCase() === 'n' || choice === '') {
914
2096
  await pressEnter();
@@ -921,10 +2103,10 @@ async function uninstallApps() {
921
2103
  if (idx < 0 || idx >= apps.length) continue;
922
2104
 
923
2105
  const app = apps[idx];
924
- console.log(` ${c.yellow}${MSG.DELETING} ${app.name}...${c.reset}`);
2106
+ console.log(` ${chalk.yellow(MSG.DELETING + ' ' + app.name + '...')}`);
925
2107
 
926
2108
  // Remove app data
927
- if (app.bundleId) {
2109
+ if (app.bundleId && !DRY_RUN) {
928
2110
  const dataPaths = [
929
2111
  path.join(HOME, 'Library/Application Support', app.name),
930
2112
  path.join(HOME, 'Library/Application Support', app.bundleId),
@@ -938,33 +2120,32 @@ async function uninstallApps() {
938
2120
 
939
2121
  dataPaths.forEach(dp => {
940
2122
  try {
941
- if (fs.existsSync(dp)) fs.rmSync(dp, { recursive: true, force: true });
2123
+ if (fs.existsSync(dp)) deleteItem(dp);
942
2124
  } catch (e) {}
943
2125
  });
944
2126
  }
945
2127
 
946
2128
  // Remove app
947
- try {
948
- fs.rmSync(app.path, { recursive: true, force: true });
949
- console.log(` ${c.green}${CHECK}${c.reset} ${app.name} ${MSG.REMOVED}`);
950
- } catch (e) {
951
- console.log(` ${c.red}${MSG.NEED_SUDO}${c.reset}`);
952
- console.log(` ${c.dim}sudo rm -rf "${app.path}"${c.reset}`);
2129
+ if (deleteItem(app.path)) {
2130
+ console.log(` ${chalk.green(figures.tick)} ${app.name} ${MSG.REMOVED}`);
2131
+ } else {
2132
+ console.log(` ${chalk.red(MSG.NEED_SUDO)}`);
2133
+ console.log(` ${chalk.dim('sudo rm -rf "' + app.path + '"')}`);
953
2134
  }
954
2135
  }
955
2136
 
956
2137
  await pressEnter();
957
2138
  }
958
2139
 
959
- // ══════════════════════════════════════════════════════════════════════════════
960
- // OPTION 5: MANAGE STARTUP
961
- // ══════════════════════════════════════════════════════════════════════════════
2140
+ // ============================================================================
2141
+ // MANAGE STARTUP
2142
+ // ============================================================================
962
2143
 
963
2144
  async function manageStartup() {
964
2145
  header();
965
2146
  sectionHeader(MSG.STARTUP_ITEMS);
966
2147
 
967
- console.log(` ${c.white}${MSG.STARTUP_ITEMS}:${c.reset}`);
2148
+ console.log(` ${chalk.white(MSG.STARTUP_ITEMS + ':')}`);
968
2149
 
969
2150
  // Get login items via AppleScript
970
2151
  const loginItems = exec(`osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null`);
@@ -972,14 +2153,14 @@ async function manageStartup() {
972
2153
  if (loginItems) {
973
2154
  const items = loginItems.split(', ').filter(i => i.trim());
974
2155
  items.forEach((item, idx) => {
975
- console.log(` ${c.cyan}[${idx + 1}]${c.reset} ${c.green}${CHECK}${c.reset} ${item} ${c.dim}(${MSG.ENABLED})${c.reset}`);
2156
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${chalk.green(figures.tick)} ${item} ${chalk.dim('(' + MSG.ENABLED + ')')}`);
976
2157
  });
977
2158
  } else {
978
- console.log(` ${c.dim}No login items found${c.reset}`);
2159
+ console.log(chalk.dim(' No login items found'));
979
2160
  }
980
2161
 
981
2162
  console.log();
982
- console.log(` ${c.white}${MSG.LAUNCH_AGENTS}:${c.reset}`);
2163
+ console.log(` ${chalk.white(MSG.LAUNCH_AGENTS + ':')}`);
983
2164
 
984
2165
  // User Launch Agents
985
2166
  const launchAgentsDir = path.join(HOME, 'Library/LaunchAgents');
@@ -993,29 +2174,29 @@ async function manageStartup() {
993
2174
  const loaded = exec(`launchctl list 2>/dev/null | grep "${name}"`);
994
2175
 
995
2176
  const status = loaded ?
996
- `${c.green}${CHECK}${c.reset} ${MSG.ENABLED}` :
997
- `${c.red}${CROSS}${c.reset} ${MSG.DISABLED}`;
2177
+ `${chalk.green(figures.tick)} ${MSG.ENABLED}` :
2178
+ `${chalk.red(figures.cross)} ${MSG.DISABLED}`;
998
2179
 
999
- console.log(` ${c.cyan}[${idx}]${c.reset} ${status} ${c.dim}${name}${c.reset}`);
2180
+ console.log(` ${chalk.cyan('[' + idx + ']')} ${status} ${chalk.dim(name)}`);
1000
2181
  idx++;
1001
2182
  }
1002
2183
  }
1003
2184
 
1004
2185
  console.log();
1005
- console.log(` ${c.dim}Enter number to toggle, 'd' to disable all, or 'n' to go back${c.reset}`);
1006
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2186
+ console.log(chalk.dim(" Enter number to toggle, 'd' to disable all, or 'n' to go back"));
2187
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1007
2188
 
1008
2189
  if (choice.toLowerCase() === 'd') {
1009
2190
  exec(`osascript -e 'tell application "System Events" to delete every login item' 2>/dev/null`);
1010
- console.log(` ${c.green}${CHECK}${c.reset} All login items disabled`);
2191
+ console.log(` ${chalk.green(figures.tick)} All login items disabled`);
1011
2192
  }
1012
2193
 
1013
2194
  await pressEnter();
1014
2195
  }
1015
2196
 
1016
- // ══════════════════════════════════════════════════════════════════════════════
1017
- // OPTION 6: CLEAN BROWSERS
1018
- // ══════════════════════════════════════════════════════════════════════════════
2197
+ // ============================================================================
2198
+ // CLEAN BROWSERS
2199
+ // ============================================================================
1019
2200
 
1020
2201
  async function cleanBrowsers() {
1021
2202
  header();
@@ -1033,37 +2214,37 @@ async function cleanBrowsers() {
1033
2214
  }
1034
2215
  }
1035
2216
 
1036
- console.log(` ${c.white}Chrome:${c.reset}`);
2217
+ console.log(` ${chalk.white('Chrome:')}`);
1037
2218
  addBrowserItem('~/Library/Caches/Google/Chrome', 'Chrome Cache');
1038
2219
  addBrowserItem('~/Library/Application Support/Google/Chrome/Default/Service Worker', 'Chrome Service Workers');
1039
2220
  addBrowserItem('~/Library/Application Support/Google/Chrome/Default/GPUCache', 'Chrome GPU Cache');
1040
2221
 
1041
- console.log(` ${c.white}Safari:${c.reset}`);
2222
+ console.log(` ${chalk.white('Safari:')}`);
1042
2223
  addBrowserItem('~/Library/Caches/com.apple.Safari', 'Safari Cache');
1043
2224
  addBrowserItem('~/Library/Safari/LocalStorage', 'Safari Local Storage');
1044
2225
 
1045
- console.log(` ${c.white}Firefox:${c.reset}`);
2226
+ console.log(` ${chalk.white('Firefox:')}`);
1046
2227
  const ffProfile = exec('find ~/Library/Application\\ Support/Firefox/Profiles -maxdepth 1 -name "*.default*" 2>/dev/null | head -1');
1047
2228
  if (ffProfile) {
1048
2229
  addBrowserItem(`${ffProfile}/cache2`, 'Firefox Cache');
1049
2230
  }
1050
2231
 
1051
2232
  items.forEach((item, i) => {
1052
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${item.name.padEnd(30)} ${c.dim}${String(item.size).padStart(6)} MB${c.reset}`);
2233
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
1053
2234
  });
1054
2235
 
1055
2236
  if (items.length === 0) {
1056
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
2237
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1057
2238
  await pressEnter();
1058
2239
  return;
1059
2240
  }
1060
2241
 
1061
2242
  console.log();
1062
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalBrowser)}${c.reset}`);
2243
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalBrowser))}`);
1063
2244
  console.log();
1064
2245
 
1065
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/a/n]:${c.reset}`);
1066
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2246
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
2247
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1067
2248
 
1068
2249
  if (choice.toLowerCase() === 'n' || choice === '') {
1069
2250
  await pressEnter();
@@ -1085,27 +2266,28 @@ async function cleanBrowsers() {
1085
2266
 
1086
2267
  for (const item of items) {
1087
2268
  if (!item.selected) continue;
1088
- try {
1089
- fs.rmSync(item.path, { recursive: true, force: true });
1090
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
2269
+ if (deleteItem(item.path)) {
2270
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
1091
2271
  cleaned += item.size;
1092
- } catch (e) {}
2272
+ }
1093
2273
  }
1094
2274
 
1095
2275
  console.log();
1096
- console.log(` ${c.green}${MSG.FREED}: ${formatSize(cleaned)}${c.reset}`);
2276
+ console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(cleaned))}`);
2277
+
2278
+ playNotificationSound();
1097
2279
  await pressEnter();
1098
2280
  }
1099
2281
 
1100
- // ══════════════════════════════════════════════════════════════════════════════
1101
- // OPTION 7: KILL PROCESSES
1102
- // ══════════════════════════════════════════════════════════════════════════════
2282
+ // ============================================================================
2283
+ // KILL PROCESSES
2284
+ // ============================================================================
1103
2285
 
1104
2286
  async function killProcesses() {
1105
2287
  header();
1106
2288
  sectionHeader(MSG.PROCESS_MEM);
1107
2289
 
1108
- console.log(` ${c.white}${MSG.PROCESS_MEM}:${c.reset}`);
2290
+ console.log(` ${chalk.white(MSG.PROCESS_MEM + ':')}`);
1109
2291
 
1110
2292
  const procs = [];
1111
2293
  const totalMemBytes = parseInt(exec("sysctl hw.memsize 2>/dev/null | awk '{print $2}'")) || 8589934592;
@@ -1130,20 +2312,20 @@ async function killProcesses() {
1130
2312
 
1131
2313
  procs.push({ pid, name, memMB });
1132
2314
 
1133
- console.log(` ${c.cyan}[${String(procs.length).padStart(2)}]${c.reset} ${name.padEnd(30)} ${c.red}${memMB} MB RAM${c.reset}`);
2315
+ console.log(` ${chalk.cyan('[' + String(procs.length).padStart(2) + ']')} ${name.padEnd(30)} ${chalk.red(memMB + ' MB RAM')}`);
1134
2316
 
1135
2317
  if (procs.length >= 10) break;
1136
2318
  }
1137
2319
 
1138
2320
  if (procs.length === 0) {
1139
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
2321
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1140
2322
  await pressEnter();
1141
2323
  return;
1142
2324
  }
1143
2325
 
1144
2326
  console.log();
1145
- console.log(` ${c.dim}${MSG.KILL} process [number/n]:${c.reset}`);
1146
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2327
+ console.log(chalk.dim(` ${MSG.KILL} process [number/n]:`));
2328
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1147
2329
 
1148
2330
  if (choice.toLowerCase() === 'n' || choice === '') {
1149
2331
  await pressEnter();
@@ -1153,66 +2335,25 @@ async function killProcesses() {
1153
2335
  const idx = parseInt(choice) - 1;
1154
2336
  if (idx >= 0 && idx < procs.length) {
1155
2337
  const proc = procs[idx];
1156
- try {
1157
- process.kill(parseInt(proc.pid), 'SIGKILL');
1158
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.KILLED}: ${proc.name} (PID: ${proc.pid})`);
1159
- } catch (e) {
1160
- console.log(` ${c.red}${CROSS}${c.reset} ${MSG.FAILED_KILL} ${proc.name}`);
1161
- }
1162
- }
1163
-
1164
- await pressEnter();
1165
- }
1166
2338
 
1167
- // ══════════════════════════════════════════════════════════════════════════════
1168
- // OPTION 8: STATISTICS
1169
- // ══════════════════════════════════════════════════════════════════════════════
1170
-
1171
- async function showStatistics() {
1172
- header();
1173
- sectionHeader(MSG.OPT_STATS);
1174
-
1175
- if (!fs.existsSync(HISTORY_FILE)) {
1176
- console.log(` ${c.dim}${MSG.NO_HISTORY}${c.reset}`);
1177
- await pressEnter();
1178
- return;
2339
+ if (DRY_RUN) {
2340
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would kill: ${proc.name} (PID: ${proc.pid})`));
2341
+ } else {
2342
+ try {
2343
+ process.kill(parseInt(proc.pid), 'SIGKILL');
2344
+ console.log(` ${chalk.green(figures.tick)} ${MSG.KILLED}: ${proc.name} (PID: ${proc.pid})`);
2345
+ } catch (e) {
2346
+ console.log(` ${chalk.red(figures.cross)} ${MSG.FAILED_KILL} ${proc.name}`);
2347
+ }
2348
+ }
1179
2349
  }
1180
2350
 
1181
- const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
1182
- const currentMonth = new Date().toISOString().slice(0, 7);
1183
-
1184
- const monthLines = history.filter(l => l.startsWith(currentMonth));
1185
- const monthCleanups = monthLines.length;
1186
-
1187
- let monthFreed = 0;
1188
- let monthScoreSum = 0;
1189
-
1190
- monthLines.forEach(line => {
1191
- const freedMatch = line.match(/(\d+)\s*MB/);
1192
- const scoreMatch = line.match(/Score:\s*(\d+)/);
1193
- if (freedMatch) monthFreed += parseInt(freedMatch[1]);
1194
- if (scoreMatch) monthScoreSum += parseInt(scoreMatch[1]);
1195
- });
1196
-
1197
- const monthAvg = monthCleanups > 0 ? Math.round(monthScoreSum / monthCleanups) : 0;
1198
-
1199
- console.log(` ${c.white}${MSG.THIS_MONTH}:${c.reset}`);
1200
- console.log(` ${BULLET} ${MSG.CLEANINGS}: ${monthCleanups}`);
1201
- console.log(` ${BULLET} ${MSG.TOTAL} ${MSG.FREED}: ${formatSize(monthFreed)}`);
1202
- console.log(` ${BULLET} ${MSG.AVG_SCORE}: ${monthAvg}/100`);
1203
- console.log();
1204
-
1205
- console.log(` ${c.white}${MSG.HISTORY}:${c.reset}`);
1206
- history.slice(-10).forEach(line => {
1207
- console.log(` ${c.dim}${line}${c.reset}`);
1208
- });
1209
-
1210
2351
  await pressEnter();
1211
2352
  }
1212
2353
 
1213
- // ══════════════════════════════════════════════════════════════════════════════
1214
- // OPTION 9: SETTINGS
1215
- // ══════════════════════════════════════════════════════════════════════════════
2354
+ // ============================================================================
2355
+ // SETTINGS
2356
+ // ============================================================================
1216
2357
 
1217
2358
  async function showSettings() {
1218
2359
  while (true) {
@@ -1221,15 +2362,18 @@ async function showSettings() {
1221
2362
 
1222
2363
  const currentLang = LANG_CODE === 'en' ? 'EN' : 'PT';
1223
2364
 
1224
- console.log(` ${c.cyan}[1]${c.reset} ${MSG.EXCLUSIONS}`);
1225
- console.log(` ${c.cyan}[2]${c.reset} ${MSG.CUSTOM_PATHS}`);
1226
- console.log(` ${c.cyan}[3]${c.reset} ${MSG.CHANGE_LANG} (current: ${currentLang})`);
1227
- console.log(` ${c.cyan}[4]${c.reset} ${MSG.RESET_DEFAULTS}`);
2365
+ console.log(` ${chalk.cyan('[1]')} ${MSG.EXCLUSIONS}`);
2366
+ console.log(` ${chalk.cyan('[2]')} ${MSG.CUSTOM_PATHS}`);
2367
+ console.log(` ${chalk.cyan('[3]')} ${MSG.CHANGE_LANG} (current: ${currentLang})`);
2368
+ console.log(` ${chalk.cyan('[4]')} ${MSG.RESET_DEFAULTS}`);
2369
+ console.log(` ${chalk.cyan('[5]')} ${DRY_RUN ? chalk.green(figures.tick) : figures.radioOff} Dry-Run Mode`);
2370
+ console.log(` ${chalk.cyan('[6]')} ${SECURE_DELETE ? chalk.green(figures.tick) : figures.radioOff} Secure Delete Mode`);
2371
+ console.log(` ${chalk.cyan('[7]')} View Backups`);
1228
2372
  console.log();
1229
- console.log(` ${c.cyan}[0]${c.reset} ${MSG.BACK}`);
2373
+ console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
1230
2374
  console.log();
1231
2375
 
1232
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2376
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1233
2377
 
1234
2378
  switch (choice) {
1235
2379
  case '1':
@@ -1237,24 +2381,24 @@ async function showSettings() {
1237
2381
  header();
1238
2382
  sectionHeader(MSG.EXCLUSIONS);
1239
2383
 
1240
- console.log(` ${c.white}${MSG.CURRENT_EXCLUSIONS}:${c.reset}`);
2384
+ console.log(` ${chalk.white(MSG.CURRENT_EXCLUSIONS + ':')}`);
1241
2385
  if (fs.existsSync(EXCLUSIONS_FILE)) {
1242
2386
  const exclusions = fs.readFileSync(EXCLUSIONS_FILE, 'utf8').split('\n').filter(l => l.trim());
1243
2387
  exclusions.forEach((ex, i) => console.log(` ${i + 1}. ${ex}`));
1244
2388
  } else {
1245
- console.log(` ${c.dim}None${c.reset}`);
2389
+ console.log(chalk.dim(' None'));
1246
2390
  }
1247
2391
  console.log();
1248
- console.log(` ${c.dim}${MSG.ENTER_PATH}:${c.reset}`);
2392
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
1249
2393
 
1250
- const newExclusion = await prompt(` ${c.white}${MSG.PATH}:${c.reset} `);
2394
+ const newExclusion = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
1251
2395
 
1252
2396
  if (newExclusion === 'c') {
1253
2397
  if (fs.existsSync(EXCLUSIONS_FILE)) fs.unlinkSync(EXCLUSIONS_FILE);
1254
- console.log(` ${c.green}${CHECK}${c.reset} Exclusions ${MSG.CLEARED}`);
2398
+ console.log(` ${chalk.green(figures.tick)} Exclusions ${MSG.CLEARED}`);
1255
2399
  } else if (newExclusion !== 'n' && newExclusion) {
1256
2400
  fs.appendFileSync(EXCLUSIONS_FILE, newExclusion + '\n');
1257
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.ADDED}: ${newExclusion}`);
2401
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newExclusion}`);
1258
2402
  }
1259
2403
  await pressEnter();
1260
2404
  break;
@@ -1264,24 +2408,24 @@ async function showSettings() {
1264
2408
  header();
1265
2409
  sectionHeader(MSG.CUSTOM_PATHS);
1266
2410
 
1267
- console.log(` ${c.white}${MSG.CURRENT_PATHS}:${c.reset}`);
2411
+ console.log(` ${chalk.white(MSG.CURRENT_PATHS + ':')}`);
1268
2412
  if (fs.existsSync(CUSTOM_PATHS_FILE)) {
1269
2413
  const paths = fs.readFileSync(CUSTOM_PATHS_FILE, 'utf8').split('\n').filter(l => l.trim() && !l.startsWith('#'));
1270
2414
  paths.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
1271
2415
  } else {
1272
- console.log(` ${c.dim}None${c.reset}`);
2416
+ console.log(chalk.dim(' None'));
1273
2417
  }
1274
2418
  console.log();
1275
- console.log(` ${c.dim}${MSG.ENTER_PATH}:${c.reset}`);
2419
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
1276
2420
 
1277
- const newPath = await prompt(` ${c.white}${MSG.PATH}:${c.reset} `);
2421
+ const newPath = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
1278
2422
 
1279
2423
  if (newPath === 'c') {
1280
2424
  if (fs.existsSync(CUSTOM_PATHS_FILE)) fs.unlinkSync(CUSTOM_PATHS_FILE);
1281
- console.log(` ${c.green}${CHECK}${c.reset} Custom paths ${MSG.CLEARED}`);
2425
+ console.log(` ${chalk.green(figures.tick)} Custom paths ${MSG.CLEARED}`);
1282
2426
  } else if (newPath !== 'n' && newPath) {
1283
2427
  fs.appendFileSync(CUSTOM_PATHS_FILE, newPath + '\n');
1284
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.ADDED}: ${newPath}`);
2428
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newPath}`);
1285
2429
  }
1286
2430
  await pressEnter();
1287
2431
  break;
@@ -1290,10 +2434,9 @@ async function showSettings() {
1290
2434
  // Change language
1291
2435
  const newLang = LANG_CODE === 'pt' ? 'en' : 'pt';
1292
2436
  fs.writeFileSync(LANGUAGE_FILE, newLang);
1293
- console.log(` ${c.green}${CHECK}${c.reset} ${newLang === 'en' ? MSG.LANG_CHANGED_EN : MSG.LANG_CHANGED_PT}`);
2437
+ console.log(` ${chalk.green(figures.tick)} ${newLang === 'en' ? MSG.LANG_CHANGED_EN : MSG.LANG_CHANGED_PT}`);
1294
2438
  await pressEnter();
1295
2439
  // Restart to apply language change
1296
- const { spawn } = require('child_process');
1297
2440
  spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1298
2441
  process.exit(0);
1299
2442
  break;
@@ -1303,14 +2446,41 @@ async function showSettings() {
1303
2446
  [EXCLUSIONS_FILE, CUSTOM_PATHS_FILE, LANGUAGE_FILE].forEach(f => {
1304
2447
  if (fs.existsSync(f)) fs.unlinkSync(f);
1305
2448
  });
1306
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.SETTINGS_RESET}`);
2449
+ console.log(` ${chalk.green(figures.tick)} ${MSG.SETTINGS_RESET}`);
1307
2450
  await pressEnter();
1308
2451
  // Restart to apply changes
1309
- const { spawn: spawn2 } = require('child_process');
1310
- spawn2(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
2452
+ spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1311
2453
  process.exit(0);
1312
2454
  break;
1313
2455
 
2456
+ case '5':
2457
+ DRY_RUN = !DRY_RUN;
2458
+ console.log(` ${chalk.green(figures.tick)} Dry-Run Mode: ${DRY_RUN ? 'ON' : 'OFF'}`);
2459
+ await pressEnter();
2460
+ break;
2461
+
2462
+ case '6':
2463
+ SECURE_DELETE = !SECURE_DELETE;
2464
+ console.log(` ${chalk.green(figures.tick)} Secure Delete Mode: ${SECURE_DELETE ? 'ON' : 'OFF'}`);
2465
+ await pressEnter();
2466
+ break;
2467
+
2468
+ case '7':
2469
+ // View backups
2470
+ header();
2471
+ sectionHeader('Backups');
2472
+
2473
+ const backups = listBackups();
2474
+ if (backups.length === 0) {
2475
+ console.log(chalk.dim(' No backups found'));
2476
+ } else {
2477
+ backups.forEach((b, i) => {
2478
+ console.log(` ${chalk.cyan('[' + (i + 1) + ']')} ${b}`);
2479
+ });
2480
+ }
2481
+ await pressEnter();
2482
+ break;
2483
+
1314
2484
  case '0':
1315
2485
  return;
1316
2486
 
@@ -1320,56 +2490,84 @@ async function showSettings() {
1320
2490
  }
1321
2491
  }
1322
2492
 
1323
- // ══════════════════════════════════════════════════════════════════════════════
2493
+ // ============================================================================
1324
2494
  // MAIN MENU
1325
- // ══════════════════════════════════════════════════════════════════════════════
2495
+ // ============================================================================
1326
2496
 
1327
2497
  async function mainMenu() {
1328
2498
  while (true) {
1329
2499
  header();
1330
2500
 
1331
- console.log(` ${c.cyan}[1]${c.reset} 🧹 ${MSG.OPT_QUICK}`);
1332
- console.log(` ${c.cyan}[2]${c.reset} 📦 ${MSG.OPT_LARGE}`);
1333
- console.log(` ${c.cyan}[3]${c.reset} 🔄 ${MSG.OPT_DUPLICATES}`);
1334
- console.log(` ${c.cyan}[4]${c.reset} 🗑️ ${MSG.OPT_APPS}`);
1335
- console.log(` ${c.cyan}[5]${c.reset} 🚀 ${MSG.OPT_STARTUP}`);
1336
- console.log(` ${c.cyan}[6]${c.reset} 🌐 ${MSG.OPT_BROWSERS}`);
1337
- console.log(` ${c.cyan}[7]${c.reset} 💀 ${MSG.OPT_PROCESSES}`);
1338
- console.log(` ${c.cyan}[8]${c.reset} 📊 ${MSG.OPT_STATS}`);
1339
- console.log(` ${c.cyan}[9]${c.reset} ⚙️ ${MSG.OPT_SETTINGS}`);
2501
+ // Show update notification
2502
+ await showUpdateNotification();
2503
+
2504
+ // Main options
2505
+ console.log(chalk.white(' CLEANING'));
2506
+ console.log(` ${chalk.cyan('[1]')} ${MSG.OPT_QUICK}`);
2507
+ console.log(` ${chalk.cyan('[2]')} ${MSG.OPT_LARGE}`);
2508
+ console.log(` ${chalk.cyan('[3]')} ${MSG.OPT_DUPLICATES}`);
2509
+ console.log(` ${chalk.cyan('[4]')} ${MSG.OPT_APPS}`);
2510
+ console.log(` ${chalk.cyan('[5]')} ${MSG.OPT_BROWSERS}`);
2511
+ console.log(` ${chalk.cyan('[6]')} ${MSG.OPT_PRIVACY}`);
2512
+ console.log();
2513
+
2514
+ console.log(chalk.white(' SYSTEM'));
2515
+ console.log(` ${chalk.cyan('[7]')} ${MSG.OPT_STARTUP}`);
2516
+ console.log(` ${chalk.cyan('[8]')} ${MSG.OPT_PROCESSES}`);
2517
+ console.log(` ${chalk.cyan('[9]')} ${MSG.OPT_MEMORY}`);
2518
+ console.log(` ${chalk.cyan('[10]')} ${MSG.OPT_DISK}`);
2519
+ console.log();
2520
+
2521
+ console.log(chalk.white(' TOOLS'));
2522
+ console.log(` ${chalk.cyan('[11]')} ${MSG.OPT_BENCHMARK}`);
2523
+ console.log(` ${chalk.cyan('[12]')} ${MSG.OPT_SCHEDULER}`);
2524
+ console.log(` ${chalk.cyan('[13]')} ${MSG.OPT_EXPORT}`);
2525
+ console.log(` ${chalk.cyan('[14]')} ${MSG.OPT_STATS}`);
2526
+ console.log(` ${chalk.cyan('[15]')} ${MSG.OPT_SETTINGS}`);
1340
2527
  console.log();
1341
- console.log(` ${c.cyan}[0]${c.reset} ${MSG.OPT_EXIT}`);
2528
+
2529
+ console.log(` ${chalk.cyan('[0]')} ${MSG.OPT_EXIT}`);
1342
2530
  console.log();
1343
2531
 
1344
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2532
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1345
2533
 
1346
2534
  switch (choice) {
1347
2535
  case '1': await quickClean(); break;
1348
2536
  case '2': await findLargeFiles(); break;
1349
2537
  case '3': await findDuplicates(); break;
1350
2538
  case '4': await uninstallApps(); break;
1351
- case '5': await manageStartup(); break;
1352
- case '6': await cleanBrowsers(); break;
1353
- case '7': await killProcesses(); break;
1354
- case '8': await showStatistics(); break;
1355
- case '9': await showSettings(); break;
2539
+ case '5': await cleanBrowsers(); break;
2540
+ case '6': await privacySweep(); break;
2541
+ case '7': await manageStartup(); break;
2542
+ case '8': await killProcesses(); break;
2543
+ case '9': await memoryOptimizer(); break;
2544
+ case '10': await diskAnalyzer(); break;
2545
+ case '11': await benchmarkMode(); break;
2546
+ case '12': await scheduler(); break;
2547
+ case '13': await exportHtmlReport(); break;
2548
+ case '14': await showStatistics(); break;
2549
+ case '15': await showSettings(); break;
1356
2550
  case '0':
1357
2551
  clearScreen();
1358
2552
  console.log();
1359
- console.log(`${c.dim}por Salvador Reis ${BULLET} github.com/salvadorreis${c.reset}`);
2553
+ console.log(gradient.pastel.multiline(`
2554
+ Thank you for using Aris Mac Cleaner!
2555
+
2556
+ por Salvador Reis ${figures.bullet} github.com/salvadorreis
2557
+ `));
1360
2558
  console.log();
1361
2559
  process.exit(0);
1362
2560
  break;
1363
2561
  default:
1364
- console.log(` ${c.red}${MSG.INVALID}${c.reset}`);
2562
+ console.log(` ${chalk.red(MSG.INVALID)}`);
1365
2563
  await new Promise(r => setTimeout(r, 1000));
1366
2564
  }
1367
2565
  }
1368
2566
  }
1369
2567
 
1370
- // ══════════════════════════════════════════════════════════════════════════════
2568
+ // ============================================================================
1371
2569
  // MAIN
1372
- // ══════════════════════════════════════════════════════════════════════════════
2570
+ // ============================================================================
1373
2571
 
1374
2572
  // Check if macOS
1375
2573
  if (os.platform() !== 'darwin') {
@@ -1380,32 +2578,63 @@ if (os.platform() !== 'darwin') {
1380
2578
  // Command line arguments
1381
2579
  const args = process.argv.slice(2);
1382
2580
 
1383
- if (args.includes('--preview') || args.includes('--dry-run')) {
1384
- console.log('Preview mode - nothing will be deleted');
1385
- process.exit(0);
2581
+ // Parse flags
2582
+ if (args.includes('--dry-run')) {
2583
+ DRY_RUN = true;
1386
2584
  }
1387
2585
 
1388
- if (args.includes('--quick') || args.includes('-q')) {
1389
- (async () => {
1390
- getSystemStats();
1391
- await quickClean();
1392
- })();
1393
- } else if (args.includes('--version') || args.includes('-v')) {
2586
+ if (args.includes('--secure')) {
2587
+ SECURE_DELETE = true;
2588
+ }
2589
+
2590
+ if (args.includes('--version') || args.includes('-v')) {
1394
2591
  console.log(`Aris Mac Cleaner v${VERSION}`);
1395
2592
  process.exit(0);
1396
- } else if (args.includes('--help') || args.includes('-h')) {
2593
+ }
2594
+
2595
+ if (args.includes('--help') || args.includes('-h')) {
1397
2596
  console.log(`
1398
- Aris Mac Cleaner v${VERSION}
2597
+ ${gradient.pastel.multiline(ASCII_LOGO)}
1399
2598
 
1400
2599
  Usage:
1401
- aris-mac-cleaner Start interactive menu
1402
- aris-mac-cleaner -q Quick clean without menu
1403
- aris-mac-cleaner -v Show version
1404
- aris-mac-cleaner -h Show this help
2600
+ aris-mac-cleaner Start interactive menu
2601
+ aris-mac-cleaner -q Quick clean without menu
2602
+ aris-mac-cleaner --dry-run Preview mode (nothing deleted)
2603
+ aris-mac-cleaner --secure Secure delete (overwrite before delete)
2604
+ aris-mac-cleaner -v Show version
2605
+ aris-mac-cleaner -h Show this help
1405
2606
 
1406
2607
  Config directory: ~/.aris-mac-cleaner/
2608
+
2609
+ Premium Features:
2610
+ - Animated logo with gradient colors
2611
+ - Progress bars and spinners
2612
+ - Auto-update checker
2613
+ - Backup before clean
2614
+ - Cleanup history with ASCII charts
2615
+ - Disk analyzer with visual tree
2616
+ - Smart recommendations
2617
+ - HTML report export
2618
+ - Benchmark mode
2619
+ - Privacy sweep
2620
+ - Memory optimizer
2621
+ - Scheduled cleanups (launchd)
2622
+ - Sound notifications
2623
+ - Dry-run and secure delete modes
1407
2624
  `);
1408
2625
  process.exit(0);
2626
+ }
2627
+
2628
+ if (args.includes('--quick') || args.includes('-q')) {
2629
+ (async () => {
2630
+ getSystemStats();
2631
+ await quickClean();
2632
+ })();
1409
2633
  } else {
1410
- mainMenu();
2634
+ // Show animated logo and start menu
2635
+ (async () => {
2636
+ await animateLogo();
2637
+ getSystemStats();
2638
+ mainMenu();
2639
+ })();
1411
2640
  }