aris-mac-cleaner 2.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 +210 -42
  2. package/bin/cli.js +2598 -205
  3. package/package.json +28 -4
package/bin/cli.js CHANGED
@@ -1,34 +1,456 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync, spawnSync } = require('child_process');
3
+ // ============================================================================
4
+ // ARIS MAC CLEANER v3.5 PREMIUM
5
+ // por Salvador Reis
6
+ // ============================================================================
7
+
8
+ const { execSync, spawnSync, spawn } = require('child_process');
4
9
  const os = require('os');
5
10
  const fs = require('fs');
6
11
  const path = require('path');
12
+ const readline = require('readline');
13
+ const crypto = require('crypto');
14
+ const https = require('https');
7
15
 
8
- // Cores
9
- const colors = {
10
- reset: '\x1b[0m',
11
- red: '\x1b[31m',
12
- green: '\x1b[32m',
13
- yellow: '\x1b[33m',
14
- blue: '\x1b[34m',
15
- cyan: '\x1b[36m',
16
- white: '\x1b[37m',
17
- bold: '\x1b[1m',
18
- dim: '\x1b[2m'
19
- };
16
+ // Premium dependencies (with fallbacks)
17
+ let chalk, gradient, ora, cliProgress, asciichart, notifier, boxen, figures;
20
18
 
21
- const c = colors;
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
+ }
22
39
 
23
- // Verificar se é macOS
24
- if (os.platform() !== 'darwin') {
25
- console.log(`${c.red}Este programa funciona em macOS${c.reset}`);
26
- process.exit(1);
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(', ') };
27
82
  }
28
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
+ }
116
+
117
+ // ============================================================================
118
+ // CONSTANTS
119
+ // ============================================================================
120
+
121
+ const VERSION = '3.5.0';
122
+ const PACKAGE_NAME = 'aris-mac-cleaner';
29
123
  const HOME = os.homedir();
124
+ const CONFIG_DIR = path.join(HOME, '.aris-mac-cleaner');
125
+ const HISTORY_FILE = path.join(CONFIG_DIR, 'history.log');
126
+ const EXCLUSIONS_FILE = path.join(CONFIG_DIR, 'exclusions.conf');
127
+ const CUSTOM_PATHS_FILE = path.join(CONFIG_DIR, 'custom-paths.conf');
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;
148
+
149
+ // ============================================================================
150
+ // MULTI-LANGUAGE
151
+ // ============================================================================
152
+
153
+ let LANG_CODE = 'pt';
154
+ if (fs.existsSync(LANGUAGE_FILE)) {
155
+ LANG_CODE = fs.readFileSync(LANGUAGE_FILE, 'utf8').trim();
156
+ }
157
+
158
+ const MSG = LANG_CODE === 'pt' ? {
159
+ WELCOME: 'Bem-vindo ao Aris Mac Cleaner',
160
+ MENU_TITLE: 'MENU PRINCIPAL',
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',
166
+ OPT_BROWSERS: 'Limpar Browsers',
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',
176
+ OPT_EXIT: 'Sair',
177
+ CHOOSE: 'Escolha',
178
+ INVALID: 'Opcao invalida',
179
+ PRESS_ENTER: 'Pressiona ENTER para continuar',
180
+ SCANNING: 'A analisar...',
181
+ FOUND: 'Encontrado',
182
+ TOTAL: 'Total',
183
+ DELETE: 'Apagar',
184
+ KEEP: 'Manter',
185
+ CANCEL: 'Cancelar',
186
+ CONFIRM: 'Confirmar',
187
+ FREED: 'Libertado',
188
+ AVAILABLE: 'Disponivel',
189
+ HEALTH_SCORE: 'Health Score',
190
+ EXCELLENT: 'Excelente',
191
+ GOOD: 'Bom',
192
+ ATTENTION: 'Atencao',
193
+ CRITICAL: 'Critico',
194
+ DIAGNOSE: 'DIAGNOSTICO DO SISTEMA',
195
+ DISK: 'Disco',
196
+ RAM: 'RAM',
197
+ SWAP: 'Swap',
198
+ CPU: 'CPU',
199
+ IN_USE: 'em uso',
200
+ FREE_OF: 'livres de',
201
+ SELECT_DELETE: 'Seleciona para apagar',
202
+ SELECT_KEEP: 'Seleciona para manter',
203
+ ALL: 'todos',
204
+ NONE: 'nenhum',
205
+ BACK: 'Voltar',
206
+ FINAL_REPORT: 'RELATORIO FINAL',
207
+ READY: 'Pronto para trabalhar!',
208
+ RESTART_REC: 'Recomendacao: Reinicia o Mac para limpar memoria',
209
+ NO_ITEMS: 'Nada encontrado',
210
+ CLEANING: 'A limpar...',
211
+ CATEGORIES: 'CATEGORIAS',
212
+ SYSTEM_CACHES: 'Caches do Sistema',
213
+ DEV_TOOLS: 'Ferramentas de Dev',
214
+ BROWSERS_CAT: 'Browsers',
215
+ DOWNLOADS_CAT: 'Downloads',
216
+ APPS_CAT: 'Aplicacoes',
217
+ PROCESS_MEM: 'Por Memoria',
218
+ PROCESS_CPU: 'Por CPU',
219
+ KILL: 'Matar',
220
+ STARTUP_ITEMS: 'Items de Arranque',
221
+ LAUNCH_AGENTS: 'Launch Agents',
222
+ ENABLED: 'Ativo',
223
+ DISABLED: 'Desativado',
224
+ TOGGLE: 'Alternar',
225
+ DISABLE_ALL: 'Desativar todos',
226
+ THIS_MONTH: 'Este mes',
227
+ CLEANINGS: 'Limpezas',
228
+ AVG_SCORE: 'Score medio',
229
+ HISTORY: 'Historico',
230
+ EXCLUSIONS: 'Gerir Exclusoes',
231
+ CUSTOM_PATHS: 'Caminhos Personalizados',
232
+ CHANGE_LANG: 'Mudar Idioma',
233
+ RESET_DEFAULTS: 'Repor Predefinicoes',
234
+ PREVIEW_MODE: 'Modo Preview (nao apaga nada)',
235
+ LARGE_FILES: 'FICHEIROS GRANDES',
236
+ DUPLICATES: 'FICHEIROS DUPLICADOS',
237
+ UNINSTALL: 'DESINSTALAR APLICACOES',
238
+ APP_SIZE: 'app',
239
+ DATA_SIZE: 'dados',
240
+ CURRENT_EXCLUSIONS: 'Exclusoes atuais',
241
+ CURRENT_PATHS: 'Caminhos personalizados atuais',
242
+ ENTER_PATH: 'Introduz caminho (c=limpar, n=voltar)',
243
+ PATH: 'Caminho',
244
+ CLEARED: 'Limpo',
245
+ ADDED: 'Adicionado',
246
+ LANG_CHANGED_EN: 'Language changed to English',
247
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
248
+ SETTINGS_RESET: 'Configuracoes repostas',
249
+ REMOVED: 'Removido',
250
+ NEED_SUDO: 'Precisa sudo para remover app. Executa:',
251
+ DELETING: 'A apagar',
252
+ KILLED: 'Morto',
253
+ FAILED_KILL: 'Falhou ao matar',
254
+ NO_HISTORY: 'Sem historico. Faz algumas limpezas primeiro!',
255
+ FEATURE_PROGRESS: 'Funcionalidade em progresso...',
256
+ GROUP: 'Grupo',
257
+ EACH: 'cada',
258
+ IN_DUPLICATES: 'em duplicados',
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!'
276
+ } : {
277
+ WELCOME: 'Welcome to Aris Mac Cleaner',
278
+ MENU_TITLE: 'MAIN MENU',
279
+ OPT_QUICK: 'Quick Clean',
280
+ OPT_LARGE: 'Large Files',
281
+ OPT_DUPLICATES: 'Duplicates',
282
+ OPT_APPS: 'Uninstall Apps',
283
+ OPT_STARTUP: 'Startup Apps',
284
+ OPT_BROWSERS: 'Clean Browsers',
285
+ OPT_PROCESSES: 'Heavy Processes',
286
+ OPT_STATS: 'Statistics',
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',
294
+ OPT_EXIT: 'Exit',
295
+ CHOOSE: 'Choose',
296
+ INVALID: 'Invalid option',
297
+ PRESS_ENTER: 'Press ENTER to continue',
298
+ SCANNING: 'Scanning...',
299
+ FOUND: 'Found',
300
+ TOTAL: 'Total',
301
+ DELETE: 'Delete',
302
+ KEEP: 'Keep',
303
+ CANCEL: 'Cancel',
304
+ CONFIRM: 'Confirm',
305
+ FREED: 'Freed',
306
+ AVAILABLE: 'Available',
307
+ HEALTH_SCORE: 'Health Score',
308
+ EXCELLENT: 'Excellent',
309
+ GOOD: 'Good',
310
+ ATTENTION: 'Warning',
311
+ CRITICAL: 'Critical',
312
+ DIAGNOSE: 'SYSTEM DIAGNOSTICS',
313
+ DISK: 'Disk',
314
+ RAM: 'RAM',
315
+ SWAP: 'Swap',
316
+ CPU: 'CPU',
317
+ IN_USE: 'in use',
318
+ FREE_OF: 'free of',
319
+ SELECT_DELETE: 'Select to delete',
320
+ SELECT_KEEP: 'Select to keep',
321
+ ALL: 'all',
322
+ NONE: 'none',
323
+ BACK: 'Back',
324
+ FINAL_REPORT: 'FINAL REPORT',
325
+ READY: 'Ready to work!',
326
+ RESTART_REC: 'Recommendation: Restart Mac to clear memory',
327
+ NO_ITEMS: 'Nothing found',
328
+ CLEANING: 'Cleaning...',
329
+ CATEGORIES: 'CATEGORIES',
330
+ SYSTEM_CACHES: 'System Caches',
331
+ DEV_TOOLS: 'Dev Tools',
332
+ BROWSERS_CAT: 'Browsers',
333
+ DOWNLOADS_CAT: 'Downloads',
334
+ APPS_CAT: 'Applications',
335
+ PROCESS_MEM: 'By Memory',
336
+ PROCESS_CPU: 'By CPU',
337
+ KILL: 'Kill',
338
+ STARTUP_ITEMS: 'Login Items',
339
+ LAUNCH_AGENTS: 'Launch Agents',
340
+ ENABLED: 'Enabled',
341
+ DISABLED: 'Disabled',
342
+ TOGGLE: 'Toggle',
343
+ DISABLE_ALL: 'Disable all',
344
+ THIS_MONTH: 'This month',
345
+ CLEANINGS: 'Cleanings',
346
+ AVG_SCORE: 'Avg score',
347
+ HISTORY: 'History',
348
+ EXCLUSIONS: 'Manage Exclusions',
349
+ CUSTOM_PATHS: 'Custom Paths',
350
+ CHANGE_LANG: 'Change Language',
351
+ RESET_DEFAULTS: 'Reset Defaults',
352
+ PREVIEW_MODE: 'Preview Mode (doesn\'t delete)',
353
+ LARGE_FILES: 'LARGE FILES',
354
+ DUPLICATES: 'DUPLICATE FILES',
355
+ UNINSTALL: 'UNINSTALL APPLICATIONS',
356
+ APP_SIZE: 'app',
357
+ DATA_SIZE: 'data',
358
+ CURRENT_EXCLUSIONS: 'Current exclusions',
359
+ CURRENT_PATHS: 'Current custom paths',
360
+ ENTER_PATH: 'Enter path (c=clear, n=back)',
361
+ PATH: 'Path',
362
+ CLEARED: 'Cleared',
363
+ ADDED: 'Added',
364
+ LANG_CHANGED_EN: 'Language changed to English',
365
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
366
+ SETTINGS_RESET: 'Settings reset to defaults',
367
+ REMOVED: 'Removed',
368
+ NEED_SUDO: 'Need sudo to remove app. Run:',
369
+ DELETING: 'Deleting',
370
+ KILLED: 'Killed',
371
+ FAILED_KILL: 'Failed to kill',
372
+ NO_HISTORY: 'No history yet. Run some cleanups first!',
373
+ FEATURE_PROGRESS: 'Feature in progress...',
374
+ GROUP: 'Group',
375
+ EACH: 'each',
376
+ IN_DUPLICATES: 'in duplicates',
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!'
394
+ };
395
+
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
+ // ============================================================================
451
+ // UTILITY FUNCTIONS
452
+ // ============================================================================
30
453
 
31
- // Função para executar comandos
32
454
  function exec(cmd) {
33
455
  try {
34
456
  return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
@@ -37,211 +459,2182 @@ function exec(cmd) {
37
459
  }
38
460
  }
39
461
 
40
- // Função para obter tamanho de pasta
41
- function getSize(path) {
462
+ function getSize(p) {
463
+ const fullPath = p.replace(/^~/, HOME);
42
464
  try {
43
- const result = exec(`du -sm "${path}" 2>/dev/null`);
465
+ if (!fs.existsSync(fullPath)) return 0;
466
+ const result = exec(`du -sm "${fullPath}" 2>/dev/null`);
44
467
  return parseInt(result.split('\t')[0]) || 0;
45
468
  } catch (e) {
46
469
  return 0;
47
470
  }
48
471
  }
49
472
 
50
- // Função para limpar pasta
51
- function cleanPath(p, name) {
52
- const fullPath = p.replace('~', HOME);
53
- if (fs.existsSync(fullPath)) {
473
+ function formatSize(sizeMB) {
474
+ if (sizeMB >= 1024) {
475
+ return `${(sizeMB / 1024).toFixed(1)} GB`;
476
+ }
477
+ return `${sizeMB} MB`;
478
+ }
479
+
480
+ function isExcluded(p) {
481
+ if (!fs.existsSync(EXCLUSIONS_FILE)) return false;
482
+ const exclusions = fs.readFileSync(EXCLUSIONS_FILE, 'utf8').split('\n').filter(l => l.trim());
483
+ return exclusions.some(ex => p.includes(ex));
484
+ }
485
+
486
+ function logCleanup(freed, score) {
487
+ const line = `${new Date().toISOString().slice(0, 16).replace('T', ' ')} | ${MSG.FREED}: ${freed} MB | Score: ${score}/100\n`;
488
+ fs.appendFileSync(HISTORY_FILE, line);
489
+ }
490
+
491
+ function clearScreen() {
492
+ process.stdout.write('\x1b[2J\x1b[H');
493
+ }
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
+
517
+ function header() {
518
+ clearScreen();
519
+ const date = new Date().toLocaleDateString('en-GB', {
520
+ weekday: 'long', day: '2-digit', month: 'long', year: 'numeric'
521
+ });
522
+ const time = new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
523
+
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
+ }
542
+ }
543
+
544
+ function sectionHeader(title) {
545
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
546
+ console.log(chalk.white('|') + ' ' + chalk.cyan(figures.pointer + ' ' + title));
547
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
548
+ console.log();
549
+ }
550
+
551
+ // ============================================================================
552
+ // AUTO-UPDATE CHECKER
553
+ // ============================================================================
554
+
555
+ async function checkForUpdates() {
556
+ return new Promise((resolve) => {
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
+ });
573
+ });
574
+
575
+ req.on('error', () => resolve({ hasUpdate: false }));
576
+ req.on('timeout', () => {
577
+ req.destroy();
578
+ resolve({ hasUpdate: false });
579
+ });
580
+ });
581
+ }
582
+
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
+ }
591
+ }
592
+
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
+ }
615
+
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
+ // ============================================================================
707
+
708
+ function getSystemStats() {
709
+ // Disk
710
+ const diskInfo = exec("df -h / | tail -1").split(/\s+/);
711
+ const diskRaw = exec("df / | tail -1").split(/\s+/);
712
+ systemStats.diskTotal = diskInfo[1]?.replace('Gi', '') || '0';
713
+ systemStats.diskFree = diskInfo[3]?.replace('Gi', '') || '0';
714
+ systemStats.diskPercent = parseInt(diskInfo[4]) || 0;
715
+ systemStats.diskFreeBlocks = parseInt(diskRaw[3]) || 0;
716
+
717
+ if (systemStats.diskPercent < 50) {
718
+ systemStats.diskStatus = chalk.green(`${figures.tick} ${MSG.EXCELLENT}`);
719
+ } else if (systemStats.diskPercent < 80) {
720
+ systemStats.diskStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
721
+ } else {
722
+ systemStats.diskStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
723
+ }
724
+
725
+ // RAM
726
+ const ramFreePercent = parseInt(exec("memory_pressure 2>/dev/null | grep 'System-wide' | awk '{print $5}'")) || 50;
727
+ systemStats.ramUsedPercent = 100 - ramFreePercent;
728
+
729
+ if (systemStats.ramUsedPercent < 70) {
730
+ systemStats.ramStatus = chalk.green(`${figures.tick} OK`);
731
+ } else if (systemStats.ramUsedPercent < 90) {
732
+ systemStats.ramStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
733
+ } else {
734
+ systemStats.ramStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
735
+ }
736
+
737
+ // Swap
738
+ const swapStr = exec("sysctl vm.swapusage 2>/dev/null | awk '{print $7}'");
739
+ systemStats.swapUsed = parseInt(swapStr) || 0;
740
+
741
+ if (systemStats.swapUsed < 2000) {
742
+ systemStats.swapStatus = chalk.green(`${figures.tick} OK`);
743
+ } else if (systemStats.swapUsed < 8000) {
744
+ systemStats.swapStatus = chalk.yellow(`${figures.warning} High`);
745
+ } else {
746
+ systemStats.swapStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
747
+ }
748
+
749
+ // CPU
750
+ const cpuIdle = parseFloat(exec("top -l 1 2>/dev/null | grep 'CPU usage' | awk '{print $7}'")) || 50;
751
+ systemStats.cpuUsed = Math.round(100 - cpuIdle);
752
+
753
+ if (systemStats.cpuUsed < 50) {
754
+ systemStats.cpuStatus = chalk.green(`${figures.tick} OK`);
755
+ } else if (systemStats.cpuUsed < 80) {
756
+ systemStats.cpuStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
757
+ } else {
758
+ systemStats.cpuStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
759
+ }
760
+ }
761
+
762
+ function showDiagnostics() {
763
+ sectionHeader(MSG.DIAGNOSE);
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}`);
768
+ console.log();
769
+ }
770
+
771
+ function calculateHealthScore() {
772
+ let score = 100;
773
+ if (systemStats.diskPercent > 80) score -= 30;
774
+ else if (systemStats.diskPercent > 60) score -= 10;
775
+ if (systemStats.ramUsedPercent > 90) score -= 30;
776
+ else if (systemStats.ramUsedPercent > 70) score -= 10;
777
+ if (systemStats.swapUsed > 8000) score -= 20;
778
+ else if (systemStats.swapUsed > 4000) score -= 10;
779
+
780
+ let color = chalk.green;
781
+ let text = MSG.EXCELLENT;
782
+ if (score < 60) { color = chalk.red; text = MSG.ATTENTION; }
783
+ else if (score < 80) { color = chalk.yellow; text = MSG.GOOD; }
784
+
785
+ return { score, color, text };
786
+ }
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
+
910
+ function showFinalReport(freed) {
911
+ const diskFreeAfter = exec("df -h / | tail -1").split(/\s+/)[3];
912
+ const health = calculateHealthScore();
913
+
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));
930
+
931
+ if (systemStats.swapUsed > 5000 || systemStats.ramUsedPercent > 85) {
932
+ console.log(chalk.yellow(`${figures.warning} ${MSG.RESTART_REC}`));
933
+ console.log();
934
+ }
935
+
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
+ };
946
+
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
+
54
1129
  try {
55
- execSync(`rm -rf "${fullPath}"`, { stdio: 'ignore' });
56
- console.log(` ${c.green}✓${c.reset} ${name}`);
57
- return true;
1130
+ execSync('sudo purge', { stdio: 'inherit' });
1131
+ spinner.succeed(chalk.green(MSG.MEMORY_FREED));
58
1132
  } catch (e) {
59
- return false;
60
- }
61
- }
62
- return false;
63
- }
64
-
65
- // Limpar consola
66
- console.clear();
67
-
68
- // Header
69
- console.log();
70
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
71
- console.log(`${c.cyan}║${c.reset}${c.bold}${c.white} ARIS MAC CLEANER v2.0 ${c.reset}${c.cyan}║${c.reset}`);
72
- console.log(`${c.cyan}║${c.reset}${c.dim} por Salvador Reis ${c.reset}${c.cyan}║${c.reset}`);
73
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
74
- console.log();
75
-
76
- // FASE 1: DIAGNÓSTICO
77
- console.log(`${c.white}┌─────────────────────────────────────────────────────────────────┐${c.reset}`);
78
- console.log(`${c.white}│${c.reset} ${c.blue}▶ FASE 1: DIAGNÓSTICO DO SISTEMA${c.reset} ${c.white}│${c.reset}`);
79
- console.log(`${c.white}└─────────────────────────────────────────────────────────────────┘${c.reset}`);
80
- console.log();
81
-
82
- // Disco
83
- const diskInfo = exec("df -h / | tail -1").split(/\s+/);
84
- const diskTotal = diskInfo[1];
85
- const diskUsed = diskInfo[2];
86
- const diskFree = diskInfo[3];
87
- const diskPercent = parseInt(diskInfo[4]);
88
- const diskFreeBefore = parseInt(exec("df / | tail -1").split(/\s+/)[3]);
89
-
90
- let diskStatus = `${c.green}✓ Excelente${c.reset}`;
91
- if (diskPercent >= 80) diskStatus = `${c.red}✗ Crítico${c.reset}`;
92
- else if (diskPercent >= 50) diskStatus = `${c.yellow}! Atenção${c.reset}`;
93
-
94
- // RAM
95
- const ramFreePercent = parseInt(exec("memory_pressure 2>/dev/null | grep 'System-wide' | awk '{print $5}'")) || 50;
96
- const ramUsedPercent = 100 - ramFreePercent;
97
-
98
- let ramStatus = `${c.green}✓ OK${c.reset}`;
99
- if (ramUsedPercent >= 90) ramStatus = `${c.red}✗ Crítico${c.reset}`;
100
- else if (ramUsedPercent >= 70) ramStatus = `${c.yellow}! Pressão${c.reset}`;
101
-
102
- // Swap
103
- const swapUsed = parseInt(exec("sysctl vm.swapusage 2>/dev/null | awk '{print int($7)}'")) || 0;
104
-
105
- let swapStatus = `${c.green}✓ OK${c.reset}`;
106
- if (swapUsed >= 8000) swapStatus = `${c.red}✗ Crítico${c.reset}`;
107
- else if (swapUsed >= 2000) swapStatus = `${c.yellow}! Alto${c.reset}`;
108
-
109
- // CPU
110
- const cpuIdle = parseFloat(exec("top -l 1 | grep 'CPU usage' | awk '{print $7}'")) || 50;
111
- const cpuUsed = Math.round(100 - cpuIdle);
112
-
113
- let cpuStatus = `${c.green}✓ OK${c.reset}`;
114
- if (cpuUsed >= 80) cpuStatus = `${c.red}✗ Sobrecarregado${c.reset}`;
115
- else if (cpuUsed >= 50) cpuStatus = `${c.yellow}! Ocupado${c.reset}`;
116
-
117
- console.log(` • ${c.bold}Disco${c.reset} ${diskFree} livres de ${diskTotal} ${diskStatus}`);
118
- console.log(` • ${c.bold}RAM${c.reset} ${ramUsedPercent}% em uso ${ramStatus}`);
119
- console.log(` • ${c.bold}Swap${c.reset} ${swapUsed}MB em uso ${swapStatus}`);
120
- console.log(` • ${c.bold}CPU${c.reset} ${cpuUsed}% em uso ${cpuStatus}`);
121
- console.log();
122
-
123
- // FASE 2: ANÁLISE
124
- console.log(`${c.white}┌─────────────────────────────────────────────────────────────────┐${c.reset}`);
125
- console.log(`${c.white}│${c.reset} ${c.blue}▶ FASE 2: ANÁLISE DE LIMPEZA${c.reset} ${c.white}│${c.reset}`);
126
- console.log(`${c.white}└─────────────────────────────────────────────────────────────────┘${c.reset}`);
127
- console.log();
128
-
129
- const cleanPaths = [
130
- { path: `${HOME}/Library/Caches/Google`, name: 'Google Cache' },
131
- { path: `${HOME}/Library/Caches/com.spotify.client`, name: 'Spotify Cache' },
132
- { path: `${HOME}/Library/Caches/CocoaPods`, name: 'CocoaPods Cache' },
133
- { path: `${HOME}/Library/Caches/Homebrew`, name: 'Homebrew Cache' },
134
- { path: `${HOME}/Library/Caches/typescript`, name: 'TypeScript Cache' },
135
- { path: `${HOME}/Library/Caches/node-gyp`, name: 'Node-gyp Cache' },
136
- { path: `${HOME}/Library/Caches/pip`, name: 'Pip Cache' },
137
- { path: `${HOME}/Library/Caches/yarn`, name: 'Yarn Cache' },
138
- { path: `${HOME}/Library/Caches/pnpm`, name: 'pnpm Cache' },
139
- { path: `${HOME}/.npm`, name: 'npm Cache' },
140
- { path: `${HOME}/Library/Application Support/Code/User/workspaceStorage`, name: 'VS Code Storage' },
141
- { path: `${HOME}/Library/Developer/Xcode/DerivedData`, name: 'Xcode DerivedData' },
142
- { path: `${HOME}/Library/Application Support/Google/GoogleUpdater/crx_cache`, name: 'Google Updater' },
143
- { path: `${HOME}/Library/Application Support/Google/Chrome/component_crx_cache`, name: 'Chrome Components' },
144
- ];
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();
1145
+ }
1146
+
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;
145
1172
 
146
- let totalClean = 0;
1173
+ exec('rm /tmp/benchmark_test');
1174
+ spinner1.succeed(`Disk: Write ${Math.round(100000 / diskWrite)} MB/s, Read ${Math.round(100000 / diskRead)} MB/s`);
147
1175
 
148
- cleanPaths.forEach(item => {
149
- const size = getSize(item.path);
150
- if (size > 0) {
151
- totalClean += size;
152
- console.log(` • ${item.name.padEnd(25)} ${c.dim}${String(size).padStart(6)} MB${c.reset}`);
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);
153
1193
  }
154
- });
1194
+ const cpuTime = Date.now() - cpuStart;
1195
+ spinner2.succeed(`CPU: ${cpuTime}ms for 10M sqrt operations`);
155
1196
 
156
- console.log();
157
- console.log(` → ${c.bold}Total a libertar:${c.reset} ${c.green}~${Math.round(totalClean / 1024)} GB${c.reset} ${c.dim}(${totalClean} MB)${c.reset}`);
158
- console.log();
1197
+ results.tests.push({
1198
+ name: 'CPU (10M sqrt)',
1199
+ value: cpuTime,
1200
+ unit: 'ms'
1201
+ });
159
1202
 
160
- // FASE 3: LIMPEZA
161
- console.log(`${c.white}┌─────────────────────────────────────────────────────────────────┐${c.reset}`);
162
- console.log(`${c.white}│${c.reset} ${c.blue}▶ FASE 3: LIMPEZA EM PROGRESSO${c.reset} ${c.white}│${c.reset}`);
163
- console.log(`${c.white}└─────────────────────────────────────────────────────────────────┘${c.reset}`);
164
- console.log();
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`);
165
1212
 
166
- // npm cache
167
- try {
168
- execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
169
- console.log(` ${c.green}✓${c.reset} npm cache`);
170
- } catch (e) {}
1213
+ results.tests.push({
1214
+ name: 'Memory (100MB alloc)',
1215
+ value: memTime,
1216
+ unit: 'ms'
1217
+ });
171
1218
 
172
- // Limpar paths
173
- cleanPaths.forEach(item => {
174
- cleanPath(item.path, item.name);
175
- });
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);
176
1222
 
177
- // Claude versões antigas
178
- const claudeVersionsPath = `${HOME}/.local/share/claude/versions`;
179
- if (fs.existsSync(claudeVersionsPath)) {
180
- try {
181
- const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
182
- if (versions.length > 1) {
183
- versions.slice(1).forEach(v => {
184
- fs.rmSync(path.join(claudeVersionsPath, v), { recursive: true, force: true });
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}`));
185
1234
  });
186
- console.log(` ${c.green}✓${c.reset} Claude (${versions.length - 1} versões antigas)`);
187
1235
  }
188
- } catch (e) {}
1236
+ }
1237
+
1238
+ await pressEnter();
189
1239
  }
190
1240
 
191
- // Downloads antigos
192
- try {
193
- execSync('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null', { stdio: 'ignore' });
194
- execSync('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null', { stdio: 'ignore' });
195
- } catch (e) {}
1241
+ // ============================================================================
1242
+ // SCHEDULER (LAUNCHD)
1243
+ // ============================================================================
196
1244
 
197
- // Logs
198
- try {
199
- execSync('rm -rf ~/Library/Logs/*.log 2>/dev/null', { stdio: 'ignore' });
200
- console.log(` ${c.green}✓${c.reset} Logs do sistema`);
201
- } catch (e) {}
202
-
203
- console.log();
204
-
205
- // FASE 4: RESULTADO
206
- const diskFreeAfter = parseInt(exec("df / | tail -1").split(/\s+/)[3]);
207
- const freed = Math.round((diskFreeAfter - diskFreeBefore) / 1024 / 1024);
208
- const diskFreeGBAfter = exec("df -h / | tail -1").split(/\s+/)[3];
209
- const diskPercentAfter = parseInt(exec("df / | tail -1").split(/\s+/)[4]);
210
-
211
- // Health Score
212
- let healthScore = 100;
213
- if (diskPercentAfter > 80) healthScore -= 30;
214
- else if (diskPercentAfter > 60) healthScore -= 10;
215
- if (ramUsedPercent > 90) healthScore -= 30;
216
- else if (ramUsedPercent > 70) healthScore -= 10;
217
- if (swapUsed > 8000) healthScore -= 20;
218
- else if (swapUsed > 4000) healthScore -= 10;
219
-
220
- let healthColor = c.green;
221
- let healthText = 'Excelente';
222
- if (healthScore < 60) { healthColor = c.red; healthText = 'Atenção'; }
223
- else if (healthScore < 80) { healthColor = c.yellow; healthText = 'Bom'; }
224
-
225
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
226
- console.log(`${c.cyan}║${c.reset}${c.bold}${c.white} RELATÓRIO FINAL ${c.reset}${c.cyan}║${c.reset}`);
227
- console.log(`${c.cyan}╠══════════════════════════════════════════════════════════════════╣${c.reset}`);
228
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
229
- console.log(`${c.cyan}║${c.reset} ${c.bold}Disco${c.reset} ${c.cyan}║${c.reset}`);
230
- console.log(`${c.cyan}║${c.reset} → Disponível: ${c.green}${diskFreeGBAfter}${c.reset} ${c.cyan}║${c.reset}`);
231
- if (freed > 0) {
232
- console.log(`${c.cyan}║${c.reset} → Libertado: ${c.green}+${freed} GB${c.reset} ${c.cyan}║${c.reset}`);
233
- }
234
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
235
- console.log(`${c.cyan}║${c.reset} ${c.bold}Health Score${c.reset} ${c.cyan}║${c.reset}`);
236
- console.log(`${c.cyan}║${c.reset} → ${healthColor}${healthScore}/100 - ${healthText}${c.reset} ${c.cyan}║${c.reset}`);
237
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
238
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
239
- console.log();
240
-
241
- if (swapUsed > 5000 || ramUsedPercent > 85) {
242
- console.log(`${c.yellow}⚠ Recomendação: Reinicia o Mac para limpar memória${c.reset}`);
243
- console.log();
244
- }
245
-
246
- console.log(`${c.green}✓ Pronto para trabalhar!${c.reset}`);
247
- console.log();
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
+ // ============================================================================
1602
+
1603
+ async function quickClean() {
1604
+ header();
1605
+ getSystemStats();
1606
+ showDiagnostics();
1607
+ showRecommendations();
1608
+ sectionHeader(MSG.CATEGORIES);
1609
+
1610
+ const items = [];
1611
+ let totalSize = 0;
1612
+
1613
+ function addItem(p, name, category) {
1614
+ const fullPath = p.replace(/^~/, HOME);
1615
+ if (isExcluded(fullPath)) return;
1616
+
1617
+ const size = getSize(p);
1618
+ if (size > 0) {
1619
+ items.push({ path: fullPath, name, category, size, selected: true });
1620
+ totalSize += size;
1621
+ }
1622
+ }
1623
+
1624
+ const spinner = ora(MSG.SCANNING).start();
1625
+
1626
+ // System Caches
1627
+ addItem('~/Library/Caches/Google', 'Google/Chrome Cache', 'system');
1628
+ addItem('~/Library/Caches/com.spotify.client', 'Spotify Cache', 'system');
1629
+ addItem('~/Library/Caches/com.apple.Safari', 'Safari Cache', 'system');
1630
+ addItem('~/Library/Logs', 'System Logs', 'system');
1631
+
1632
+ // Dev Tools
1633
+ addItem('~/Library/Caches/CocoaPods', 'CocoaPods Cache', 'dev');
1634
+ addItem('~/Library/Caches/Homebrew', 'Homebrew Cache', 'dev');
1635
+ addItem('~/Library/Caches/typescript', 'TypeScript Cache', 'dev');
1636
+ addItem('~/Library/Caches/node-gyp', 'Node-gyp Cache', 'dev');
1637
+ addItem('~/Library/Caches/pip', 'Pip Cache', 'dev');
1638
+ addItem('~/Library/Caches/yarn', 'Yarn Cache', 'dev');
1639
+ addItem('~/Library/Caches/pnpm', 'pnpm Cache', 'dev');
1640
+ addItem('~/.npm', 'npm Cache', 'dev');
1641
+ addItem('~/Library/Application Support/Code/User/workspaceStorage', 'VS Code Workspace', 'dev');
1642
+ addItem('~/Library/Developer/Xcode/DerivedData', 'Xcode DerivedData', 'dev');
1643
+
1644
+ // Browsers
1645
+ addItem('~/Library/Application Support/Google/GoogleUpdater/crx_cache', 'Google Updater', 'browsers');
1646
+ addItem('~/Library/Application Support/Google/Chrome/component_crx_cache', 'Chrome Components', 'browsers');
1647
+
1648
+ // Claude versions
1649
+ const claudeVersionsPath = path.join(HOME, '.local/share/claude/versions');
1650
+ if (fs.existsSync(claudeVersionsPath)) {
1651
+ try {
1652
+ const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
1653
+ if (versions.length > 1) {
1654
+ let claudeSize = 0;
1655
+ versions.slice(1).forEach(v => {
1656
+ claudeSize += getSize(path.join(claudeVersionsPath, v));
1657
+ });
1658
+ if (claudeSize > 0) {
1659
+ items.push({
1660
+ path: 'CLAUDE_VERSIONS',
1661
+ name: `Claude (${versions.length - 1} old versions)`,
1662
+ category: 'apps',
1663
+ size: claudeSize,
1664
+ selected: true
1665
+ });
1666
+ totalSize += claudeSize;
1667
+ }
1668
+ }
1669
+ } catch (e) {}
1670
+ }
1671
+
1672
+ // Old Downloads
1673
+ const dmgCount = parseInt(exec('find ~/Downloads -name "*.dmg" -mtime +7 2>/dev/null | wc -l')) || 0;
1674
+ const zipCount = parseInt(exec('find ~/Downloads -name "*.zip" -mtime +30 2>/dev/null | wc -l')) || 0;
1675
+ if (dmgCount > 0 || zipCount > 0) {
1676
+ const dlSize = parseInt(exec('find ~/Downloads \\( -name "*.dmg" -mtime +7 -o -name "*.zip" -mtime +30 \\) -exec du -sm {} \\; 2>/dev/null | awk \'{sum+=$1} END {print sum}\'')) || 0;
1677
+ if (dlSize > 0) {
1678
+ items.push({
1679
+ path: 'OLD_DOWNLOADS',
1680
+ name: `Old Downloads (${dmgCount} dmg, ${zipCount} zip)`,
1681
+ category: 'downloads',
1682
+ size: dlSize,
1683
+ selected: true
1684
+ });
1685
+ totalSize += dlSize;
1686
+ }
1687
+ }
1688
+
1689
+ // Custom paths
1690
+ if (fs.existsSync(CUSTOM_PATHS_FILE)) {
1691
+ const customPaths = fs.readFileSync(CUSTOM_PATHS_FILE, 'utf8').split('\n').filter(l => l.trim() && !l.startsWith('#'));
1692
+ customPaths.forEach(cp => {
1693
+ addItem(cp, `Custom: ${path.basename(cp)}`, 'custom');
1694
+ });
1695
+ }
1696
+
1697
+ spinner.stop();
1698
+
1699
+ if (items.length === 0) {
1700
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1701
+ await pressEnter();
1702
+ return;
1703
+ }
1704
+
1705
+ // Show by category
1706
+ function showCategory(catId, catName) {
1707
+ const catItems = items.filter(i => i.category === catId);
1708
+ if (catItems.length === 0) return;
1709
+
1710
+ const catSize = catItems.reduce((sum, i) => sum + i.size, 0);
1711
+ console.log(` ${chalk.white(' ' + catName)} ${chalk.dim('(' + formatSize(catSize) + ')')}`);
1712
+
1713
+ catItems.forEach(item => {
1714
+ const idx = items.indexOf(item) + 1;
1715
+ console.log(` ${chalk.cyan('[' + String(idx).padStart(2) + ']')} ${item.name.padEnd(35)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
1716
+ });
1717
+ console.log();
1718
+ }
1719
+
1720
+ showCategory('system', MSG.SYSTEM_CACHES);
1721
+ showCategory('dev', MSG.DEV_TOOLS);
1722
+ showCategory('browsers', MSG.BROWSERS_CAT);
1723
+ showCategory('downloads', MSG.DOWNLOADS_CAT);
1724
+ showCategory('apps', MSG.APPS_CAT);
1725
+ showCategory('custom', 'Custom');
1726
+
1727
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalSize))}`);
1728
+ console.log();
1729
+
1730
+ // Selection
1731
+ sectionHeader(MSG.SELECT_DELETE);
1732
+ console.log(chalk.dim(` a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10`));
1733
+ console.log();
1734
+
1735
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1736
+
1737
+ if (choice.toLowerCase() === 'n' || choice === '') {
1738
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
1739
+ await pressEnter();
1740
+ return;
1741
+ }
1742
+
1743
+ // Process selection
1744
+ if (choice.toLowerCase() !== 'a') {
1745
+ items.forEach(item => item.selected = false);
1746
+
1747
+ const parts = choice.split(',');
1748
+ parts.forEach(part => {
1749
+ part = part.trim();
1750
+ if (part.includes('-')) {
1751
+ const [start, end] = part.split('-').map(n => parseInt(n));
1752
+ for (let j = start; j <= end; j++) {
1753
+ if (j > 0 && j <= items.length) items[j - 1].selected = true;
1754
+ }
1755
+ } else {
1756
+ const idx = parseInt(part) - 1;
1757
+ if (idx >= 0 && idx < items.length) items[idx].selected = true;
1758
+ }
1759
+ });
1760
+ }
1761
+
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
1771
+ console.log();
1772
+ sectionHeader(MSG.CLEANING);
1773
+
1774
+ const progressBar = createProgressBar(selectedItems.length);
1775
+ progressBar.start(selectedItems.length, 0);
1776
+
1777
+ let cleanedSize = 0;
1778
+ const claudeVersionsPathLocal = path.join(HOME, '.local/share/claude/versions');
1779
+
1780
+ for (let i = 0; i < items.length; i++) {
1781
+ const item = items[i];
1782
+ if (!item.selected) continue;
1783
+
1784
+ if (item.path === 'CLAUDE_VERSIONS') {
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
+ }
1793
+ cleanedSize += item.size;
1794
+ } else if (item.path === 'OLD_DOWNLOADS') {
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
+ }
1799
+ cleanedSize += item.size;
1800
+ } else if (fs.existsSync(item.path)) {
1801
+ if (deleteItem(item.path)) {
1802
+ cleanedSize += item.size;
1803
+ }
1804
+ }
1805
+
1806
+ progressBar.increment();
1807
+ }
1808
+
1809
+ progressBar.stop();
1810
+
1811
+ // npm cache
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
+ });
1824
+
1825
+ console.log();
1826
+ getSystemStats();
1827
+ showFinalReport(cleanedSize);
1828
+ await pressEnter();
1829
+ }
1830
+
1831
+ // ============================================================================
1832
+ // FIND LARGE FILES
1833
+ // ============================================================================
1834
+
1835
+ async function findLargeFiles() {
1836
+ header();
1837
+ sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
1838
+
1839
+ const spinner = ora(MSG.SCANNING).start();
1840
+
1841
+ const files = [];
1842
+
1843
+ const output = exec(`find ~ -type f -size +100M 2>/dev/null | head -50 | xargs -I {} du -m {} 2>/dev/null | sort -rn | head -30`);
1844
+
1845
+ output.split('\n').filter(l => l.trim()).forEach(line => {
1846
+ const match = line.match(/^\s*(\d+)\s+(.+)$/);
1847
+ if (!match) return;
1848
+
1849
+ const size = parseInt(match[1]);
1850
+ const file = match[2];
1851
+
1852
+ // Skip system/protected paths
1853
+ if (file.includes('/Library/Application Support/') ||
1854
+ file.includes('/.Trash/') ||
1855
+ file.includes('/node_modules/')) return;
1856
+
1857
+ files.push({ path: file, size });
1858
+ });
1859
+
1860
+ spinner.stop();
1861
+
1862
+ if (files.length === 0) {
1863
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1864
+ await pressEnter();
1865
+ return;
1866
+ }
1867
+
1868
+ header();
1869
+ sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
1870
+
1871
+ const totalLarge = files.reduce((sum, f) => sum + f.size, 0);
1872
+
1873
+ files.forEach((file, i) => {
1874
+ let displayFile = file.path.replace(HOME, '~');
1875
+ if (displayFile.length > 50) {
1876
+ displayFile = '...' + displayFile.slice(-47);
1877
+ }
1878
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${chalk.dim(String(file.size).padStart(6) + ' MB')} ${displayFile}`);
1879
+ });
1880
+
1881
+ console.log();
1882
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalLarge))} ${chalk.dim('(' + files.length + ' files)')}`);
1883
+ console.log();
1884
+
1885
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
1886
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1887
+
1888
+ if (choice.toLowerCase() === 'n' || choice === '') {
1889
+ await pressEnter();
1890
+ return;
1891
+ }
1892
+
1893
+ const selected = new Array(files.length).fill(false);
1894
+
1895
+ if (choice.toLowerCase() === 'a') {
1896
+ selected.fill(true);
1897
+ } else {
1898
+ const parts = choice.split(',');
1899
+ parts.forEach(part => {
1900
+ part = part.trim();
1901
+ if (part.includes('-')) {
1902
+ const [start, end] = part.split('-').map(n => parseInt(n));
1903
+ for (let j = start; j <= end; j++) {
1904
+ if (j > 0 && j <= files.length) selected[j - 1] = true;
1905
+ }
1906
+ } else {
1907
+ const idx = parseInt(part) - 1;
1908
+ if (idx >= 0 && idx < files.length) selected[idx] = true;
1909
+ }
1910
+ });
1911
+ }
1912
+
1913
+ let deletedSize = 0;
1914
+ console.log();
1915
+
1916
+ for (let i = 0; i < files.length; i++) {
1917
+ if (!selected[i]) continue;
1918
+
1919
+ if (deleteItem(files[i].path)) {
1920
+ console.log(` ${chalk.green(figures.tick)} Deleted: ${path.basename(files[i].path)}`);
1921
+ deletedSize += files[i].size;
1922
+ }
1923
+ }
1924
+
1925
+ console.log();
1926
+ console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(deletedSize))}`);
1927
+
1928
+ playNotificationSound();
1929
+ await pressEnter();
1930
+ }
1931
+
1932
+ // ============================================================================
1933
+ // FIND DUPLICATES
1934
+ // ============================================================================
1935
+
1936
+ async function findDuplicates() {
1937
+ header();
1938
+ sectionHeader(MSG.DUPLICATES);
1939
+
1940
+ const spinner = ora(`${MSG.SCANNING} (this may take a while)...`).start();
1941
+
1942
+ // Find files > 1MB, group by size
1943
+ const sizeMap = new Map();
1944
+
1945
+ const output = exec(`find ~ -type f -size +1M 2>/dev/null | head -500`);
1946
+ const files = output.split('\n').filter(l => l.trim());
1947
+
1948
+ files.forEach(file => {
1949
+ try {
1950
+ const stats = fs.statSync(file);
1951
+ const size = stats.size;
1952
+ if (!sizeMap.has(size)) sizeMap.set(size, []);
1953
+ sizeMap.get(size).push(file);
1954
+ } catch (e) {}
1955
+ });
1956
+
1957
+ spinner.text = MSG.HASHING;
1958
+
1959
+ // Find duplicates by hash for files with same size
1960
+ const hashGroups = new Map();
1961
+
1962
+ for (const [size, fileList] of sizeMap) {
1963
+ if (fileList.length < 2) continue;
1964
+
1965
+ for (const file of fileList) {
1966
+ try {
1967
+ const hash = exec(`md5 -q "${file}" 2>/dev/null`);
1968
+ if (hash) {
1969
+ if (!hashGroups.has(hash)) hashGroups.set(hash, []);
1970
+ hashGroups.get(hash).push(file);
1971
+ }
1972
+ } catch (e) {}
1973
+ }
1974
+ }
1975
+
1976
+ spinner.stop();
1977
+
1978
+ // Display duplicate groups
1979
+ header();
1980
+ sectionHeader(MSG.DUPLICATES);
1981
+
1982
+ let groupNum = 0;
1983
+ let totalDupSize = 0;
1984
+ const allDups = [];
1985
+
1986
+ for (const [hash, fileList] of hashGroups) {
1987
+ if (fileList.length < 2) continue;
1988
+
1989
+ groupNum++;
1990
+ const fileSize = Math.round(getSize(fileList[0]));
1991
+ const dupSpace = (fileList.length - 1) * fileSize;
1992
+ totalDupSize += dupSpace;
1993
+
1994
+ console.log(` ${chalk.white(MSG.GROUP + ' ' + groupNum)} ${chalk.dim('(' + fileSize + ' MB ' + MSG.EACH + ')')}`);
1995
+
1996
+ fileList.forEach((file, idx) => {
1997
+ let display = file.replace(HOME, '~');
1998
+ if (display.length > 50) display = '...' + display.slice(-47);
1999
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${display}`);
2000
+ allDups.push(file);
2001
+ });
2002
+ console.log();
2003
+
2004
+ if (groupNum >= 10) break;
2005
+ }
2006
+
2007
+ if (groupNum === 0) {
2008
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2009
+ await pressEnter();
2010
+ return;
2011
+ }
2012
+
2013
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalDupSize))} ${MSG.IN_DUPLICATES}`);
2014
+ console.log();
2015
+ console.log(chalk.dim(` ${MSG.SELECT_KEEP}`));
2016
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2017
+
2018
+ console.log(` ${chalk.yellow(MSG.FEATURE_PROGRESS)}`);
2019
+ await pressEnter();
2020
+ }
2021
+
2022
+ // ============================================================================
2023
+ // UNINSTALL APPS
2024
+ // ============================================================================
2025
+
2026
+ async function uninstallApps() {
2027
+ header();
2028
+ sectionHeader(MSG.UNINSTALL);
2029
+
2030
+ const spinner = ora(MSG.SCANNING).start();
2031
+
2032
+ const apps = [];
2033
+
2034
+ const appDirs = fs.readdirSync('/Applications').filter(f => f.endsWith('.app'));
2035
+
2036
+ for (const appDir of appDirs) {
2037
+ const appPath = path.join('/Applications', appDir);
2038
+ const appName = appDir.replace('.app', '');
2039
+
2040
+ const appSize = getSize(appPath);
2041
+
2042
+ // Find associated data
2043
+ let dataSize = 0;
2044
+ let bundleId = '';
2045
+
2046
+ try {
2047
+ bundleId = exec(`defaults read "${appPath}/Contents/Info" CFBundleIdentifier 2>/dev/null`);
2048
+ } catch (e) {}
2049
+
2050
+ if (bundleId) {
2051
+ const dataPaths = [
2052
+ path.join(HOME, 'Library/Application Support', appName),
2053
+ path.join(HOME, 'Library/Application Support', bundleId),
2054
+ path.join(HOME, 'Library/Preferences', `${bundleId}.plist`),
2055
+ path.join(HOME, 'Library/Caches', bundleId),
2056
+ path.join(HOME, 'Library/Caches', appName),
2057
+ path.join(HOME, 'Library/Containers', bundleId),
2058
+ path.join(HOME, 'Library/Saved Application State', `${bundleId}.savedState`)
2059
+ ];
2060
+
2061
+ dataPaths.forEach(dp => {
2062
+ if (fs.existsSync(dp)) {
2063
+ dataSize += getSize(dp);
2064
+ }
2065
+ });
2066
+ }
2067
+
2068
+ const total = appSize + dataSize;
2069
+ if (total < 50) continue;
2070
+
2071
+ apps.push({ name: appName, path: appPath, appSize, dataSize, bundleId });
2072
+
2073
+ if (apps.length >= 25) break;
2074
+ }
2075
+
2076
+ spinner.stop();
2077
+
2078
+ header();
2079
+ sectionHeader(MSG.UNINSTALL);
2080
+
2081
+ if (apps.length === 0) {
2082
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2083
+ await pressEnter();
2084
+ return;
2085
+ }
2086
+
2087
+ apps.forEach((app, i) => {
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 + ')')}`);
2089
+ });
2090
+
2091
+ console.log();
2092
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/n]:`));
2093
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2094
+
2095
+ if (choice.toLowerCase() === 'n' || choice === '') {
2096
+ await pressEnter();
2097
+ return;
2098
+ }
2099
+
2100
+ const parts = choice.split(',');
2101
+ for (const part of parts) {
2102
+ const idx = parseInt(part.trim()) - 1;
2103
+ if (idx < 0 || idx >= apps.length) continue;
2104
+
2105
+ const app = apps[idx];
2106
+ console.log(` ${chalk.yellow(MSG.DELETING + ' ' + app.name + '...')}`);
2107
+
2108
+ // Remove app data
2109
+ if (app.bundleId && !DRY_RUN) {
2110
+ const dataPaths = [
2111
+ path.join(HOME, 'Library/Application Support', app.name),
2112
+ path.join(HOME, 'Library/Application Support', app.bundleId),
2113
+ path.join(HOME, 'Library/Preferences', `${app.bundleId}.plist`),
2114
+ path.join(HOME, 'Library/Caches', app.bundleId),
2115
+ path.join(HOME, 'Library/Caches', app.name),
2116
+ path.join(HOME, 'Library/Containers', app.bundleId),
2117
+ path.join(HOME, 'Library/Saved Application State', `${app.bundleId}.savedState`),
2118
+ path.join(HOME, 'Library/Logs', app.name)
2119
+ ];
2120
+
2121
+ dataPaths.forEach(dp => {
2122
+ try {
2123
+ if (fs.existsSync(dp)) deleteItem(dp);
2124
+ } catch (e) {}
2125
+ });
2126
+ }
2127
+
2128
+ // Remove app
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 + '"')}`);
2134
+ }
2135
+ }
2136
+
2137
+ await pressEnter();
2138
+ }
2139
+
2140
+ // ============================================================================
2141
+ // MANAGE STARTUP
2142
+ // ============================================================================
2143
+
2144
+ async function manageStartup() {
2145
+ header();
2146
+ sectionHeader(MSG.STARTUP_ITEMS);
2147
+
2148
+ console.log(` ${chalk.white(MSG.STARTUP_ITEMS + ':')}`);
2149
+
2150
+ // Get login items via AppleScript
2151
+ const loginItems = exec(`osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null`);
2152
+
2153
+ if (loginItems) {
2154
+ const items = loginItems.split(', ').filter(i => i.trim());
2155
+ items.forEach((item, idx) => {
2156
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${chalk.green(figures.tick)} ${item} ${chalk.dim('(' + MSG.ENABLED + ')')}`);
2157
+ });
2158
+ } else {
2159
+ console.log(chalk.dim(' No login items found'));
2160
+ }
2161
+
2162
+ console.log();
2163
+ console.log(` ${chalk.white(MSG.LAUNCH_AGENTS + ':')}`);
2164
+
2165
+ // User Launch Agents
2166
+ const launchAgentsDir = path.join(HOME, 'Library/LaunchAgents');
2167
+ let idx = 100;
2168
+
2169
+ if (fs.existsSync(launchAgentsDir)) {
2170
+ const agents = fs.readdirSync(launchAgentsDir).filter(f => f.endsWith('.plist'));
2171
+
2172
+ for (const agent of agents) {
2173
+ const name = agent.replace('.plist', '');
2174
+ const loaded = exec(`launchctl list 2>/dev/null | grep "${name}"`);
2175
+
2176
+ const status = loaded ?
2177
+ `${chalk.green(figures.tick)} ${MSG.ENABLED}` :
2178
+ `${chalk.red(figures.cross)} ${MSG.DISABLED}`;
2179
+
2180
+ console.log(` ${chalk.cyan('[' + idx + ']')} ${status} ${chalk.dim(name)}`);
2181
+ idx++;
2182
+ }
2183
+ }
2184
+
2185
+ console.log();
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 + ':')} `);
2188
+
2189
+ if (choice.toLowerCase() === 'd') {
2190
+ exec(`osascript -e 'tell application "System Events" to delete every login item' 2>/dev/null`);
2191
+ console.log(` ${chalk.green(figures.tick)} All login items disabled`);
2192
+ }
2193
+
2194
+ await pressEnter();
2195
+ }
2196
+
2197
+ // ============================================================================
2198
+ // CLEAN BROWSERS
2199
+ // ============================================================================
2200
+
2201
+ async function cleanBrowsers() {
2202
+ header();
2203
+ sectionHeader(MSG.BROWSERS_CAT);
2204
+
2205
+ const items = [];
2206
+ let totalBrowser = 0;
2207
+
2208
+ function addBrowserItem(p, name) {
2209
+ const fullPath = p.replace(/^~/, HOME);
2210
+ const size = getSize(p);
2211
+ if (size > 0) {
2212
+ items.push({ path: fullPath, name, size, selected: false });
2213
+ totalBrowser += size;
2214
+ }
2215
+ }
2216
+
2217
+ console.log(` ${chalk.white('Chrome:')}`);
2218
+ addBrowserItem('~/Library/Caches/Google/Chrome', 'Chrome Cache');
2219
+ addBrowserItem('~/Library/Application Support/Google/Chrome/Default/Service Worker', 'Chrome Service Workers');
2220
+ addBrowserItem('~/Library/Application Support/Google/Chrome/Default/GPUCache', 'Chrome GPU Cache');
2221
+
2222
+ console.log(` ${chalk.white('Safari:')}`);
2223
+ addBrowserItem('~/Library/Caches/com.apple.Safari', 'Safari Cache');
2224
+ addBrowserItem('~/Library/Safari/LocalStorage', 'Safari Local Storage');
2225
+
2226
+ console.log(` ${chalk.white('Firefox:')}`);
2227
+ const ffProfile = exec('find ~/Library/Application\\ Support/Firefox/Profiles -maxdepth 1 -name "*.default*" 2>/dev/null | head -1');
2228
+ if (ffProfile) {
2229
+ addBrowserItem(`${ffProfile}/cache2`, 'Firefox Cache');
2230
+ }
2231
+
2232
+ items.forEach((item, i) => {
2233
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
2234
+ });
2235
+
2236
+ if (items.length === 0) {
2237
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2238
+ await pressEnter();
2239
+ return;
2240
+ }
2241
+
2242
+ console.log();
2243
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalBrowser))}`);
2244
+ console.log();
2245
+
2246
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
2247
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2248
+
2249
+ if (choice.toLowerCase() === 'n' || choice === '') {
2250
+ await pressEnter();
2251
+ return;
2252
+ }
2253
+
2254
+ if (choice.toLowerCase() === 'a') {
2255
+ items.forEach(item => item.selected = true);
2256
+ } else {
2257
+ const parts = choice.split(',');
2258
+ parts.forEach(part => {
2259
+ const idx = parseInt(part.trim()) - 1;
2260
+ if (idx >= 0 && idx < items.length) items[idx].selected = true;
2261
+ });
2262
+ }
2263
+
2264
+ let cleaned = 0;
2265
+ console.log();
2266
+
2267
+ for (const item of items) {
2268
+ if (!item.selected) continue;
2269
+ if (deleteItem(item.path)) {
2270
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
2271
+ cleaned += item.size;
2272
+ }
2273
+ }
2274
+
2275
+ console.log();
2276
+ console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(cleaned))}`);
2277
+
2278
+ playNotificationSound();
2279
+ await pressEnter();
2280
+ }
2281
+
2282
+ // ============================================================================
2283
+ // KILL PROCESSES
2284
+ // ============================================================================
2285
+
2286
+ async function killProcesses() {
2287
+ header();
2288
+ sectionHeader(MSG.PROCESS_MEM);
2289
+
2290
+ console.log(` ${chalk.white(MSG.PROCESS_MEM + ':')}`);
2291
+
2292
+ const procs = [];
2293
+ const totalMemBytes = parseInt(exec("sysctl hw.memsize 2>/dev/null | awk '{print $2}'")) || 8589934592;
2294
+ const totalMemMB = Math.round(totalMemBytes / 1024 / 1024);
2295
+
2296
+ const output = exec('ps aux -m 2>/dev/null | head -15');
2297
+ const lines = output.split('\n').slice(1); // Skip header
2298
+
2299
+ for (const line of lines) {
2300
+ const parts = line.split(/\s+/);
2301
+ if (parts.length < 11) continue;
2302
+
2303
+ const pid = parts[1];
2304
+ const mem = parseFloat(parts[3]) || 0;
2305
+ const name = parts[10];
2306
+
2307
+ // Skip system processes
2308
+ if (['kernel_task', 'WindowServer', 'launchd'].includes(name)) continue;
2309
+
2310
+ const memMB = Math.round(mem * totalMemMB / 100);
2311
+ if (memMB < 100) continue;
2312
+
2313
+ procs.push({ pid, name, memMB });
2314
+
2315
+ console.log(` ${chalk.cyan('[' + String(procs.length).padStart(2) + ']')} ${name.padEnd(30)} ${chalk.red(memMB + ' MB RAM')}`);
2316
+
2317
+ if (procs.length >= 10) break;
2318
+ }
2319
+
2320
+ if (procs.length === 0) {
2321
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2322
+ await pressEnter();
2323
+ return;
2324
+ }
2325
+
2326
+ console.log();
2327
+ console.log(chalk.dim(` ${MSG.KILL} process [number/n]:`));
2328
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2329
+
2330
+ if (choice.toLowerCase() === 'n' || choice === '') {
2331
+ await pressEnter();
2332
+ return;
2333
+ }
2334
+
2335
+ const idx = parseInt(choice) - 1;
2336
+ if (idx >= 0 && idx < procs.length) {
2337
+ const proc = procs[idx];
2338
+
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
+ }
2349
+ }
2350
+
2351
+ await pressEnter();
2352
+ }
2353
+
2354
+ // ============================================================================
2355
+ // SETTINGS
2356
+ // ============================================================================
2357
+
2358
+ async function showSettings() {
2359
+ while (true) {
2360
+ header();
2361
+ sectionHeader(MSG.OPT_SETTINGS);
2362
+
2363
+ const currentLang = LANG_CODE === 'en' ? 'EN' : 'PT';
2364
+
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`);
2372
+ console.log();
2373
+ console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
2374
+ console.log();
2375
+
2376
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2377
+
2378
+ switch (choice) {
2379
+ case '1':
2380
+ // Manage exclusions
2381
+ header();
2382
+ sectionHeader(MSG.EXCLUSIONS);
2383
+
2384
+ console.log(` ${chalk.white(MSG.CURRENT_EXCLUSIONS + ':')}`);
2385
+ if (fs.existsSync(EXCLUSIONS_FILE)) {
2386
+ const exclusions = fs.readFileSync(EXCLUSIONS_FILE, 'utf8').split('\n').filter(l => l.trim());
2387
+ exclusions.forEach((ex, i) => console.log(` ${i + 1}. ${ex}`));
2388
+ } else {
2389
+ console.log(chalk.dim(' None'));
2390
+ }
2391
+ console.log();
2392
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
2393
+
2394
+ const newExclusion = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
2395
+
2396
+ if (newExclusion === 'c') {
2397
+ if (fs.existsSync(EXCLUSIONS_FILE)) fs.unlinkSync(EXCLUSIONS_FILE);
2398
+ console.log(` ${chalk.green(figures.tick)} Exclusions ${MSG.CLEARED}`);
2399
+ } else if (newExclusion !== 'n' && newExclusion) {
2400
+ fs.appendFileSync(EXCLUSIONS_FILE, newExclusion + '\n');
2401
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newExclusion}`);
2402
+ }
2403
+ await pressEnter();
2404
+ break;
2405
+
2406
+ case '2':
2407
+ // Custom paths
2408
+ header();
2409
+ sectionHeader(MSG.CUSTOM_PATHS);
2410
+
2411
+ console.log(` ${chalk.white(MSG.CURRENT_PATHS + ':')}`);
2412
+ if (fs.existsSync(CUSTOM_PATHS_FILE)) {
2413
+ const paths = fs.readFileSync(CUSTOM_PATHS_FILE, 'utf8').split('\n').filter(l => l.trim() && !l.startsWith('#'));
2414
+ paths.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
2415
+ } else {
2416
+ console.log(chalk.dim(' None'));
2417
+ }
2418
+ console.log();
2419
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
2420
+
2421
+ const newPath = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
2422
+
2423
+ if (newPath === 'c') {
2424
+ if (fs.existsSync(CUSTOM_PATHS_FILE)) fs.unlinkSync(CUSTOM_PATHS_FILE);
2425
+ console.log(` ${chalk.green(figures.tick)} Custom paths ${MSG.CLEARED}`);
2426
+ } else if (newPath !== 'n' && newPath) {
2427
+ fs.appendFileSync(CUSTOM_PATHS_FILE, newPath + '\n');
2428
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newPath}`);
2429
+ }
2430
+ await pressEnter();
2431
+ break;
2432
+
2433
+ case '3':
2434
+ // Change language
2435
+ const newLang = LANG_CODE === 'pt' ? 'en' : 'pt';
2436
+ fs.writeFileSync(LANGUAGE_FILE, newLang);
2437
+ console.log(` ${chalk.green(figures.tick)} ${newLang === 'en' ? MSG.LANG_CHANGED_EN : MSG.LANG_CHANGED_PT}`);
2438
+ await pressEnter();
2439
+ // Restart to apply language change
2440
+ spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
2441
+ process.exit(0);
2442
+ break;
2443
+
2444
+ case '4':
2445
+ // Reset defaults
2446
+ [EXCLUSIONS_FILE, CUSTOM_PATHS_FILE, LANGUAGE_FILE].forEach(f => {
2447
+ if (fs.existsSync(f)) fs.unlinkSync(f);
2448
+ });
2449
+ console.log(` ${chalk.green(figures.tick)} ${MSG.SETTINGS_RESET}`);
2450
+ await pressEnter();
2451
+ // Restart to apply changes
2452
+ spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
2453
+ process.exit(0);
2454
+ break;
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
+
2484
+ case '0':
2485
+ return;
2486
+
2487
+ default:
2488
+ break;
2489
+ }
2490
+ }
2491
+ }
2492
+
2493
+ // ============================================================================
2494
+ // MAIN MENU
2495
+ // ============================================================================
2496
+
2497
+ async function mainMenu() {
2498
+ while (true) {
2499
+ header();
2500
+
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}`);
2527
+ console.log();
2528
+
2529
+ console.log(` ${chalk.cyan('[0]')} ${MSG.OPT_EXIT}`);
2530
+ console.log();
2531
+
2532
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2533
+
2534
+ switch (choice) {
2535
+ case '1': await quickClean(); break;
2536
+ case '2': await findLargeFiles(); break;
2537
+ case '3': await findDuplicates(); break;
2538
+ case '4': await uninstallApps(); 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;
2550
+ case '0':
2551
+ clearScreen();
2552
+ console.log();
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
+ `));
2558
+ console.log();
2559
+ process.exit(0);
2560
+ break;
2561
+ default:
2562
+ console.log(` ${chalk.red(MSG.INVALID)}`);
2563
+ await new Promise(r => setTimeout(r, 1000));
2564
+ }
2565
+ }
2566
+ }
2567
+
2568
+ // ============================================================================
2569
+ // MAIN
2570
+ // ============================================================================
2571
+
2572
+ // Check if macOS
2573
+ if (os.platform() !== 'darwin') {
2574
+ console.log('This program only works on macOS');
2575
+ process.exit(1);
2576
+ }
2577
+
2578
+ // Command line arguments
2579
+ const args = process.argv.slice(2);
2580
+
2581
+ // Parse flags
2582
+ if (args.includes('--dry-run')) {
2583
+ DRY_RUN = true;
2584
+ }
2585
+
2586
+ if (args.includes('--secure')) {
2587
+ SECURE_DELETE = true;
2588
+ }
2589
+
2590
+ if (args.includes('--version') || args.includes('-v')) {
2591
+ console.log(`Aris Mac Cleaner v${VERSION}`);
2592
+ process.exit(0);
2593
+ }
2594
+
2595
+ if (args.includes('--help') || args.includes('-h')) {
2596
+ console.log(`
2597
+ ${gradient.pastel.multiline(ASCII_LOGO)}
2598
+
2599
+ Usage:
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
2606
+
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
2624
+ `);
2625
+ process.exit(0);
2626
+ }
2627
+
2628
+ if (args.includes('--quick') || args.includes('-q')) {
2629
+ (async () => {
2630
+ getSystemStats();
2631
+ await quickClean();
2632
+ })();
2633
+ } else {
2634
+ // Show animated logo and start menu
2635
+ (async () => {
2636
+ await animateLogo();
2637
+ getSystemStats();
2638
+ mainMenu();
2639
+ })();
2640
+ }