aris-mac-cleaner 3.0.0 → 4.0.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 +223 -56
  2. package/bin/cli.js +3319 -528
  3. package/package.json +29 -4
package/bin/cli.js CHANGED
@@ -1,54 +1,228 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ╔══════════════════════════════════════════════════════════════════════════════╗
4
- // ARIS MAC CLEANER v3.0 ║
5
- //por Salvador Reis
6
- // ╚══════════════════════════════════════════════════════════════════════════════╝
3
+ // ============================================================================
4
+ // ARIS MAC CLEANER v3.5 PREMIUM
5
+ // por Salvador Reis
6
+ // ============================================================================
7
7
 
8
- const { execSync, spawnSync } = require('child_process');
8
+ const { execSync, spawnSync, spawn } = require('child_process');
9
9
  const os = require('os');
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
  const readline = require('readline');
13
13
  const crypto = require('crypto');
14
+ const https = require('https');
15
+
16
+ // Premium dependencies (with fallbacks)
17
+ let chalk, gradient, ora, cliProgress, asciichart, notifier, boxen, figures;
18
+
19
+ try {
20
+ chalk = require('chalk');
21
+ } catch (e) {
22
+ chalk = {
23
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
24
+ green: s => `\x1b[32m${s}\x1b[0m`,
25
+ red: s => `\x1b[31m${s}\x1b[0m`,
26
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
27
+ white: s => `\x1b[37m${s}\x1b[0m`,
28
+ gray: s => `\x1b[90m${s}\x1b[0m`,
29
+ dim: s => `\x1b[2m${s}\x1b[0m`,
30
+ bold: s => `\x1b[1m${s}\x1b[0m`,
31
+ magenta: s => `\x1b[35m${s}\x1b[0m`,
32
+ blue: s => `\x1b[34m${s}\x1b[0m`,
33
+ bgCyan: { black: s => `\x1b[46m\x1b[30m${s}\x1b[0m` },
34
+ bgGreen: { black: s => `\x1b[42m\x1b[30m${s}\x1b[0m` },
35
+ bgRed: { white: s => `\x1b[41m\x1b[37m${s}\x1b[0m` },
36
+ bgYellow: { black: s => `\x1b[43m\x1b[30m${s}\x1b[0m` }
37
+ };
38
+ }
39
+
40
+ try {
41
+ gradient = require('gradient-string');
42
+ } catch (e) {
43
+ gradient = {
44
+ pastel: { multiline: s => s },
45
+ cristal: { multiline: s => s },
46
+ rainbow: { multiline: s => s },
47
+ vice: { multiline: s => s },
48
+ mind: { multiline: s => s }
49
+ };
50
+ }
51
+
52
+ try {
53
+ ora = require('ora');
54
+ } catch (e) {
55
+ ora = (text) => ({
56
+ start: () => ({ succeed: () => {}, fail: () => {}, stop: () => {}, text: '' }),
57
+ succeed: () => {},
58
+ fail: () => {},
59
+ stop: () => {}
60
+ });
61
+ }
62
+
63
+ try {
64
+ cliProgress = require('cli-progress');
65
+ } catch (e) {
66
+ cliProgress = {
67
+ SingleBar: class {
68
+ constructor() {}
69
+ start() {}
70
+ update() {}
71
+ stop() {}
72
+ increment() {}
73
+ },
74
+ Presets: { shades_classic: {} }
75
+ };
76
+ }
77
+
78
+ try {
79
+ asciichart = require('asciichart');
80
+ } catch (e) {
81
+ asciichart = { plot: (data) => data.join(', ') };
82
+ }
83
+
84
+ try {
85
+ notifier = require('node-notifier');
86
+ } catch (e) {
87
+ notifier = { notify: () => {} };
88
+ }
89
+
90
+ try {
91
+ boxen = require('boxen');
92
+ } catch (e) {
93
+ boxen = (text) => text;
94
+ }
95
+
96
+ try {
97
+ figures = require('figures');
98
+ } catch (e) {
99
+ figures = {
100
+ tick: '\u2713',
101
+ cross: '\u2717',
102
+ bullet: '\u2022',
103
+ arrowRight: '\u2192',
104
+ warning: '\u26A0',
105
+ info: '\u2139',
106
+ star: '\u2605',
107
+ heart: '\u2665',
108
+ pointer: '\u25B6',
109
+ line: '\u2500',
110
+ radioOn: '\u25C9',
111
+ radioOff: '\u25EF',
112
+ checkboxOn: '\u2612',
113
+ checkboxOff: '\u2610'
114
+ };
115
+ }
116
+
117
+ // Inquirer for arrow key navigation
118
+ let inquirer;
119
+ let useInquirer = true;
120
+
121
+ try {
122
+ inquirer = require('inquirer');
123
+ } catch (e) {
124
+ useInquirer = false;
125
+ }
126
+
127
+ // ============================================================================
128
+ // INQUIRER HELPER FUNCTIONS
129
+ // ============================================================================
130
+
131
+ async function selectOption(message, choices, fallbackPromptFn) {
132
+ if (useInquirer && inquirer) {
133
+ try {
134
+ const { selection } = await inquirer.prompt([{
135
+ type: 'list',
136
+ name: 'selection',
137
+ message: message,
138
+ choices: choices,
139
+ loop: false,
140
+ pageSize: 20
141
+ }]);
142
+ return selection;
143
+ } catch (e) {
144
+ if (fallbackPromptFn) return fallbackPromptFn();
145
+ return null;
146
+ }
147
+ }
148
+ if (fallbackPromptFn) return fallbackPromptFn();
149
+ return null;
150
+ }
151
+
152
+ async function selectMultiple(message, choices, fallbackPromptFn) {
153
+ if (useInquirer && inquirer) {
154
+ try {
155
+ const { selections } = await inquirer.prompt([{
156
+ type: 'checkbox',
157
+ name: 'selections',
158
+ message: message,
159
+ choices: choices,
160
+ pageSize: 20
161
+ }]);
162
+ return selections;
163
+ } catch (e) {
164
+ if (fallbackPromptFn) return fallbackPromptFn();
165
+ return [];
166
+ }
167
+ }
168
+ if (fallbackPromptFn) return fallbackPromptFn();
169
+ return [];
170
+ }
14
171
 
15
- const VERSION = '3.0';
172
+ async function confirmAction(message, defaultValue = false, fallbackPromptFn) {
173
+ if (useInquirer && inquirer) {
174
+ try {
175
+ const { confirmed } = await inquirer.prompt([{
176
+ type: 'confirm',
177
+ name: 'confirmed',
178
+ message: message,
179
+ default: defaultValue
180
+ }]);
181
+ return confirmed;
182
+ } catch (e) {
183
+ if (fallbackPromptFn) return fallbackPromptFn();
184
+ return defaultValue;
185
+ }
186
+ }
187
+ if (fallbackPromptFn) return fallbackPromptFn();
188
+ return defaultValue;
189
+ }
190
+
191
+ // ============================================================================
192
+ // CONSTANTS
193
+ // ============================================================================
194
+
195
+ const VERSION = '4.0.0';
196
+ const PACKAGE_NAME = 'aris-mac-cleaner';
16
197
  const HOME = os.homedir();
17
198
  const CONFIG_DIR = path.join(HOME, '.aris-mac-cleaner');
18
199
  const HISTORY_FILE = path.join(CONFIG_DIR, 'history.log');
19
200
  const EXCLUSIONS_FILE = path.join(CONFIG_DIR, 'exclusions.conf');
20
201
  const CUSTOM_PATHS_FILE = path.join(CONFIG_DIR, 'custom-paths.conf');
21
202
  const LANGUAGE_FILE = path.join(CONFIG_DIR, 'language');
203
+ const BACKUP_DIR = path.join(CONFIG_DIR, 'backups');
204
+ const REPORTS_DIR = path.join(CONFIG_DIR, 'reports');
205
+ const SCHEDULE_FILE = path.join(CONFIG_DIR, 'schedule.plist');
206
+ const BENCHMARK_FILE = path.join(CONFIG_DIR, 'benchmark.log');
207
+ const USAGE_FILE = path.join(CONFIG_DIR, 'usage.json');
208
+
209
+ // Ensure directories exist
210
+ [CONFIG_DIR, BACKUP_DIR, REPORTS_DIR].forEach(dir => {
211
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
212
+ });
213
+
214
+ // ============================================================================
215
+ // GLOBAL STATE
216
+ // ============================================================================
217
+
218
+ let DRY_RUN = false;
219
+ let SECURE_DELETE = false;
220
+ let systemStats = {};
221
+ let lastCleanupResults = null;
22
222
 
23
- // Create config directory
24
- if (!fs.existsSync(CONFIG_DIR)) {
25
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
26
- }
27
-
28
- // ══════════════════════════════════════════════════════════════════════════════
29
- // COLORS AND SYMBOLS
30
- // ══════════════════════════════════════════════════════════════════════════════
31
-
32
- const c = {
33
- reset: '\x1b[0m',
34
- red: '\x1b[0;31m',
35
- green: '\x1b[0;32m',
36
- yellow: '\x1b[1;33m',
37
- blue: '\x1b[0;34m',
38
- cyan: '\x1b[0;36m',
39
- white: '\x1b[1;37m',
40
- dim: '\x1b[2m',
41
- bold: '\x1b[1m'
42
- };
43
-
44
- const CHECK = '✓';
45
- const CROSS = '✗';
46
- const ARROW = '→';
47
- const BULLET = '•';
48
-
49
- // ══════════════════════════════════════════════════════════════════════════════
223
+ // ============================================================================
50
224
  // MULTI-LANGUAGE
51
- // ══════════════════════════════════════════════════════════════════════════════
225
+ // ============================================================================
52
226
 
53
227
  let LANG_CODE = 'pt';
54
228
  if (fs.existsSync(LANGUAGE_FILE)) {
@@ -58,18 +232,51 @@ if (fs.existsSync(LANGUAGE_FILE)) {
58
232
  const MSG = LANG_CODE === 'pt' ? {
59
233
  WELCOME: 'Bem-vindo ao Aris Mac Cleaner',
60
234
  MENU_TITLE: 'MENU PRINCIPAL',
61
- OPT_QUICK: 'Limpeza Rápida (caches e temporários)',
62
- OPT_LARGE: 'Encontrar Ficheiros Grandes (>100MB)',
63
- OPT_DUPLICATES: 'Encontrar Duplicados',
64
- OPT_APPS: 'Desinstalar Aplicações',
65
- OPT_STARTUP: 'Gerir Apps de Arranque',
66
- OPT_BROWSERS: 'Limpar Browsers',
67
- OPT_PROCESSES: 'Matar Processos Pesados',
68
- OPT_STATS: 'Ver Estatísticas',
69
- OPT_SETTINGS: 'Configurações',
70
- OPT_EXIT: 'Sair',
235
+ // Section headers
236
+ SECTION_CLEANING: 'LIMPEZAS',
237
+ SECTION_ANALYSIS: 'ANALISES',
238
+ SECTION_SETTINGS: 'DEFINICOES',
239
+ // Menu options
240
+ OPT_QUICK: 'Quick Clean',
241
+ OPT_DEEP: 'Deep Clean',
242
+ OPT_LARGE: 'Large Files',
243
+ OPT_DUPLICATES: 'Duplicates',
244
+ OPT_APPS: 'App Uninstaller',
245
+ OPT_STARTUP: 'Startup Manager',
246
+ OPT_BROWSERS: 'Browser Cleaner',
247
+ OPT_PROCESSES: 'Process Manager',
248
+ OPT_STATS: 'Statistics',
249
+ OPT_SETTINGS: 'Settings',
250
+ OPT_DISK: 'Disk Analyzer',
251
+ OPT_PRIVACY: 'Privacy Sweep',
252
+ OPT_MEMORY: 'Memory Optimizer',
253
+ OPT_BENCHMARK: 'Benchmark',
254
+ OPT_THERMAL: 'Thermal Monitor',
255
+ OPT_SCHEDULER: 'Scheduler',
256
+ OPT_EXPORT: 'Export Report',
257
+ OPT_EXIT: 'Exit',
258
+ // Deep Clean
259
+ DEEP_CLEAN: 'Deep Clean',
260
+ DEEP_CLEAN_DESC: 'Limpeza agressiva completa do sistema',
261
+ DEEP_CLEAN_WARNING: 'AVISO: Esta operacao vai limpar TUDO. Podes perder dados de cache importantes.',
262
+ DEEP_CLEAN_CONFIRM: 'Tens a certeza que queres continuar?',
263
+ DEEP_CLEAN_TIME: 'Tempo estimado: 2-5 minutos',
264
+ // Deep Clean categories
265
+ CAT_SYSTEM_CACHES: 'Caches do Sistema',
266
+ CAT_APP_CACHES: 'Caches de Aplicacoes',
267
+ CAT_XCODE: 'Xcode',
268
+ CAT_DEV_CACHES: 'Caches de Dev',
269
+ CAT_DOCKER: 'Docker',
270
+ CAT_IOS_BACKUPS: 'Backups iOS',
271
+ CAT_OLD_DOWNLOADS: 'Downloads Antigos',
272
+ CAT_MAIL: 'Mail Attachments',
273
+ CAT_TRASH: 'Lixo',
274
+ CAT_FONTS: 'Caches de Fontes',
275
+ CAT_ADOBE: 'Adobe Caches',
276
+ CAT_MUSIC: 'Music/Spotify Caches',
277
+ CAT_LOGS: 'Logs Antigos',
71
278
  CHOOSE: 'Escolha',
72
- INVALID: 'Opção inválida',
279
+ INVALID: 'Opcao invalida',
73
280
  PRESS_ENTER: 'Pressiona ENTER para continuar',
74
281
  SCANNING: 'A analisar...',
75
282
  FOUND: 'Encontrado',
@@ -79,13 +286,13 @@ const MSG = LANG_CODE === 'pt' ? {
79
286
  CANCEL: 'Cancelar',
80
287
  CONFIRM: 'Confirmar',
81
288
  FREED: 'Libertado',
82
- AVAILABLE: 'Disponível',
289
+ AVAILABLE: 'Disponivel',
83
290
  HEALTH_SCORE: 'Health Score',
84
291
  EXCELLENT: 'Excelente',
85
292
  GOOD: 'Bom',
86
- ATTENTION: 'Atenção',
87
- CRITICAL: 'Crítico',
88
- DIAGNOSE: 'DIAGNÓSTICO DO SISTEMA',
293
+ ATTENTION: 'Atencao',
294
+ CRITICAL: 'Critico',
295
+ DIAGNOSE: 'DIAGNOSTICO DO SISTEMA',
89
296
  DISK: 'Disco',
90
297
  RAM: 'RAM',
91
298
  SWAP: 'Swap',
@@ -97,9 +304,9 @@ const MSG = LANG_CODE === 'pt' ? {
97
304
  ALL: 'todos',
98
305
  NONE: 'nenhum',
99
306
  BACK: 'Voltar',
100
- FINAL_REPORT: 'RELATÓRIO FINAL',
307
+ FINAL_REPORT: 'RELATORIO FINAL',
101
308
  READY: 'Pronto para trabalhar!',
102
- RESTART_REC: 'Recomendação: Reinicia o Mac para limpar memória',
309
+ RESTART_REC: 'Recomendacao: Reinicia o Mac para limpar memoria',
103
310
  NO_ITEMS: 'Nada encontrado',
104
311
  CLEANING: 'A limpar...',
105
312
  CATEGORIES: 'CATEGORIAS',
@@ -107,8 +314,8 @@ const MSG = LANG_CODE === 'pt' ? {
107
314
  DEV_TOOLS: 'Ferramentas de Dev',
108
315
  BROWSERS_CAT: 'Browsers',
109
316
  DOWNLOADS_CAT: 'Downloads',
110
- APPS_CAT: 'Aplicações',
111
- PROCESS_MEM: 'Por Memória',
317
+ APPS_CAT: 'Aplicacoes',
318
+ PROCESS_MEM: 'Por Memoria',
112
319
  PROCESS_CPU: 'Por CPU',
113
320
  KILL: 'Matar',
114
321
  STARTUP_ITEMS: 'Items de Arranque',
@@ -117,53 +324,138 @@ const MSG = LANG_CODE === 'pt' ? {
117
324
  DISABLED: 'Desativado',
118
325
  TOGGLE: 'Alternar',
119
326
  DISABLE_ALL: 'Desativar todos',
120
- THIS_MONTH: 'Este mês',
327
+ THIS_MONTH: 'Este mes',
121
328
  CLEANINGS: 'Limpezas',
122
- AVG_SCORE: 'Score médio',
123
- HISTORY: 'Histórico',
124
- EXCLUSIONS: 'Gerir Exclusões',
329
+ AVG_SCORE: 'Score medio',
330
+ HISTORY: 'Historico',
331
+ EXCLUSIONS: 'Gerir Exclusoes',
125
332
  CUSTOM_PATHS: 'Caminhos Personalizados',
126
333
  CHANGE_LANG: 'Mudar Idioma',
127
- RESET_DEFAULTS: 'Repor Predefinições',
128
- PREVIEW_MODE: 'Modo Preview (não apaga nada)',
334
+ RESET_DEFAULTS: 'Repor Predefinicoes',
335
+ PREVIEW_MODE: 'Modo Preview (nao apaga nada)',
129
336
  LARGE_FILES: 'FICHEIROS GRANDES',
130
337
  DUPLICATES: 'FICHEIROS DUPLICADOS',
131
- UNINSTALL: 'DESINSTALAR APLICAÇÕES',
338
+ UNINSTALL: 'DESINSTALAR APLICACOES',
132
339
  APP_SIZE: 'app',
133
340
  DATA_SIZE: 'dados',
134
- CURRENT_EXCLUSIONS: 'Exclusões atuais',
341
+ CURRENT_EXCLUSIONS: 'Exclusoes atuais',
135
342
  CURRENT_PATHS: 'Caminhos personalizados atuais',
136
343
  ENTER_PATH: 'Introduz caminho (c=limpar, n=voltar)',
137
344
  PATH: 'Caminho',
138
345
  CLEARED: 'Limpo',
139
346
  ADDED: 'Adicionado',
140
347
  LANG_CHANGED_EN: 'Language changed to English',
141
- LANG_CHANGED_PT: 'Idioma alterado para Português',
142
- SETTINGS_RESET: 'Configurações repostas',
348
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
349
+ SETTINGS_RESET: 'Configuracoes repostas',
143
350
  REMOVED: 'Removido',
144
351
  NEED_SUDO: 'Precisa sudo para remover app. Executa:',
145
352
  DELETING: 'A apagar',
146
353
  KILLED: 'Morto',
147
354
  FAILED_KILL: 'Falhou ao matar',
148
- NO_HISTORY: 'Sem histórico. Faz algumas limpezas primeiro!',
355
+ NO_HISTORY: 'Sem historico. Faz algumas limpezas primeiro!',
149
356
  FEATURE_PROGRESS: 'Funcionalidade em progresso...',
150
357
  GROUP: 'Grupo',
151
358
  EACH: 'cada',
152
359
  IN_DUPLICATES: 'em duplicados',
153
- HASHING: 'A calcular hashes...'
360
+ HASHING: 'A calcular hashes...',
361
+ DRY_RUN_MODE: 'MODO DRY-RUN (nada sera apagado)',
362
+ SECURE_DELETE_MODE: 'MODO SEGURO (overwrite antes de apagar)',
363
+ BACKUP_CREATED: 'Backup criado',
364
+ SCHEDULE_CREATED: 'Agendamento criado',
365
+ SCHEDULE_REMOVED: 'Agendamento removido',
366
+ REPORT_EXPORTED: 'Relatorio exportado',
367
+ UPDATE_AVAILABLE: 'Atualizacao disponivel',
368
+ UP_TO_DATE: 'Ja tens a versao mais recente',
369
+ MEMORY_FREED: 'Memoria libertada',
370
+ PRIVACY_CLEANED: 'Privacidade limpa',
371
+ BENCHMARK_COMPLETE: 'Benchmark completo',
372
+ RECOMMENDATIONS: 'RECOMENDACOES',
373
+ DISK_ANALYSIS: 'ANALISE DE DISCO',
374
+ SCHEDULED_DAILY: 'Agendado diariamente',
375
+ SCHEDULED_WEEKLY: 'Agendado semanalmente',
376
+ CLEANUP_COMPLETE: 'Limpeza completa!',
377
+ THERMAL_STATUS: 'ESTADO TERMAL',
378
+ THERMAL_COOL: 'FRIO',
379
+ THERMAL_GOOD: 'BOM',
380
+ THERMAL_WARM: 'MORNO',
381
+ THERMAL_HOT: 'QUENTE',
382
+ THERMAL_CRITICAL: 'CRITICO',
383
+ THERMAL_TIP_COOL: 'Sistema a funcionar eficientemente',
384
+ THERMAL_TIP_GOOD: 'Operacao normal',
385
+ THERMAL_TIP_WARM: 'Sistema a trabalhar forte',
386
+ THERMAL_TIP_HOT: 'Fecha apps pesadas ou aguarda arrefecimento',
387
+ THERMAL_TIP_CRITICAL: 'CPU throttling ativo! Fecha apps imediatamente',
388
+ THERMAL_LEVEL: 'Nivel',
389
+ THERMAL_TIP: 'Dica',
390
+ THERMAL_DETAILED: 'Queres info detalhada de energia? (precisa sudo)',
391
+ THERMAL_STATS_APP: 'Para monitoramento continuo, instala Stats:',
392
+ THERMAL_POWER: 'Potencia',
393
+ THERMAL_CPU_POWER: 'CPU',
394
+ THERMAL_GPU_POWER: 'GPU',
395
+ THERMAL_ANE_POWER: 'ANE',
396
+ THERMAL_FREQ: 'Frequencia',
397
+ THERMAL_LOW: 'Baixo',
398
+ THERMAL_MEDIUM: 'Medio',
399
+ THERMAL_HIGH: 'Alto',
400
+ THERMAL_VERY_HIGH: 'Muito Alto',
401
+ // Confirmation mode
402
+ CONFIRM_MODE: 'Modo de confirmacao:',
403
+ CONFIRM_AUTO: 'Auto - apagar tudo',
404
+ CONFIRM_MANUAL: 'Manual - confirmar cada item',
405
+ CONFIRM_DRYRUN: 'Dry-run - apenas pre-visualizar',
406
+ CONFIRM_CHOOSE: 'Escolhe [a/m/d]:',
407
+ CONFIRM_ITEM: 'Apagar:',
408
+ CONFIRM_YES_NO_ALL: '[s/n/a] (Sim / Nao / Aceitar restantes):',
409
+ SUMMARY_DELETED: 'Apagados',
410
+ SUMMARY_SKIPPED: 'Ignorados',
411
+ SUMMARY_FREED: 'Libertado',
412
+ SUMMARY_WOULD_FREE: 'Seria libertado'
154
413
  } : {
155
414
  WELCOME: 'Welcome to Aris Mac Cleaner',
156
415
  MENU_TITLE: 'MAIN MENU',
157
- OPT_QUICK: 'Quick Clean (caches and temp files)',
158
- OPT_LARGE: 'Find Large Files (>100MB)',
159
- OPT_DUPLICATES: 'Find Duplicates',
160
- OPT_APPS: 'Uninstall Applications',
161
- OPT_STARTUP: 'Manage Startup Apps',
162
- OPT_BROWSERS: 'Clean Browsers',
163
- OPT_PROCESSES: 'Kill Heavy Processes',
164
- OPT_STATS: 'View Statistics',
416
+ // Section headers
417
+ SECTION_CLEANING: 'CLEANING',
418
+ SECTION_ANALYSIS: 'ANALYSIS',
419
+ SECTION_SETTINGS: 'SETTINGS',
420
+ // Menu options
421
+ OPT_QUICK: 'Quick Clean',
422
+ OPT_DEEP: 'Deep Clean',
423
+ OPT_LARGE: 'Large Files',
424
+ OPT_DUPLICATES: 'Duplicates',
425
+ OPT_APPS: 'App Uninstaller',
426
+ OPT_STARTUP: 'Startup Manager',
427
+ OPT_BROWSERS: 'Browser Cleaner',
428
+ OPT_PROCESSES: 'Process Manager',
429
+ OPT_STATS: 'Statistics',
165
430
  OPT_SETTINGS: 'Settings',
431
+ OPT_DISK: 'Disk Analyzer',
432
+ OPT_PRIVACY: 'Privacy Sweep',
433
+ OPT_MEMORY: 'Memory Optimizer',
434
+ OPT_BENCHMARK: 'Benchmark',
435
+ OPT_THERMAL: 'Thermal Monitor',
436
+ OPT_SCHEDULER: 'Scheduler',
437
+ OPT_EXPORT: 'Export Report',
166
438
  OPT_EXIT: 'Exit',
439
+ // Deep Clean
440
+ DEEP_CLEAN: 'Deep Clean',
441
+ DEEP_CLEAN_DESC: 'Aggressive full system cleanup',
442
+ DEEP_CLEAN_WARNING: 'WARNING: This will clean EVERYTHING. You may lose important cache data.',
443
+ DEEP_CLEAN_CONFIRM: 'Are you sure you want to continue?',
444
+ DEEP_CLEAN_TIME: 'Estimated time: 2-5 minutes',
445
+ // Deep Clean categories
446
+ CAT_SYSTEM_CACHES: 'System Caches',
447
+ CAT_APP_CACHES: 'Application Caches',
448
+ CAT_XCODE: 'Xcode',
449
+ CAT_DEV_CACHES: 'Dev Caches',
450
+ CAT_DOCKER: 'Docker',
451
+ CAT_IOS_BACKUPS: 'iOS Backups',
452
+ CAT_OLD_DOWNLOADS: 'Old Downloads',
453
+ CAT_MAIL: 'Mail Attachments',
454
+ CAT_TRASH: 'Trash',
455
+ CAT_FONTS: 'Font Caches',
456
+ CAT_ADOBE: 'Adobe Caches',
457
+ CAT_MUSIC: 'Music/Spotify Caches',
458
+ CAT_LOGS: 'Old Logs',
167
459
  CHOOSE: 'Choose',
168
460
  INVALID: 'Invalid option',
169
461
  PRESS_ENTER: 'Press ENTER to continue',
@@ -234,7 +526,7 @@ const MSG = LANG_CODE === 'pt' ? {
234
526
  CLEARED: 'Cleared',
235
527
  ADDED: 'Added',
236
528
  LANG_CHANGED_EN: 'Language changed to English',
237
- LANG_CHANGED_PT: 'Idioma alterado para Português',
529
+ LANG_CHANGED_PT: 'Idioma alterado para Portugues',
238
530
  SETTINGS_RESET: 'Settings reset to defaults',
239
531
  REMOVED: 'Removed',
240
532
  NEED_SUDO: 'Need sudo to remove app. Run:',
@@ -246,12 +538,118 @@ const MSG = LANG_CODE === 'pt' ? {
246
538
  GROUP: 'Group',
247
539
  EACH: 'each',
248
540
  IN_DUPLICATES: 'in duplicates',
249
- HASHING: 'Calculating hashes...'
541
+ HASHING: 'Calculating hashes...',
542
+ DRY_RUN_MODE: 'DRY-RUN MODE (nothing will be deleted)',
543
+ SECURE_DELETE_MODE: 'SECURE MODE (overwrite before delete)',
544
+ BACKUP_CREATED: 'Backup created',
545
+ SCHEDULE_CREATED: 'Schedule created',
546
+ SCHEDULE_REMOVED: 'Schedule removed',
547
+ REPORT_EXPORTED: 'Report exported',
548
+ UPDATE_AVAILABLE: 'Update available',
549
+ UP_TO_DATE: 'You have the latest version',
550
+ MEMORY_FREED: 'Memory freed',
551
+ PRIVACY_CLEANED: 'Privacy cleaned',
552
+ BENCHMARK_COMPLETE: 'Benchmark complete',
553
+ RECOMMENDATIONS: 'RECOMMENDATIONS',
554
+ DISK_ANALYSIS: 'DISK ANALYSIS',
555
+ SCHEDULED_DAILY: 'Scheduled daily',
556
+ SCHEDULED_WEEKLY: 'Scheduled weekly',
557
+ CLEANUP_COMPLETE: 'Cleanup complete!',
558
+ THERMAL_STATUS: 'THERMAL STATUS',
559
+ THERMAL_COOL: 'COOL',
560
+ THERMAL_GOOD: 'GOOD',
561
+ THERMAL_WARM: 'WARM',
562
+ THERMAL_HOT: 'HOT',
563
+ THERMAL_CRITICAL: 'CRITICAL',
564
+ THERMAL_TIP_COOL: 'System running efficiently',
565
+ THERMAL_TIP_GOOD: 'Normal operation',
566
+ THERMAL_TIP_WARM: 'System working hard',
567
+ THERMAL_TIP_HOT: 'Close heavy apps or wait for cooldown',
568
+ THERMAL_TIP_CRITICAL: 'CPU throttling active! Close apps immediately',
569
+ THERMAL_LEVEL: 'Level',
570
+ THERMAL_TIP: 'Tip',
571
+ THERMAL_DETAILED: 'Want detailed power info? (requires sudo)',
572
+ THERMAL_STATS_APP: 'For continuous monitoring, install Stats:',
573
+ THERMAL_POWER: 'Power',
574
+ THERMAL_CPU_POWER: 'CPU',
575
+ THERMAL_GPU_POWER: 'GPU',
576
+ THERMAL_ANE_POWER: 'ANE',
577
+ THERMAL_FREQ: 'Frequency',
578
+ THERMAL_LOW: 'Low',
579
+ THERMAL_MEDIUM: 'Medium',
580
+ THERMAL_HIGH: 'High',
581
+ THERMAL_VERY_HIGH: 'Very High',
582
+ // Confirmation mode
583
+ CONFIRM_MODE: 'Confirmation mode:',
584
+ CONFIRM_AUTO: 'Auto - delete all',
585
+ CONFIRM_MANUAL: 'Manual - confirm each item',
586
+ CONFIRM_DRYRUN: 'Dry-run - just preview',
587
+ CONFIRM_CHOOSE: 'Choose [a/m/d]:',
588
+ CONFIRM_ITEM: 'Delete:',
589
+ CONFIRM_YES_NO_ALL: '[y/n/a] (Yes / No / Accept all remaining):',
590
+ SUMMARY_DELETED: 'Deleted',
591
+ SUMMARY_SKIPPED: 'Skipped',
592
+ SUMMARY_FREED: 'Freed',
593
+ SUMMARY_WOULD_FREE: 'Would free'
250
594
  };
251
595
 
252
- // ══════════════════════════════════════════════════════════════════════════════
596
+ // ============================================================================
597
+ // ANIMATED ASCII LOGO
598
+ // ============================================================================
599
+
600
+ const ASCII_LOGO = `
601
+ _ ____ ___ ____
602
+ / \\ | _ \\|_ _/ ___|
603
+ / _ \\ | |_) || |\\___ \\
604
+ / ___ \\| _ < | | ___) |
605
+ /_/ \\_\\_| \\_\\___|____/
606
+
607
+ MAC CLEANER v${VERSION}
608
+ `;
609
+
610
+ const ASCII_LOGO_FRAMES = [
611
+ `
612
+ _
613
+ / \\
614
+ / _ \\
615
+ / ___ \\
616
+ /_/ \\_\\
617
+ `,
618
+ `
619
+ _ ____
620
+ / \\ | _ \\
621
+ / _ \\ | |_) |
622
+ / ___ \\| _ <
623
+ /_/ \\_\\_| \\_\\
624
+ `,
625
+ `
626
+ _ ____ ___ ____
627
+ / \\ | _ \\|_ _/ ___|
628
+ / _ \\ | |_) || |\\___ \\
629
+ / ___ \\| _ < | | ___) |
630
+ /_/ \\_\\_| \\_\\___|____/
631
+ `,
632
+ ASCII_LOGO
633
+ ];
634
+
635
+ async function animateLogo() {
636
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
637
+
638
+ for (const frame of ASCII_LOGO_FRAMES) {
639
+ process.stdout.write('\x1b[2J\x1b[H');
640
+ console.log(gradient.cristal.multiline(frame));
641
+ await sleep(150);
642
+ }
643
+
644
+ // Final sparkle effect
645
+ process.stdout.write('\x1b[2J\x1b[H');
646
+ console.log(gradient.pastel.multiline(ASCII_LOGO));
647
+ await sleep(300);
648
+ }
649
+
650
+ // ============================================================================
253
651
  // UTILITY FUNCTIONS
254
- // ══════════════════════════════════════════════════════════════════════════════
652
+ // ============================================================================
255
653
 
256
654
  function exec(cmd) {
257
655
  try {
@@ -294,6 +692,105 @@ function clearScreen() {
294
692
  process.stdout.write('\x1b[2J\x1b[H');
295
693
  }
296
694
 
695
+ function prompt(question) {
696
+ return new Promise((resolve) => {
697
+ const rl = readline.createInterface({
698
+ input: process.stdin,
699
+ output: process.stdout
700
+ });
701
+ rl.question(question, (answer) => {
702
+ rl.close();
703
+ resolve(answer.trim());
704
+ });
705
+ });
706
+ }
707
+
708
+ async function pressEnter() {
709
+ console.log();
710
+ await prompt(` ${chalk.dim(MSG.PRESS_ENTER + '...')} `);
711
+ }
712
+
713
+ // ============================================================================
714
+ // CONFIRMATION MODE SYSTEM
715
+ // ============================================================================
716
+
717
+ async function askConfirmationMode() {
718
+ const choices = [
719
+ { name: chalk.green(MSG.CONFIRM_AUTO), value: 'auto' },
720
+ { name: chalk.yellow(MSG.CONFIRM_MANUAL), value: 'manual' },
721
+ { name: chalk.blue(MSG.CONFIRM_DRYRUN), value: 'dryrun' }
722
+ ];
723
+
724
+ const fallback = async () => {
725
+ console.log();
726
+ console.log(chalk.cyan(' +-------------------------------------+'));
727
+ console.log(chalk.cyan(' | ') + chalk.white(MSG.CONFIRM_MODE.padEnd(35)) + chalk.cyan('|'));
728
+ console.log(chalk.cyan(' | |'));
729
+ console.log(chalk.cyan(' | ') + chalk.green('[a]') + chalk.white(' ' + MSG.CONFIRM_AUTO.padEnd(30)) + chalk.cyan('|'));
730
+ console.log(chalk.cyan(' | ') + chalk.yellow('[m]') + chalk.white(' ' + MSG.CONFIRM_MANUAL.padEnd(30)) + chalk.cyan('|'));
731
+ console.log(chalk.cyan(' | ') + chalk.blue('[d]') + chalk.white(' ' + MSG.CONFIRM_DRYRUN.padEnd(30)) + chalk.cyan('|'));
732
+ console.log(chalk.cyan(' | |'));
733
+ console.log(chalk.cyan(' +-------------------------------------+'));
734
+ console.log();
735
+ const c = await prompt(` ${chalk.white(MSG.CONFIRM_CHOOSE)} `);
736
+ switch (c.toLowerCase()) {
737
+ case 'm': return 'manual';
738
+ case 'd': return 'dryrun';
739
+ default: return 'auto';
740
+ }
741
+ };
742
+
743
+ console.log();
744
+ return await selectOption(MSG.CONFIRM_MODE, choices, fallback);
745
+ }
746
+
747
+ async function confirmItem(itemPath, itemSize) {
748
+ const displayPath = itemPath.replace(HOME, '~');
749
+ const shortPath = displayPath.length > 45 ? '...' + displayPath.slice(-42) : displayPath;
750
+ const itemDisplay = `${shortPath} ${chalk.dim('(' + formatSize(itemSize) + ')')}`;
751
+
752
+ const choices = [
753
+ { name: LANG_CODE === 'pt' ? 'Sim' : 'Yes', value: 'yes' },
754
+ { name: LANG_CODE === 'pt' ? 'Nao' : 'No', value: 'no' },
755
+ { name: LANG_CODE === 'pt' ? 'Aceitar restantes' : 'Accept all remaining', value: 'all' }
756
+ ];
757
+
758
+ const fallback = async () => {
759
+ console.log();
760
+ console.log(` ${chalk.yellow(MSG.CONFIRM_ITEM)} ${chalk.white(shortPath)} ${chalk.dim('(' + formatSize(itemSize) + ')')}`);
761
+ const answer = await prompt(` ${chalk.dim(MSG.CONFIRM_YES_NO_ALL)} `);
762
+ const yesKeys = LANG_CODE === 'pt' ? ['s', 'sim'] : ['y', 'yes'];
763
+ if (['a'].includes(answer.toLowerCase())) return 'all';
764
+ if (yesKeys.includes(answer.toLowerCase())) return 'yes';
765
+ return 'no';
766
+ };
767
+
768
+ console.log();
769
+ return await selectOption(`${MSG.CONFIRM_ITEM} ${itemDisplay}`, choices, fallback);
770
+ }
771
+
772
+ function showCleanupSummary(deleted, skipped, freedSize, mode) {
773
+ console.log();
774
+ console.log(chalk.white(' ' + '='.repeat(50)));
775
+ console.log();
776
+
777
+ if (mode === 'dryrun') {
778
+ console.log(` ${chalk.blue(figures.info)} ${chalk.white(MSG.DRY_RUN_MODE)}`);
779
+ console.log();
780
+ console.log(` ${figures.bullet} ${MSG.SUMMARY_WOULD_FREE}: ${chalk.green(formatSize(freedSize))}`);
781
+ } else {
782
+ console.log(` ${figures.tick} ${MSG.SUMMARY_DELETED}: ${chalk.green(deleted)}`);
783
+ console.log(` ${figures.cross} ${MSG.SUMMARY_SKIPPED}: ${chalk.yellow(skipped)}`);
784
+ console.log(` ${figures.arrowRight} ${MSG.SUMMARY_FREED}: ${chalk.green(formatSize(freedSize))}`);
785
+ }
786
+
787
+ console.log();
788
+ }
789
+
790
+ // ============================================================================
791
+ // PREMIUM HEADER WITH GRADIENT
792
+ // ============================================================================
793
+
297
794
  function header() {
298
795
  clearScreen();
299
796
  const date = new Date().toLocaleDateString('en-GB', {
@@ -301,44 +798,189 @@ function header() {
301
798
  });
302
799
  const time = new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
303
800
 
304
- console.log();
305
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
306
- console.log(`${c.cyan}║${c.reset}${c.white} ARIS MAC CLEANER v${VERSION} ${c.reset}${c.cyan}║${c.reset}`);
307
- console.log(`${c.cyan}║${c.reset}${c.dim} ${date} ${BULLET} ${time} ${c.reset}${c.cyan}║${c.reset}`);
308
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
309
- console.log();
801
+ const titleBox = `
802
+ ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
803
+ ${chalk.cyan('|')}${chalk.white(' ARIS MAC CLEANER v' + VERSION + ' ')}${chalk.cyan('|')}
804
+ ${chalk.cyan('|')}${chalk.gray(' ' + date + ' ' + figures.bullet + ' ' + time + ' ')}${chalk.cyan('|')}
805
+ ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
806
+ `;
807
+
808
+ console.log(gradient.pastel.multiline(titleBox));
809
+
810
+ // Show mode indicators
811
+ if (DRY_RUN) {
812
+ console.log(chalk.bgYellow.black(` ${figures.warning} ${MSG.DRY_RUN_MODE} `));
813
+ console.log();
814
+ }
815
+ if (SECURE_DELETE) {
816
+ console.log(chalk.bgRed.white(` ${figures.warning} ${MSG.SECURE_DELETE_MODE} `));
817
+ console.log();
818
+ }
310
819
  }
311
820
 
312
821
  function sectionHeader(title) {
313
- console.log(`${c.white}┌─────────────────────────────────────────────────────────────────┐${c.reset}`);
314
- console.log(`${c.white}│${c.reset} ${c.blue}▶ ${title}${c.reset}`);
315
- console.log(`${c.white}└─────────────────────────────────────────────────────────────────┘${c.reset}`);
822
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
823
+ console.log(chalk.white('|') + ' ' + chalk.cyan(figures.pointer + ' ' + title));
824
+ console.log(chalk.white('+' + '-'.repeat(65) + '+'));
316
825
  console.log();
317
826
  }
318
827
 
319
- function prompt(question) {
828
+ // ============================================================================
829
+ // AUTO-UPDATE CHECKER
830
+ // ============================================================================
831
+
832
+ async function checkForUpdates() {
320
833
  return new Promise((resolve) => {
321
- const rl = readline.createInterface({
322
- input: process.stdin,
323
- output: process.stdout
834
+ const req = https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5000 }, (res) => {
835
+ let data = '';
836
+ res.on('data', chunk => data += chunk);
837
+ res.on('end', () => {
838
+ try {
839
+ const json = JSON.parse(data);
840
+ const latestVersion = json.version;
841
+ if (latestVersion && latestVersion !== VERSION) {
842
+ resolve({ hasUpdate: true, version: latestVersion });
843
+ } else {
844
+ resolve({ hasUpdate: false });
845
+ }
846
+ } catch (e) {
847
+ resolve({ hasUpdate: false });
848
+ }
849
+ });
324
850
  });
325
- rl.question(question, (answer) => {
326
- rl.close();
327
- resolve(answer.trim());
851
+
852
+ req.on('error', () => resolve({ hasUpdate: false }));
853
+ req.on('timeout', () => {
854
+ req.destroy();
855
+ resolve({ hasUpdate: false });
328
856
  });
329
857
  });
330
858
  }
331
859
 
332
- async function pressEnter() {
333
- console.log();
334
- await prompt(` ${c.dim}${MSG.PRESS_ENTER}...${c.reset} `);
860
+ async function showUpdateNotification() {
861
+ const update = await checkForUpdates();
862
+ if (update.hasUpdate) {
863
+ console.log();
864
+ console.log(chalk.bgGreen.black(` ${figures.star} ${MSG.UPDATE_AVAILABLE}: v${update.version} `));
865
+ console.log(chalk.dim(` npm update -g ${PACKAGE_NAME}`));
866
+ console.log();
867
+ }
335
868
  }
336
869
 
337
- // ══════════════════════════════════════════════════════════════════════════════
338
- // SYSTEM DIAGNOSTICS
339
- // ══════════════════════════════════════════════════════════════════════════════
870
+ // ============================================================================
871
+ // BACKUP SYSTEM
872
+ // ============================================================================
873
+
874
+ function createBackup(items) {
875
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
876
+ const backupFile = path.join(BACKUP_DIR, `backup-${timestamp}.json`);
877
+
878
+ const backupData = {
879
+ timestamp: new Date().toISOString(),
880
+ version: VERSION,
881
+ items: items.map(item => ({
882
+ path: item.path,
883
+ name: item.name,
884
+ size: item.size,
885
+ category: item.category
886
+ }))
887
+ };
888
+
889
+ fs.writeFileSync(backupFile, JSON.stringify(backupData, null, 2));
890
+ return backupFile;
891
+ }
340
892
 
341
- let systemStats = {};
893
+ function listBackups() {
894
+ if (!fs.existsSync(BACKUP_DIR)) return [];
895
+ return fs.readdirSync(BACKUP_DIR)
896
+ .filter(f => f.endsWith('.json'))
897
+ .sort()
898
+ .reverse()
899
+ .slice(0, 10);
900
+ }
901
+
902
+ // ============================================================================
903
+ // SECURE DELETE
904
+ // ============================================================================
905
+
906
+ function secureDelete(filePath) {
907
+ if (!fs.existsSync(filePath)) return;
908
+
909
+ const stats = fs.statSync(filePath);
910
+
911
+ if (stats.isDirectory()) {
912
+ // For directories, just use rm -rf (secure delete of dirs is complex)
913
+ fs.rmSync(filePath, { recursive: true, force: true });
914
+ return;
915
+ }
916
+
917
+ // Overwrite file content 3 times before deletion
918
+ const size = stats.size;
919
+ const fd = fs.openSync(filePath, 'w');
920
+
921
+ // Pass 1: zeros
922
+ const zeros = Buffer.alloc(Math.min(size, 1024 * 1024), 0);
923
+ let written = 0;
924
+ while (written < size) {
925
+ fs.writeSync(fd, zeros, 0, Math.min(zeros.length, size - written));
926
+ written += zeros.length;
927
+ }
928
+
929
+ // Pass 2: ones
930
+ fs.lseekSync ? fs.lseekSync(fd, 0, 0) : null;
931
+ const ones = Buffer.alloc(Math.min(size, 1024 * 1024), 0xFF);
932
+ written = 0;
933
+ while (written < size) {
934
+ fs.writeSync(fd, ones, 0, Math.min(ones.length, size - written));
935
+ written += ones.length;
936
+ }
937
+
938
+ // Pass 3: random
939
+ const random = crypto.randomBytes(Math.min(size, 1024 * 1024));
940
+ written = 0;
941
+ while (written < size) {
942
+ fs.writeSync(fd, random, 0, Math.min(random.length, size - written));
943
+ written += random.length;
944
+ }
945
+
946
+ fs.closeSync(fd);
947
+ fs.unlinkSync(filePath);
948
+ }
949
+
950
+ function deleteItem(itemPath) {
951
+ if (DRY_RUN) {
952
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would delete: ${itemPath}`));
953
+ return true;
954
+ }
955
+
956
+ try {
957
+ if (SECURE_DELETE) {
958
+ secureDelete(itemPath);
959
+ } else {
960
+ fs.rmSync(itemPath, { recursive: true, force: true });
961
+ }
962
+ return true;
963
+ } catch (e) {
964
+ return false;
965
+ }
966
+ }
967
+
968
+ // ============================================================================
969
+ // PROGRESS BAR
970
+ // ============================================================================
971
+
972
+ function createProgressBar(total, format) {
973
+ return new cliProgress.SingleBar({
974
+ format: format || ' {bar} | {percentage}% | {value}/{total}',
975
+ barCompleteChar: '\u2588',
976
+ barIncompleteChar: '\u2591',
977
+ hideCursor: true
978
+ }, cliProgress.Presets.shades_classic);
979
+ }
980
+
981
+ // ============================================================================
982
+ // SYSTEM DIAGNOSTICS
983
+ // ============================================================================
342
984
 
343
985
  function getSystemStats() {
344
986
  // Disk
@@ -350,11 +992,11 @@ function getSystemStats() {
350
992
  systemStats.diskFreeBlocks = parseInt(diskRaw[3]) || 0;
351
993
 
352
994
  if (systemStats.diskPercent < 50) {
353
- systemStats.diskStatus = `${c.green}${CHECK} ${MSG.EXCELLENT}${c.reset}`;
995
+ systemStats.diskStatus = chalk.green(`${figures.tick} ${MSG.EXCELLENT}`);
354
996
  } else if (systemStats.diskPercent < 80) {
355
- systemStats.diskStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
997
+ systemStats.diskStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
356
998
  } else {
357
- systemStats.diskStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
999
+ systemStats.diskStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
358
1000
  }
359
1001
 
360
1002
  // RAM
@@ -362,11 +1004,11 @@ function getSystemStats() {
362
1004
  systemStats.ramUsedPercent = 100 - ramFreePercent;
363
1005
 
364
1006
  if (systemStats.ramUsedPercent < 70) {
365
- systemStats.ramStatus = `${c.green}${CHECK} OK${c.reset}`;
1007
+ systemStats.ramStatus = chalk.green(`${figures.tick} OK`);
366
1008
  } else if (systemStats.ramUsedPercent < 90) {
367
- systemStats.ramStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
1009
+ systemStats.ramStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
368
1010
  } else {
369
- systemStats.ramStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
1011
+ systemStats.ramStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
370
1012
  }
371
1013
 
372
1014
  // Swap
@@ -374,11 +1016,11 @@ function getSystemStats() {
374
1016
  systemStats.swapUsed = parseInt(swapStr) || 0;
375
1017
 
376
1018
  if (systemStats.swapUsed < 2000) {
377
- systemStats.swapStatus = `${c.green}${CHECK} OK${c.reset}`;
1019
+ systemStats.swapStatus = chalk.green(`${figures.tick} OK`);
378
1020
  } else if (systemStats.swapUsed < 8000) {
379
- systemStats.swapStatus = `${c.yellow}! Alto${c.reset}`;
1021
+ systemStats.swapStatus = chalk.yellow(`${figures.warning} High`);
380
1022
  } else {
381
- systemStats.swapStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
1023
+ systemStats.swapStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
382
1024
  }
383
1025
 
384
1026
  // CPU
@@ -386,20 +1028,20 @@ function getSystemStats() {
386
1028
  systemStats.cpuUsed = Math.round(100 - cpuIdle);
387
1029
 
388
1030
  if (systemStats.cpuUsed < 50) {
389
- systemStats.cpuStatus = `${c.green}${CHECK} OK${c.reset}`;
1031
+ systemStats.cpuStatus = chalk.green(`${figures.tick} OK`);
390
1032
  } else if (systemStats.cpuUsed < 80) {
391
- systemStats.cpuStatus = `${c.yellow}! ${MSG.ATTENTION}${c.reset}`;
1033
+ systemStats.cpuStatus = chalk.yellow(`${figures.warning} ${MSG.ATTENTION}`);
392
1034
  } else {
393
- systemStats.cpuStatus = `${c.red}${CROSS} ${MSG.CRITICAL}${c.reset}`;
1035
+ systemStats.cpuStatus = chalk.red(`${figures.cross} ${MSG.CRITICAL}`);
394
1036
  }
395
1037
  }
396
1038
 
397
1039
  function showDiagnostics() {
398
1040
  sectionHeader(MSG.DIAGNOSE);
399
- console.log(` ${BULLET} ${c.white}${MSG.DISK}${c.reset} ${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB ${systemStats.diskStatus}`);
400
- console.log(` ${BULLET} ${c.white}${MSG.RAM}${c.reset} ${systemStats.ramUsedPercent}% ${MSG.IN_USE} ${systemStats.ramStatus}`);
401
- console.log(` ${BULLET} ${c.white}${MSG.SWAP}${c.reset} ${systemStats.swapUsed}MB ${MSG.IN_USE} ${systemStats.swapStatus}`);
402
- console.log(` ${BULLET} ${c.white}${MSG.CPU}${c.reset} ${systemStats.cpuUsed}% ${MSG.IN_USE} ${systemStats.cpuStatus}`);
1041
+ console.log(` ${figures.bullet} ${chalk.white(MSG.DISK)} ${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB ${systemStats.diskStatus}`);
1042
+ console.log(` ${figures.bullet} ${chalk.white(MSG.RAM)} ${systemStats.ramUsedPercent}% ${MSG.IN_USE} ${systemStats.ramStatus}`);
1043
+ console.log(` ${figures.bullet} ${chalk.white(MSG.SWAP)} ${systemStats.swapUsed}MB ${MSG.IN_USE} ${systemStats.swapStatus}`);
1044
+ console.log(` ${figures.bullet} ${chalk.white(MSG.CPU)} ${systemStats.cpuUsed}% ${MSG.IN_USE} ${systemStats.cpuStatus}`);
403
1045
  console.log();
404
1046
  }
405
1047
 
@@ -412,96 +1054,1223 @@ function calculateHealthScore() {
412
1054
  if (systemStats.swapUsed > 8000) score -= 20;
413
1055
  else if (systemStats.swapUsed > 4000) score -= 10;
414
1056
 
415
- let color = c.green;
1057
+ let color = chalk.green;
416
1058
  let text = MSG.EXCELLENT;
417
- if (score < 60) { color = c.red; text = MSG.ATTENTION; }
418
- else if (score < 80) { color = c.yellow; text = MSG.GOOD; }
1059
+ if (score < 60) { color = chalk.red; text = MSG.ATTENTION; }
1060
+ else if (score < 80) { color = chalk.yellow; text = MSG.GOOD; }
419
1061
 
420
1062
  return { score, color, text };
421
1063
  }
422
1064
 
1065
+ // ============================================================================
1066
+ // SOUND NOTIFICATION
1067
+ // ============================================================================
1068
+
1069
+ function playNotificationSound() {
1070
+ // Use macOS native sound
1071
+ exec('afplay /System/Library/Sounds/Glass.aiff &');
1072
+
1073
+ // Also show system notification
1074
+ notifier.notify({
1075
+ title: 'Aris Mac Cleaner',
1076
+ message: MSG.CLEANUP_COMPLETE,
1077
+ sound: true,
1078
+ icon: path.join(__dirname, 'icon.png')
1079
+ });
1080
+ }
1081
+
1082
+ // ============================================================================
1083
+ // SMART RECOMMENDATIONS
1084
+ // ============================================================================
1085
+
1086
+ function getSmartRecommendations() {
1087
+ const recommendations = [];
1088
+
1089
+ // Check disk usage
1090
+ if (systemStats.diskPercent > 80) {
1091
+ recommendations.push({
1092
+ priority: 'high',
1093
+ icon: figures.warning,
1094
+ text: LANG_CODE === 'pt'
1095
+ ? 'Disco quase cheio! Considera apagar ficheiros grandes ou desinstalar apps.'
1096
+ : 'Disk almost full! Consider deleting large files or uninstalling apps.'
1097
+ });
1098
+ }
1099
+
1100
+ // Check RAM
1101
+ if (systemStats.ramUsedPercent > 85) {
1102
+ recommendations.push({
1103
+ priority: 'high',
1104
+ icon: figures.warning,
1105
+ text: LANG_CODE === 'pt'
1106
+ ? 'Memoria RAM muito usada. Usa o otimizador de memoria ou mata processos pesados.'
1107
+ : 'RAM heavily used. Use memory optimizer or kill heavy processes.'
1108
+ });
1109
+ }
1110
+
1111
+ // Check swap
1112
+ if (systemStats.swapUsed > 5000) {
1113
+ recommendations.push({
1114
+ priority: 'medium',
1115
+ icon: figures.info,
1116
+ text: LANG_CODE === 'pt'
1117
+ ? 'Swap elevado. Considera reiniciar o Mac para limpar.'
1118
+ : 'High swap usage. Consider restarting Mac to clear.'
1119
+ });
1120
+ }
1121
+
1122
+ // Check history for patterns
1123
+ if (fs.existsSync(HISTORY_FILE)) {
1124
+ const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
1125
+ const lastWeek = history.filter(l => {
1126
+ const date = new Date(l.split(' | ')[0]);
1127
+ return (Date.now() - date.getTime()) < 7 * 24 * 60 * 60 * 1000;
1128
+ });
1129
+
1130
+ if (lastWeek.length === 0) {
1131
+ recommendations.push({
1132
+ priority: 'low',
1133
+ icon: figures.star,
1134
+ text: LANG_CODE === 'pt'
1135
+ ? 'Nao fizeste limpeza esta semana. Considera fazer uma limpeza rapida.'
1136
+ : 'No cleanup this week. Consider running a quick clean.'
1137
+ });
1138
+ }
1139
+ }
1140
+
1141
+ // Check for large caches
1142
+ const chromeCache = getSize('~/Library/Caches/Google');
1143
+ if (chromeCache > 500) {
1144
+ recommendations.push({
1145
+ priority: 'medium',
1146
+ icon: figures.bullet,
1147
+ text: LANG_CODE === 'pt'
1148
+ ? `Cache do Chrome com ${formatSize(chromeCache)}. Limpa os browsers.`
1149
+ : `Chrome cache at ${formatSize(chromeCache)}. Clean browsers.`
1150
+ });
1151
+ }
1152
+
1153
+ // Check Xcode
1154
+ const xcodeData = getSize('~/Library/Developer/Xcode/DerivedData');
1155
+ if (xcodeData > 1000) {
1156
+ recommendations.push({
1157
+ priority: 'medium',
1158
+ icon: figures.bullet,
1159
+ text: LANG_CODE === 'pt'
1160
+ ? `Xcode DerivedData com ${formatSize(xcodeData)}. Pode ser limpo.`
1161
+ : `Xcode DerivedData at ${formatSize(xcodeData)}. Can be cleaned.`
1162
+ });
1163
+ }
1164
+
1165
+ return recommendations;
1166
+ }
1167
+
1168
+ function showRecommendations() {
1169
+ const recs = getSmartRecommendations();
1170
+
1171
+ if (recs.length === 0) return;
1172
+
1173
+ sectionHeader(MSG.RECOMMENDATIONS);
1174
+
1175
+ recs.forEach(rec => {
1176
+ const color = rec.priority === 'high' ? chalk.red :
1177
+ rec.priority === 'medium' ? chalk.yellow : chalk.cyan;
1178
+ console.log(` ${color(rec.icon)} ${rec.text}`);
1179
+ });
1180
+ console.log();
1181
+ }
1182
+
1183
+ // ============================================================================
1184
+ // FINAL REPORT
1185
+ // ============================================================================
1186
+
423
1187
  function showFinalReport(freed) {
424
1188
  const diskFreeAfter = exec("df -h / | tail -1").split(/\s+/)[3];
425
1189
  const health = calculateHealthScore();
426
1190
 
427
- console.log(`${c.cyan}╔══════════════════════════════════════════════════════════════════╗${c.reset}`);
428
- console.log(`${c.cyan}║${c.reset}${c.white} ${MSG.FINAL_REPORT} ${c.reset}${c.cyan}║${c.reset}`);
429
- console.log(`${c.cyan}╠══════════════════════════════════════════════════════════════════╣${c.reset}`);
430
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
431
- console.log(`${c.cyan}║${c.reset} ${c.white}${MSG.DISK}${c.reset} ${c.cyan}║${c.reset}`);
432
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${MSG.AVAILABLE}: ${c.green}${diskFreeAfter}${c.reset} ${c.cyan}║${c.reset}`);
433
- if (freed > 0) {
434
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${MSG.FREED}: ${c.green}+${formatSize(freed)}${c.reset} ${c.cyan}║${c.reset}`);
435
- }
436
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
437
- console.log(`${c.cyan}║${c.reset} ${c.white}${MSG.HEALTH_SCORE}${c.reset} ${c.cyan}║${c.reset}`);
438
- console.log(`${c.cyan}║${c.reset} ${ARROW} ${health.color}${health.score}/100 - ${health.text}${c.reset} ${c.cyan}║${c.reset}`);
439
- console.log(`${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
440
- console.log(`${c.cyan}╚══════════════════════════════════════════════════════════════════╝${c.reset}`);
441
- console.log();
1191
+ const reportBox = `
1192
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
1193
+ ${chalk.cyan('|')}${chalk.white(' ' + MSG.FINAL_REPORT + ' ')}${chalk.cyan('|')}
1194
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
1195
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
1196
+ ${chalk.cyan('|')} ${chalk.white(MSG.DISK)} ${chalk.cyan('|')}
1197
+ ${chalk.cyan('|')} ${figures.arrowRight} ${MSG.AVAILABLE}: ${chalk.green(diskFreeAfter)} ${chalk.cyan('|')}
1198
+ ${freed > 0 ? `${chalk.cyan('|')} ${figures.arrowRight} ${MSG.FREED}: ${chalk.green('+' + formatSize(freed))} ${chalk.cyan('|')}` : ''}
1199
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
1200
+ ${chalk.cyan('|')} ${chalk.white(MSG.HEALTH_SCORE)} ${chalk.cyan('|')}
1201
+ ${chalk.cyan('|')} ${figures.arrowRight} ${health.color(health.score + '/100 - ' + health.text)} ${chalk.cyan('|')}
1202
+ ${chalk.cyan('|')} ${chalk.cyan('|')}
1203
+ ${chalk.cyan('+')}${chalk.cyan('='.repeat(68))}${chalk.cyan('+')}
1204
+ `;
1205
+
1206
+ console.log(gradient.pastel.multiline(reportBox));
442
1207
 
443
1208
  if (systemStats.swapUsed > 5000 || systemStats.ramUsedPercent > 85) {
444
- console.log(`${c.yellow} ${MSG.RESTART_REC}${c.reset}`);
1209
+ console.log(chalk.yellow(`${figures.warning} ${MSG.RESTART_REC}`));
445
1210
  console.log();
446
1211
  }
447
1212
 
448
- console.log(`${c.green}${CHECK} ${MSG.READY}${c.reset}`);
1213
+ console.log(chalk.green(`${figures.tick} ${MSG.READY}`));
1214
+
1215
+ // Store results for export
1216
+ lastCleanupResults = {
1217
+ timestamp: new Date().toISOString(),
1218
+ freed: freed,
1219
+ diskFree: diskFreeAfter,
1220
+ healthScore: health.score,
1221
+ healthText: health.text
1222
+ };
449
1223
 
450
1224
  logCleanup(freed, health.score);
1225
+
1226
+ // Play notification sound
1227
+ playNotificationSound();
451
1228
  }
452
1229
 
453
- // ══════════════════════════════════════════════════════════════════════════════
454
- // OPTION 1: QUICK CLEAN
455
- // ══════════════════════════════════════════════════════════════════════════════
1230
+ // ============================================================================
1231
+ // DISK ANALYZER
1232
+ // ============================================================================
456
1233
 
457
- async function quickClean() {
1234
+ async function diskAnalyzer() {
458
1235
  header();
459
- getSystemStats();
460
- showDiagnostics();
461
- sectionHeader(MSG.CATEGORIES);
1236
+ sectionHeader(MSG.DISK_ANALYSIS);
1237
+
1238
+ const spinner = ora(MSG.SCANNING).start();
1239
+
1240
+ // Analyze key directories
1241
+ const dirs = [
1242
+ { path: HOME, name: 'Home' },
1243
+ { path: path.join(HOME, 'Library'), name: 'Library' },
1244
+ { path: path.join(HOME, 'Documents'), name: 'Documents' },
1245
+ { path: path.join(HOME, 'Downloads'), name: 'Downloads' },
1246
+ { path: path.join(HOME, 'Desktop'), name: 'Desktop' },
1247
+ { path: path.join(HOME, 'Movies'), name: 'Movies' },
1248
+ { path: path.join(HOME, 'Music'), name: 'Music' },
1249
+ { path: path.join(HOME, 'Pictures'), name: 'Pictures' },
1250
+ { path: '/Applications', name: 'Applications' }
1251
+ ];
1252
+
1253
+ const sizes = [];
1254
+
1255
+ for (const dir of dirs) {
1256
+ if (fs.existsSync(dir.path)) {
1257
+ dir.size = getSize(dir.path);
1258
+ sizes.push(dir);
1259
+ }
1260
+ }
1261
+
1262
+ // Sort by size
1263
+ sizes.sort((a, b) => b.size - a.size);
1264
+
1265
+ spinner.stop();
1266
+
1267
+ // Calculate max for bar scaling
1268
+ const maxSize = Math.max(...sizes.map(s => s.size));
1269
+ const barWidth = 40;
1270
+
1271
+ console.log(chalk.white(' Directory Usage:'));
1272
+ console.log();
1273
+
1274
+ sizes.forEach((dir, i) => {
1275
+ const barLength = Math.round((dir.size / maxSize) * barWidth);
1276
+ const bar = chalk.cyan('\u2588'.repeat(barLength)) + chalk.gray('\u2591'.repeat(barWidth - barLength));
1277
+ const sizeStr = formatSize(dir.size).padStart(10);
1278
+
1279
+ console.log(` ${chalk.white(dir.name.padEnd(15))} ${bar} ${chalk.green(sizeStr)}`);
1280
+ });
1281
+
1282
+ console.log();
1283
+
1284
+ // Show Library breakdown
1285
+ console.log(chalk.white(' Library Breakdown:'));
1286
+ console.log();
1287
+
1288
+ const libraryDirs = [
1289
+ { path: path.join(HOME, 'Library/Caches'), name: 'Caches' },
1290
+ { path: path.join(HOME, 'Library/Application Support'), name: 'App Support' },
1291
+ { path: path.join(HOME, 'Library/Containers'), name: 'Containers' },
1292
+ { path: path.join(HOME, 'Library/Developer'), name: 'Developer' },
1293
+ { path: path.join(HOME, 'Library/Logs'), name: 'Logs' }
1294
+ ];
1295
+
1296
+ for (const dir of libraryDirs) {
1297
+ if (fs.existsSync(dir.path)) {
1298
+ const size = getSize(dir.path);
1299
+ const sizeStr = formatSize(size).padStart(10);
1300
+ console.log(` ${figures.bullet} ${chalk.dim(dir.name.padEnd(20))} ${chalk.yellow(sizeStr)}`);
1301
+ }
1302
+ }
1303
+
1304
+ await pressEnter();
1305
+ }
1306
+
1307
+ // ============================================================================
1308
+ // PRIVACY SWEEP
1309
+ // ============================================================================
1310
+
1311
+ async function privacySweep() {
1312
+ header();
1313
+ sectionHeader(MSG.OPT_PRIVACY);
462
1314
 
463
1315
  const items = [];
464
- let totalSize = 0;
465
1316
 
466
- function addItem(p, name, category) {
467
- const fullPath = p.replace(/^~/, HOME);
468
- if (isExcluded(fullPath)) return;
1317
+ // Browser data
1318
+ const browserPaths = [
1319
+ { path: '~/Library/Cookies', name: 'System Cookies' },
1320
+ { path: '~/Library/Application Support/Google/Chrome/Default/Cookies', name: 'Chrome Cookies' },
1321
+ { path: '~/Library/Application Support/Google/Chrome/Default/History', name: 'Chrome History' },
1322
+ { path: '~/Library/Application Support/Google/Chrome/Default/Login Data', name: 'Chrome Saved Logins' },
1323
+ { path: '~/Library/Safari/History.db', name: 'Safari History' },
1324
+ { path: '~/Library/Safari/Downloads.plist', name: 'Safari Downloads' },
1325
+ { path: '~/Library/Safari/LocalStorage', name: 'Safari Local Storage' },
1326
+ { path: '~/Library/Application Support/Firefox/Profiles', name: 'Firefox Profile Data' },
1327
+ { path: '~/.recently-used', name: 'Recent Files' },
1328
+ { path: '~/Library/Application Support/com.apple.sharedfilelist', name: 'Recent Items' },
1329
+ { path: '~/Library/Preferences/com.apple.recentitems.plist', name: 'Recent Items Plist' }
1330
+ ];
1331
+
1332
+ console.log(chalk.white(' Privacy-sensitive items found:'));
1333
+ console.log();
469
1334
 
470
- const size = getSize(p);
471
- if (size > 0) {
472
- items.push({ path: fullPath, name, category, size, selected: true });
473
- totalSize += size;
1335
+ browserPaths.forEach((item, i) => {
1336
+ const fullPath = item.path.replace(/^~/, HOME);
1337
+ if (fs.existsSync(fullPath)) {
1338
+ const size = getSize(item.path);
1339
+ items.push({ ...item, fullPath, size });
1340
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(formatSize(size))}`);
474
1341
  }
1342
+ });
1343
+
1344
+ if (items.length === 0) {
1345
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1346
+ await pressEnter();
1347
+ return;
475
1348
  }
476
1349
 
477
- // System Caches
478
- addItem('~/Library/Caches/Google', 'Google/Chrome Cache', 'system');
479
- addItem('~/Library/Caches/com.spotify.client', 'Spotify Cache', 'system');
480
- addItem('~/Library/Caches/com.apple.Safari', 'Safari Cache', 'system');
481
- addItem('~/Library/Logs', 'System Logs', 'system');
1350
+ console.log();
482
1351
 
483
- // Dev Tools
484
- addItem('~/Library/Caches/CocoaPods', 'CocoaPods Cache', 'dev');
485
- addItem('~/Library/Caches/Homebrew', 'Homebrew Cache', 'dev');
486
- addItem('~/Library/Caches/typescript', 'TypeScript Cache', 'dev');
487
- addItem('~/Library/Caches/node-gyp', 'Node-gyp Cache', 'dev');
488
- addItem('~/Library/Caches/pip', 'Pip Cache', 'dev');
489
- addItem('~/Library/Caches/yarn', 'Yarn Cache', 'dev');
490
- addItem('~/Library/Caches/pnpm', 'pnpm Cache', 'dev');
491
- addItem('~/.npm', 'npm Cache', 'dev');
492
- addItem('~/Library/Application Support/Code/User/workspaceStorage', 'VS Code Workspace', 'dev');
493
- addItem('~/Library/Developer/Xcode/DerivedData', 'Xcode DerivedData', 'dev');
1352
+ // Build checkbox choices for inquirer
1353
+ const itemChoices = items.map((item, i) => ({
1354
+ name: `${item.name.padEnd(30)} ${chalk.dim(formatSize(item.size))}`,
1355
+ value: item.fullPath,
1356
+ short: item.name
1357
+ }));
494
1358
 
495
- // Browsers
496
- addItem('~/Library/Application Support/Google/GoogleUpdater/crx_cache', 'Google Updater', 'browsers');
497
- addItem('~/Library/Application Support/Google/Chrome/component_crx_cache', 'Chrome Components', 'browsers');
1359
+ // Fallback for text-based selection
1360
+ const fallbackSelect = async () => {
1361
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [a=${MSG.ALL}/numbers/n=${MSG.CANCEL}]:`));
1362
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
498
1363
 
499
- // Claude versions
500
- const claudeVersionsPath = path.join(HOME, '.local/share/claude/versions');
501
- if (fs.existsSync(claudeVersionsPath)) {
502
- try {
503
- const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
504
- if (versions.length > 1) {
1364
+ if (choice.toLowerCase() === 'n' || choice === '') return [];
1365
+
1366
+ if (choice.toLowerCase() === 'a') {
1367
+ return items.map(item => item.fullPath);
1368
+ }
1369
+
1370
+ const selected = [];
1371
+ const parts = choice.split(',');
1372
+ parts.forEach(part => {
1373
+ const idx = parseInt(part.trim()) - 1;
1374
+ if (idx >= 0 && idx < items.length) selected.push(items[idx].fullPath);
1375
+ });
1376
+ return selected;
1377
+ };
1378
+
1379
+ const selectedPaths = await selectMultiple(
1380
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to select, enter to confirm)')}`,
1381
+ itemChoices,
1382
+ fallbackSelect
1383
+ );
1384
+
1385
+ if (selectedPaths.length === 0) {
1386
+ await pressEnter();
1387
+ return;
1388
+ }
1389
+
1390
+ // Mark selected items
1391
+ items.forEach(item => {
1392
+ item.selected = selectedPaths.includes(item.fullPath);
1393
+ });
1394
+
1395
+ const selectedItems = items.filter(i => i.selected);
1396
+ if (selectedItems.length === 0) {
1397
+ await pressEnter();
1398
+ return;
1399
+ }
1400
+
1401
+ // Ask confirmation mode
1402
+ const confirmMode = await askConfirmationMode();
1403
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
1404
+ let autoMode = confirmMode === 'auto';
1405
+
1406
+ let cleaned = 0;
1407
+ let deletedCount = 0;
1408
+ let skippedCount = 0;
1409
+
1410
+ if (autoMode && !isDryRun) {
1411
+ const spinner = ora(MSG.CLEANING).start();
1412
+
1413
+ for (const item of items) {
1414
+ if (!item.selected) continue;
1415
+ if (deleteItem(item.fullPath)) {
1416
+ cleaned += item.size;
1417
+ deletedCount++;
1418
+ }
1419
+ }
1420
+
1421
+ spinner.succeed(chalk.green(`${MSG.PRIVACY_CLEANED}: ${formatSize(cleaned)}`));
1422
+ } else {
1423
+ for (const item of items) {
1424
+ if (!item.selected) continue;
1425
+
1426
+ let shouldDelete = autoMode;
1427
+
1428
+ if (!autoMode && confirmMode === 'manual') {
1429
+ const answer = await confirmItem(item.fullPath, item.size);
1430
+ if (answer === 'all') {
1431
+ autoMode = true;
1432
+ shouldDelete = true;
1433
+ } else if (answer === 'yes') {
1434
+ shouldDelete = true;
1435
+ } else {
1436
+ shouldDelete = false;
1437
+ }
1438
+ }
1439
+
1440
+ if (isDryRun) {
1441
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${item.name} ${chalk.dim('(' + formatSize(item.size) + ')')}`);
1442
+ cleaned += item.size;
1443
+ deletedCount++;
1444
+ } else if (shouldDelete) {
1445
+ if (deleteItem(item.fullPath)) {
1446
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
1447
+ cleaned += item.size;
1448
+ deletedCount++;
1449
+ }
1450
+ } else {
1451
+ skippedCount++;
1452
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${item.name}`);
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ showCleanupSummary(deletedCount, skippedCount, cleaned, confirmMode);
1458
+ await pressEnter();
1459
+ }
1460
+
1461
+ // ============================================================================
1462
+ // MEMORY OPTIMIZER
1463
+ // ============================================================================
1464
+
1465
+ async function memoryOptimizer() {
1466
+ header();
1467
+ sectionHeader(MSG.OPT_MEMORY);
1468
+
1469
+ console.log(chalk.white(' Current Memory Status:'));
1470
+ console.log();
1471
+ console.log(` ${figures.bullet} RAM: ${systemStats.ramUsedPercent}% ${MSG.IN_USE}`);
1472
+ console.log(` ${figures.bullet} Swap: ${systemStats.swapUsed} MB ${MSG.IN_USE}`);
1473
+ console.log();
1474
+
1475
+ console.log(chalk.yellow(` ${figures.warning} This will run 'sudo purge' to free inactive memory.`));
1476
+ console.log(chalk.dim(' You may need to enter your password.'));
1477
+ console.log();
1478
+
1479
+ const fallbackConfirm = async () => {
1480
+ const answer = await prompt(` ${chalk.white('Continue? [y/n]:')} `);
1481
+ return answer.toLowerCase() === 'y';
1482
+ };
1483
+
1484
+ const confirmed = await confirmAction('Continue with memory optimization?', false, fallbackConfirm);
1485
+
1486
+ if (!confirmed) {
1487
+ await pressEnter();
1488
+ return;
1489
+ }
1490
+
1491
+ if (DRY_RUN) {
1492
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would run: sudo purge`));
1493
+ } else {
1494
+ const spinner = ora('Freeing memory...').start();
1495
+
1496
+ try {
1497
+ execSync('sudo purge', { stdio: 'inherit' });
1498
+ spinner.succeed(chalk.green(MSG.MEMORY_FREED));
1499
+ } catch (e) {
1500
+ spinner.fail(chalk.red('Failed to free memory'));
1501
+ }
1502
+ }
1503
+
1504
+ // Refresh stats
1505
+ getSystemStats();
1506
+ console.log();
1507
+ console.log(chalk.white(' Updated Memory Status:'));
1508
+ console.log(` ${figures.bullet} RAM: ${systemStats.ramUsedPercent}% ${MSG.IN_USE}`);
1509
+ console.log(` ${figures.bullet} Swap: ${systemStats.swapUsed} MB ${MSG.IN_USE}`);
1510
+
1511
+ await pressEnter();
1512
+ }
1513
+
1514
+ // ============================================================================
1515
+ // BENCHMARK MODE
1516
+ // ============================================================================
1517
+
1518
+ async function benchmarkMode() {
1519
+ header();
1520
+ sectionHeader(MSG.OPT_BENCHMARK);
1521
+
1522
+ console.log(chalk.white(' Running system benchmark...'));
1523
+ console.log();
1524
+
1525
+ const results = {
1526
+ timestamp: new Date().toISOString(),
1527
+ tests: []
1528
+ };
1529
+
1530
+ // Disk read speed
1531
+ const spinner1 = ora('Testing disk read speed...').start();
1532
+ const diskStart = Date.now();
1533
+ exec('dd if=/dev/zero of=/tmp/benchmark_test bs=1m count=100 2>/dev/null');
1534
+ const diskWrite = Date.now() - diskStart;
1535
+
1536
+ const diskReadStart = Date.now();
1537
+ exec('dd if=/tmp/benchmark_test of=/dev/null bs=1m 2>/dev/null');
1538
+ const diskRead = Date.now() - diskReadStart;
1539
+
1540
+ exec('rm /tmp/benchmark_test');
1541
+ spinner1.succeed(`Disk: Write ${Math.round(100000 / diskWrite)} MB/s, Read ${Math.round(100000 / diskRead)} MB/s`);
1542
+
1543
+ results.tests.push({
1544
+ name: 'Disk Write',
1545
+ value: Math.round(100000 / diskWrite),
1546
+ unit: 'MB/s'
1547
+ });
1548
+ results.tests.push({
1549
+ name: 'Disk Read',
1550
+ value: Math.round(100000 / diskRead),
1551
+ unit: 'MB/s'
1552
+ });
1553
+
1554
+ // CPU benchmark
1555
+ const spinner2 = ora('Testing CPU performance...').start();
1556
+ const cpuStart = Date.now();
1557
+ let cpuTest = 0;
1558
+ for (let i = 0; i < 10000000; i++) {
1559
+ cpuTest += Math.sqrt(i);
1560
+ }
1561
+ const cpuTime = Date.now() - cpuStart;
1562
+ spinner2.succeed(`CPU: ${cpuTime}ms for 10M sqrt operations`);
1563
+
1564
+ results.tests.push({
1565
+ name: 'CPU (10M sqrt)',
1566
+ value: cpuTime,
1567
+ unit: 'ms'
1568
+ });
1569
+
1570
+ // Memory benchmark
1571
+ const spinner3 = ora('Testing memory allocation...').start();
1572
+ const memStart = Date.now();
1573
+ const testArrays = [];
1574
+ for (let i = 0; i < 100; i++) {
1575
+ testArrays.push(new Array(1000000).fill(Math.random()));
1576
+ }
1577
+ const memTime = Date.now() - memStart;
1578
+ spinner3.succeed(`Memory: ${memTime}ms to allocate 100MB`);
1579
+
1580
+ results.tests.push({
1581
+ name: 'Memory (100MB alloc)',
1582
+ value: memTime,
1583
+ unit: 'ms'
1584
+ });
1585
+
1586
+ // Save results
1587
+ 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`;
1588
+ fs.appendFileSync(BENCHMARK_FILE, benchmarkLine);
1589
+
1590
+ console.log();
1591
+ console.log(chalk.green(`${figures.tick} ${MSG.BENCHMARK_COMPLETE}`));
1592
+
1593
+ // Show history if available
1594
+ if (fs.existsSync(BENCHMARK_FILE)) {
1595
+ const history = fs.readFileSync(BENCHMARK_FILE, 'utf8').split('\n').filter(l => l.trim());
1596
+ if (history.length > 1) {
1597
+ console.log();
1598
+ console.log(chalk.white(' Recent benchmarks:'));
1599
+ history.slice(-5).forEach(line => {
1600
+ console.log(chalk.dim(` ${line}`));
1601
+ });
1602
+ }
1603
+ }
1604
+
1605
+ await pressEnter();
1606
+ }
1607
+
1608
+ // ============================================================================
1609
+ // THERMAL MONITOR
1610
+ // ============================================================================
1611
+
1612
+ function getThermalStatus() {
1613
+ const output = exec('pmset -g therm 2>/dev/null');
1614
+
1615
+ // Parse thermal status from pmset output
1616
+ let status = 'cool';
1617
+ let level = 10;
1618
+ let emoji = '\u2744\uFE0F'; // snowflake
1619
+ let color = chalk.cyan;
1620
+ let tip = MSG.THERMAL_TIP_COOL;
1621
+
1622
+ const lowerOutput = output.toLowerCase();
1623
+
1624
+ if (lowerOutput.includes('trapping') || lowerOutput.includes('sleeping')) {
1625
+ status = 'critical';
1626
+ level = 90;
1627
+ emoji = '\uD83D\uDEA8'; // rotating light
1628
+ color = chalk.red;
1629
+ tip = MSG.THERMAL_TIP_CRITICAL;
1630
+ } else if (lowerOutput.includes('heavy')) {
1631
+ status = 'hot';
1632
+ level = 70;
1633
+ emoji = '\uD83D\uDD25'; // fire
1634
+ color = chalk.red;
1635
+ tip = MSG.THERMAL_TIP_HOT;
1636
+ } else if (lowerOutput.includes('moderate')) {
1637
+ status = 'warm';
1638
+ level = 50;
1639
+ emoji = '\uD83C\uDF24\uFE0F'; // sun behind small cloud
1640
+ color = chalk.yellow;
1641
+ tip = MSG.THERMAL_TIP_WARM;
1642
+ } else if (lowerOutput.includes('nominal')) {
1643
+ status = 'good';
1644
+ level = 30;
1645
+ emoji = '\u2705'; // check mark
1646
+ color = chalk.green;
1647
+ tip = MSG.THERMAL_TIP_GOOD;
1648
+ }
1649
+ // Default is cool (no thermal warnings)
1650
+
1651
+ const statusText = {
1652
+ cool: MSG.THERMAL_COOL,
1653
+ good: MSG.THERMAL_GOOD,
1654
+ warm: MSG.THERMAL_WARM,
1655
+ hot: MSG.THERMAL_HOT,
1656
+ critical: MSG.THERMAL_CRITICAL
1657
+ }[status];
1658
+
1659
+ return { status, level, emoji, color, statusText, tip, rawOutput: output };
1660
+ }
1661
+
1662
+ function drawThermalBox(thermal) {
1663
+ const boxWidth = 43;
1664
+ const barWidth = 20;
1665
+
1666
+ // Draw progress bar
1667
+ const filledBars = Math.round((thermal.level / 100) * barWidth);
1668
+ const emptyBars = barWidth - filledBars;
1669
+ const progressBar = '\u2588'.repeat(filledBars) + '\u2591'.repeat(emptyBars);
1670
+
1671
+ // Color the progress bar based on level
1672
+ let barColor = chalk.cyan;
1673
+ if (thermal.level >= 80) barColor = chalk.red;
1674
+ else if (thermal.level >= 60) barColor = chalk.red;
1675
+ else if (thermal.level >= 40) barColor = chalk.yellow;
1676
+ else if (thermal.level >= 20) barColor = chalk.green;
1677
+
1678
+ const lines = [
1679
+ chalk.cyan('\u256D' + '\u2500'.repeat(boxWidth) + '\u256E'),
1680
+ chalk.cyan('\u2502') + ' \uD83C\uDF21\uFE0F ' + chalk.white.bold(MSG.THERMAL_STATUS) + ' '.repeat(boxWidth - 21) + chalk.cyan('\u2502'),
1681
+ chalk.cyan('\u251C' + '\u2500'.repeat(boxWidth) + '\u2524'),
1682
+ chalk.cyan('\u2502') + ' Status: ' + thermal.emoji + ' ' + thermal.color(thermal.statusText) + ' '.repeat(boxWidth - 17 - thermal.statusText.length) + chalk.cyan('\u2502'),
1683
+ chalk.cyan('\u2502') + ' ' + MSG.THERMAL_LEVEL + ': ' + barColor(progressBar) + ' ' + thermal.level + '%' + ' '.repeat(boxWidth - 12 - barWidth - String(thermal.level).length - MSG.THERMAL_LEVEL.length) + chalk.cyan('\u2502'),
1684
+ chalk.cyan('\u2502') + ' '.repeat(boxWidth) + chalk.cyan('\u2502'),
1685
+ chalk.cyan('\u2502') + ' \uD83D\uDCA1 ' + MSG.THERMAL_TIP + ': ' + chalk.dim(thermal.tip.slice(0, boxWidth - 12)) + ' '.repeat(Math.max(0, boxWidth - 10 - MSG.THERMAL_TIP.length - thermal.tip.slice(0, boxWidth - 12).length)) + chalk.cyan('\u2502')
1686
+ ];
1687
+
1688
+ // Handle long tips with word wrap
1689
+ if (thermal.tip.length > boxWidth - 12) {
1690
+ const secondLine = thermal.tip.slice(boxWidth - 12);
1691
+ lines.push(chalk.cyan('\u2502') + ' ' + chalk.dim(secondLine) + ' '.repeat(Math.max(0, boxWidth - 5 - secondLine.length)) + chalk.cyan('\u2502'));
1692
+ }
1693
+
1694
+ lines.push(chalk.cyan('\u2570' + '\u2500'.repeat(boxWidth) + '\u256F'));
1695
+
1696
+ return lines.join('\n');
1697
+ }
1698
+
1699
+ function parsePowerMetrics(output) {
1700
+ const result = {
1701
+ cpuPower: null,
1702
+ gpuPower: null,
1703
+ anePower: null,
1704
+ cpuFreq: null
1705
+ };
1706
+
1707
+ // Parse CPU Power
1708
+ const cpuMatch = output.match(/CPU Power:\s*([\d.]+)\s*mW/i);
1709
+ if (cpuMatch) {
1710
+ result.cpuPower = parseFloat(cpuMatch[1]) / 1000; // Convert to Watts
1711
+ }
1712
+
1713
+ // Parse GPU Power
1714
+ const gpuMatch = output.match(/GPU Power:\s*([\d.]+)\s*mW/i);
1715
+ if (gpuMatch) {
1716
+ result.gpuPower = parseFloat(gpuMatch[1]) / 1000;
1717
+ }
1718
+
1719
+ // Parse ANE Power
1720
+ const aneMatch = output.match(/ANE Power:\s*([\d.]+)\s*mW/i);
1721
+ if (aneMatch) {
1722
+ result.anePower = parseFloat(aneMatch[1]) / 1000;
1723
+ }
1724
+
1725
+ // Parse CPU frequency
1726
+ const freqMatch = output.match(/CPU Average frequency as fraction of nominal:\s*([\d.]+)%/i);
1727
+ if (freqMatch) {
1728
+ result.cpuFreq = parseFloat(freqMatch[1]);
1729
+ }
1730
+
1731
+ return result;
1732
+ }
1733
+
1734
+ function getPowerLabel(watts) {
1735
+ if (watts === null) return { label: 'N/A', color: chalk.gray };
1736
+ if (watts < 2) return { label: MSG.THERMAL_LOW, color: chalk.green };
1737
+ if (watts < 5) return { label: MSG.THERMAL_MEDIUM, color: chalk.yellow };
1738
+ if (watts < 10) return { label: MSG.THERMAL_HIGH, color: chalk.red };
1739
+ return { label: MSG.THERMAL_VERY_HIGH, color: chalk.red };
1740
+ }
1741
+
1742
+ async function thermalMonitor() {
1743
+ header();
1744
+ sectionHeader(MSG.OPT_THERMAL);
1745
+
1746
+ const spinner = ora(MSG.SCANNING).start();
1747
+
1748
+ // Get thermal status
1749
+ const thermal = getThermalStatus();
1750
+
1751
+ spinner.stop();
1752
+
1753
+ // Display thermal status box
1754
+ console.log(drawThermalBox(thermal));
1755
+ console.log();
1756
+
1757
+ // Show raw output if verbose
1758
+ if (thermal.rawOutput) {
1759
+ console.log(chalk.dim(' Raw pmset output:'));
1760
+ thermal.rawOutput.split('\n').forEach(line => {
1761
+ if (line.trim()) {
1762
+ console.log(chalk.dim(' ' + line));
1763
+ }
1764
+ });
1765
+ console.log();
1766
+ }
1767
+
1768
+ // Ask about detailed power info
1769
+ console.log(chalk.yellow(` ${figures.info} ${MSG.THERMAL_DETAILED}`));
1770
+
1771
+ const fallbackThermal = async () => {
1772
+ const answer = await prompt(` ${chalk.white('[y/n]:')} `);
1773
+ return answer.toLowerCase() === 'y';
1774
+ };
1775
+
1776
+ const wantDetails = await confirmAction('View detailed power metrics?', false, fallbackThermal);
1777
+
1778
+ if (wantDetails) {
1779
+ console.log();
1780
+ const detailSpinner = ora('Reading power metrics...').start();
1781
+
1782
+ try {
1783
+ const powerOutput = execSync('sudo powermetrics --samplers cpu_power -i1 -n1 2>/dev/null', {
1784
+ encoding: 'utf8',
1785
+ stdio: ['pipe', 'pipe', 'pipe']
1786
+ });
1787
+
1788
+ detailSpinner.stop();
1789
+
1790
+ const power = parsePowerMetrics(powerOutput);
1791
+
1792
+ console.log();
1793
+ console.log(chalk.white(' ' + MSG.THERMAL_POWER + ' Consumption:'));
1794
+ console.log();
1795
+
1796
+ if (power.cpuPower !== null) {
1797
+ const cpuLabel = getPowerLabel(power.cpuPower);
1798
+ console.log(` ${figures.bullet} ${MSG.THERMAL_CPU_POWER}: ${chalk.cyan(power.cpuPower.toFixed(2) + ' W')} ${cpuLabel.color('(' + cpuLabel.label + ')')}`);
1799
+ }
1800
+
1801
+ if (power.gpuPower !== null) {
1802
+ const gpuLabel = getPowerLabel(power.gpuPower);
1803
+ console.log(` ${figures.bullet} ${MSG.THERMAL_GPU_POWER}: ${chalk.cyan(power.gpuPower.toFixed(2) + ' W')} ${gpuLabel.color('(' + gpuLabel.label + ')')}`);
1804
+ }
1805
+
1806
+ if (power.anePower !== null) {
1807
+ const aneLabel = getPowerLabel(power.anePower);
1808
+ console.log(` ${figures.bullet} ${MSG.THERMAL_ANE_POWER}: ${chalk.cyan(power.anePower.toFixed(2) + ' W')} ${aneLabel.color('(' + aneLabel.label + ')')}`);
1809
+ }
1810
+
1811
+ if (power.cpuFreq !== null) {
1812
+ console.log();
1813
+ console.log(` ${figures.bullet} ${MSG.THERMAL_FREQ}: ${chalk.cyan(power.cpuFreq.toFixed(1) + '%')} of nominal`);
1814
+ }
1815
+
1816
+ } catch (e) {
1817
+ detailSpinner.fail(chalk.red('Failed to read power metrics (sudo may have been denied)'));
1818
+ }
1819
+ }
1820
+
1821
+ console.log();
1822
+ console.log(chalk.cyan(' ' + figures.star + ' ' + MSG.THERMAL_STATS_APP));
1823
+ console.log(chalk.white(' brew install --cask stats'));
1824
+ console.log();
1825
+
1826
+ await pressEnter();
1827
+ }
1828
+
1829
+ // ============================================================================
1830
+ // SCHEDULER (LAUNCHD)
1831
+ // ============================================================================
1832
+
1833
+ async function scheduler() {
1834
+ header();
1835
+ sectionHeader(MSG.OPT_SCHEDULER);
1836
+
1837
+ const plistPath = path.join(HOME, 'Library/LaunchAgents/com.aris.mac-cleaner.plist');
1838
+ const isScheduled = fs.existsSync(plistPath);
1839
+
1840
+ console.log(chalk.white(' Cleanup Scheduler'));
1841
+ console.log();
1842
+
1843
+ let schedulerChoices;
1844
+ if (isScheduled) {
1845
+ console.log(chalk.green(` ${figures.tick} Scheduled cleanup is ACTIVE`));
1846
+ console.log();
1847
+ schedulerChoices = [
1848
+ { name: 'Remove schedule', value: 'remove' },
1849
+ { name: 'View current schedule', value: 'view' },
1850
+ { name: chalk.yellow(MSG.BACK), value: 'back' }
1851
+ ];
1852
+ } else {
1853
+ console.log(chalk.yellow(` ${figures.info} No scheduled cleanup configured`));
1854
+ console.log();
1855
+ schedulerChoices = [
1856
+ { name: 'Schedule daily cleanup (midnight)', value: 'daily' },
1857
+ { name: 'Schedule weekly cleanup (Sunday midnight)', value: 'weekly' },
1858
+ { name: chalk.yellow(MSG.BACK), value: 'back' }
1859
+ ];
1860
+ }
1861
+
1862
+ const fallbackScheduler = async () => {
1863
+ if (isScheduled) {
1864
+ console.log(` ${chalk.cyan('[1]')} Remove schedule`);
1865
+ console.log(` ${chalk.cyan('[2]')} View current schedule`);
1866
+ } else {
1867
+ console.log(` ${chalk.cyan('[1]')} Schedule daily cleanup (midnight)`);
1868
+ console.log(` ${chalk.cyan('[2]')} Schedule weekly cleanup (Sunday midnight)`);
1869
+ }
1870
+ console.log();
1871
+ console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
1872
+ console.log();
1873
+ const c = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1874
+ if (c === '0') return 'back';
1875
+ if (isScheduled) {
1876
+ return c === '1' ? 'remove' : c === '2' ? 'view' : 'back';
1877
+ } else {
1878
+ return c === '1' ? 'daily' : c === '2' ? 'weekly' : 'back';
1879
+ }
1880
+ };
1881
+
1882
+ const choice = await selectOption(MSG.OPT_SCHEDULER, schedulerChoices, fallbackScheduler);
1883
+
1884
+ if (choice === 'back' || !choice) {
1885
+ return;
1886
+ }
1887
+
1888
+ if (isScheduled) {
1889
+ if (choice === 'remove') {
1890
+ // Remove schedule
1891
+ try {
1892
+ exec(`launchctl unload "${plistPath}" 2>/dev/null`);
1893
+ fs.unlinkSync(plistPath);
1894
+ console.log(chalk.green(` ${figures.tick} ${MSG.SCHEDULE_REMOVED}`));
1895
+ } catch (e) {
1896
+ console.log(chalk.red(` ${figures.cross} Failed to remove schedule`));
1897
+ }
1898
+ } else if (choice === 'view') {
1899
+ // View schedule
1900
+ const content = fs.readFileSync(plistPath, 'utf8');
1901
+ console.log();
1902
+ console.log(chalk.dim(content));
1903
+ }
1904
+ } else {
1905
+ if (choice === 'daily' || choice === 'weekly') {
1906
+ const isDaily = choice === 'daily';
1907
+
1908
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1909
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1910
+ <plist version="1.0">
1911
+ <dict>
1912
+ <key>Label</key>
1913
+ <string>com.aris.mac-cleaner</string>
1914
+ <key>ProgramArguments</key>
1915
+ <array>
1916
+ <string>${process.execPath}</string>
1917
+ <string>${__filename}</string>
1918
+ <string>--quick</string>
1919
+ </array>
1920
+ <key>StartCalendarInterval</key>
1921
+ <dict>
1922
+ <key>Hour</key>
1923
+ <integer>0</integer>
1924
+ <key>Minute</key>
1925
+ <integer>0</integer>
1926
+ ${isDaily ? '' : '<key>Weekday</key>\n <integer>0</integer>'}
1927
+ </dict>
1928
+ <key>StandardOutPath</key>
1929
+ <string>${path.join(CONFIG_DIR, 'scheduled-cleanup.log')}</string>
1930
+ <key>StandardErrorPath</key>
1931
+ <string>${path.join(CONFIG_DIR, 'scheduled-cleanup-error.log')}</string>
1932
+ </dict>
1933
+ </plist>`;
1934
+
1935
+ try {
1936
+ fs.writeFileSync(plistPath, plistContent);
1937
+ exec(`launchctl load "${plistPath}"`);
1938
+ console.log(chalk.green(` ${figures.tick} ${isDaily ? MSG.SCHEDULED_DAILY : MSG.SCHEDULED_WEEKLY}`));
1939
+ } catch (e) {
1940
+ console.log(chalk.red(` ${figures.cross} Failed to create schedule`));
1941
+ }
1942
+ }
1943
+ }
1944
+
1945
+ await pressEnter();
1946
+ }
1947
+
1948
+ // ============================================================================
1949
+ // EXPORT HTML REPORT
1950
+ // ============================================================================
1951
+
1952
+ async function exportHtmlReport() {
1953
+ header();
1954
+ sectionHeader(MSG.OPT_EXPORT);
1955
+
1956
+ getSystemStats();
1957
+ const health = calculateHealthScore();
1958
+ const recs = getSmartRecommendations();
1959
+
1960
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
1961
+ const reportPath = path.join(REPORTS_DIR, `report-${timestamp}.html`);
1962
+
1963
+ // Read history
1964
+ let historyData = [];
1965
+ if (fs.existsSync(HISTORY_FILE)) {
1966
+ historyData = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim()).slice(-30);
1967
+ }
1968
+
1969
+ const html = `<!DOCTYPE html>
1970
+ <html lang="en">
1971
+ <head>
1972
+ <meta charset="UTF-8">
1973
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1974
+ <title>Aris Mac Cleaner Report - ${new Date().toLocaleDateString()}</title>
1975
+ <style>
1976
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1977
+ body {
1978
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1979
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
1980
+ color: #fff;
1981
+ min-height: 100vh;
1982
+ padding: 40px;
1983
+ }
1984
+ .container { max-width: 900px; margin: 0 auto; }
1985
+ .header {
1986
+ text-align: center;
1987
+ margin-bottom: 40px;
1988
+ padding: 30px;
1989
+ background: rgba(255,255,255,0.1);
1990
+ border-radius: 20px;
1991
+ backdrop-filter: blur(10px);
1992
+ }
1993
+ .header h1 {
1994
+ font-size: 2.5em;
1995
+ background: linear-gradient(90deg, #00d2ff, #3a7bd5);
1996
+ -webkit-background-clip: text;
1997
+ -webkit-text-fill-color: transparent;
1998
+ }
1999
+ .header p { color: #888; margin-top: 10px; }
2000
+ .card {
2001
+ background: rgba(255,255,255,0.05);
2002
+ border-radius: 15px;
2003
+ padding: 25px;
2004
+ margin-bottom: 20px;
2005
+ border: 1px solid rgba(255,255,255,0.1);
2006
+ }
2007
+ .card h2 {
2008
+ color: #00d2ff;
2009
+ margin-bottom: 20px;
2010
+ font-size: 1.3em;
2011
+ }
2012
+ .stat-row {
2013
+ display: flex;
2014
+ justify-content: space-between;
2015
+ padding: 12px 0;
2016
+ border-bottom: 1px solid rgba(255,255,255,0.05);
2017
+ }
2018
+ .stat-row:last-child { border-bottom: none; }
2019
+ .stat-label { color: #888; }
2020
+ .stat-value { font-weight: bold; }
2021
+ .health-score {
2022
+ text-align: center;
2023
+ padding: 40px;
2024
+ }
2025
+ .health-score .score {
2026
+ font-size: 4em;
2027
+ font-weight: bold;
2028
+ background: linear-gradient(90deg, ${health.score >= 80 ? '#00ff87, #60efff' : health.score >= 60 ? '#ffcc00, #ff6600' : '#ff0000, #ff6666'});
2029
+ -webkit-background-clip: text;
2030
+ -webkit-text-fill-color: transparent;
2031
+ }
2032
+ .health-score .label {
2033
+ color: #888;
2034
+ font-size: 1.2em;
2035
+ margin-top: 10px;
2036
+ }
2037
+ .recommendation {
2038
+ padding: 15px;
2039
+ margin: 10px 0;
2040
+ border-radius: 10px;
2041
+ background: rgba(255,204,0,0.1);
2042
+ border-left: 3px solid #ffcc00;
2043
+ }
2044
+ .recommendation.high {
2045
+ background: rgba(255,0,0,0.1);
2046
+ border-left-color: #ff4444;
2047
+ }
2048
+ .history-item {
2049
+ font-family: monospace;
2050
+ font-size: 0.9em;
2051
+ padding: 8px;
2052
+ color: #888;
2053
+ }
2054
+ .footer {
2055
+ text-align: center;
2056
+ margin-top: 40px;
2057
+ color: #666;
2058
+ font-size: 0.9em;
2059
+ }
2060
+ </style>
2061
+ </head>
2062
+ <body>
2063
+ <div class="container">
2064
+ <div class="header">
2065
+ <h1>ARIS MAC CLEANER</h1>
2066
+ <p>System Report - ${new Date().toLocaleString()}</p>
2067
+ </div>
2068
+
2069
+ <div class="card health-score">
2070
+ <div class="score">${health.score}/100</div>
2071
+ <div class="label">${MSG.HEALTH_SCORE} - ${health.text}</div>
2072
+ </div>
2073
+
2074
+ <div class="card">
2075
+ <h2>${MSG.DIAGNOSE}</h2>
2076
+ <div class="stat-row">
2077
+ <span class="stat-label">${MSG.DISK}</span>
2078
+ <span class="stat-value">${systemStats.diskFree}GB ${MSG.FREE_OF} ${systemStats.diskTotal}GB (${systemStats.diskPercent}% ${MSG.IN_USE})</span>
2079
+ </div>
2080
+ <div class="stat-row">
2081
+ <span class="stat-label">${MSG.RAM}</span>
2082
+ <span class="stat-value">${systemStats.ramUsedPercent}% ${MSG.IN_USE}</span>
2083
+ </div>
2084
+ <div class="stat-row">
2085
+ <span class="stat-label">${MSG.SWAP}</span>
2086
+ <span class="stat-value">${systemStats.swapUsed} MB ${MSG.IN_USE}</span>
2087
+ </div>
2088
+ <div class="stat-row">
2089
+ <span class="stat-label">${MSG.CPU}</span>
2090
+ <span class="stat-value">${systemStats.cpuUsed}% ${MSG.IN_USE}</span>
2091
+ </div>
2092
+ </div>
2093
+
2094
+ ${recs.length > 0 ? `
2095
+ <div class="card">
2096
+ <h2>${MSG.RECOMMENDATIONS}</h2>
2097
+ ${recs.map(r => `<div class="recommendation ${r.priority}">${r.text}</div>`).join('')}
2098
+ </div>
2099
+ ` : ''}
2100
+
2101
+ ${historyData.length > 0 ? `
2102
+ <div class="card">
2103
+ <h2>${MSG.HISTORY}</h2>
2104
+ ${historyData.map(h => `<div class="history-item">${h}</div>`).join('')}
2105
+ </div>
2106
+ ` : ''}
2107
+
2108
+ ${lastCleanupResults ? `
2109
+ <div class="card">
2110
+ <h2>Last Cleanup</h2>
2111
+ <div class="stat-row">
2112
+ <span class="stat-label">${MSG.FREED}</span>
2113
+ <span class="stat-value">${formatSize(lastCleanupResults.freed)}</span>
2114
+ </div>
2115
+ </div>
2116
+ ` : ''}
2117
+
2118
+ <div class="footer">
2119
+ <p>Generated by Aris Mac Cleaner v${VERSION}</p>
2120
+ <p>por Salvador Reis</p>
2121
+ </div>
2122
+ </div>
2123
+ </body>
2124
+ </html>`;
2125
+
2126
+ fs.writeFileSync(reportPath, html);
2127
+
2128
+ console.log(chalk.green(` ${figures.tick} ${MSG.REPORT_EXPORTED}`));
2129
+ console.log(chalk.dim(` ${reportPath}`));
2130
+ console.log();
2131
+
2132
+ const fallbackOpen = async () => {
2133
+ const answer = await prompt(` Open in browser? [y/n]: `);
2134
+ return answer.toLowerCase() === 'y';
2135
+ };
2136
+
2137
+ const shouldOpen = await confirmAction('Open report in browser?', false, fallbackOpen);
2138
+ if (shouldOpen) {
2139
+ exec(`open "${reportPath}"`);
2140
+ }
2141
+
2142
+ await pressEnter();
2143
+ }
2144
+
2145
+ // ============================================================================
2146
+ // STATISTICS WITH ASCII CHART
2147
+ // ============================================================================
2148
+
2149
+ async function showStatistics() {
2150
+ header();
2151
+ sectionHeader(MSG.OPT_STATS);
2152
+
2153
+ if (!fs.existsSync(HISTORY_FILE)) {
2154
+ console.log(` ${chalk.dim(MSG.NO_HISTORY)}`);
2155
+ await pressEnter();
2156
+ return;
2157
+ }
2158
+
2159
+ const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
2160
+ const currentMonth = new Date().toISOString().slice(0, 7);
2161
+
2162
+ const monthLines = history.filter(l => l.startsWith(currentMonth));
2163
+ const monthCleanups = monthLines.length;
2164
+
2165
+ let monthFreed = 0;
2166
+ let monthScoreSum = 0;
2167
+ const freedValues = [];
2168
+
2169
+ monthLines.forEach(line => {
2170
+ const freedMatch = line.match(/(\d+)\s*MB/);
2171
+ const scoreMatch = line.match(/Score:\s*(\d+)/);
2172
+ if (freedMatch) {
2173
+ const freed = parseInt(freedMatch[1]);
2174
+ monthFreed += freed;
2175
+ freedValues.push(freed);
2176
+ }
2177
+ if (scoreMatch) monthScoreSum += parseInt(scoreMatch[1]);
2178
+ });
2179
+
2180
+ const monthAvg = monthCleanups > 0 ? Math.round(monthScoreSum / monthCleanups) : 0;
2181
+
2182
+ console.log(chalk.white(` ${MSG.THIS_MONTH}:`));
2183
+ console.log(` ${figures.bullet} ${MSG.CLEANINGS}: ${chalk.cyan(monthCleanups)}`);
2184
+ console.log(` ${figures.bullet} ${MSG.TOTAL} ${MSG.FREED}: ${chalk.green(formatSize(monthFreed))}`);
2185
+ console.log(` ${figures.bullet} ${MSG.AVG_SCORE}: ${chalk.yellow(monthAvg + '/100')}`);
2186
+ console.log();
2187
+
2188
+ // Show ASCII chart if we have enough data
2189
+ if (freedValues.length >= 3) {
2190
+ console.log(chalk.white(' Space Freed (MB) - Last cleanups:'));
2191
+ console.log();
2192
+
2193
+ try {
2194
+ const chartData = freedValues.slice(-15);
2195
+ const chart = asciichart.plot(chartData, {
2196
+ height: 8,
2197
+ colors: [asciichart.cyan],
2198
+ format: (x) => String(Math.round(x)).padStart(6)
2199
+ });
2200
+ chart.split('\n').forEach(line => console.log(' ' + line));
2201
+ } catch (e) {
2202
+ // Fallback if asciichart fails
2203
+ freedValues.slice(-10).forEach((v, i) => {
2204
+ const bar = chalk.cyan('\u2588'.repeat(Math.min(Math.round(v / 50), 40)));
2205
+ console.log(` ${bar} ${v} MB`);
2206
+ });
2207
+ }
2208
+ console.log();
2209
+ }
2210
+
2211
+ console.log(chalk.white(` ${MSG.HISTORY}:`));
2212
+ history.slice(-10).forEach(line => {
2213
+ console.log(chalk.dim(` ${line}`));
2214
+ });
2215
+
2216
+ await pressEnter();
2217
+ }
2218
+
2219
+ // ============================================================================
2220
+ // QUICK CLEAN (ENHANCED)
2221
+ // ============================================================================
2222
+
2223
+ async function quickClean() {
2224
+ header();
2225
+ getSystemStats();
2226
+ showDiagnostics();
2227
+ showRecommendations();
2228
+ sectionHeader(MSG.CATEGORIES);
2229
+
2230
+ const items = [];
2231
+ let totalSize = 0;
2232
+
2233
+ function addItem(p, name, category) {
2234
+ const fullPath = p.replace(/^~/, HOME);
2235
+ if (isExcluded(fullPath)) return;
2236
+
2237
+ const size = getSize(p);
2238
+ if (size > 0) {
2239
+ items.push({ path: fullPath, name, category, size, selected: true });
2240
+ totalSize += size;
2241
+ }
2242
+ }
2243
+
2244
+ const spinner = ora(MSG.SCANNING).start();
2245
+
2246
+ // System Caches
2247
+ addItem('~/Library/Caches/Google', 'Google/Chrome Cache', 'system');
2248
+ addItem('~/Library/Caches/com.spotify.client', 'Spotify Cache', 'system');
2249
+ addItem('~/Library/Caches/com.apple.Safari', 'Safari Cache', 'system');
2250
+ addItem('~/Library/Logs', 'System Logs', 'system');
2251
+
2252
+ // Dev Tools
2253
+ addItem('~/Library/Caches/CocoaPods', 'CocoaPods Cache', 'dev');
2254
+ addItem('~/Library/Caches/Homebrew', 'Homebrew Cache', 'dev');
2255
+ addItem('~/Library/Caches/typescript', 'TypeScript Cache', 'dev');
2256
+ addItem('~/Library/Caches/node-gyp', 'Node-gyp Cache', 'dev');
2257
+ addItem('~/Library/Caches/pip', 'Pip Cache', 'dev');
2258
+ addItem('~/Library/Caches/yarn', 'Yarn Cache', 'dev');
2259
+ addItem('~/Library/Caches/pnpm', 'pnpm Cache', 'dev');
2260
+ addItem('~/.npm', 'npm Cache', 'dev');
2261
+ addItem('~/Library/Application Support/Code/User/workspaceStorage', 'VS Code Workspace', 'dev');
2262
+ addItem('~/Library/Developer/Xcode/DerivedData', 'Xcode DerivedData', 'dev');
2263
+
2264
+ // Browsers
2265
+ addItem('~/Library/Application Support/Google/GoogleUpdater/crx_cache', 'Google Updater', 'browsers');
2266
+ addItem('~/Library/Application Support/Google/Chrome/component_crx_cache', 'Chrome Components', 'browsers');
2267
+
2268
+ // Claude versions
2269
+ const claudeVersionsPath = path.join(HOME, '.local/share/claude/versions');
2270
+ if (fs.existsSync(claudeVersionsPath)) {
2271
+ try {
2272
+ const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
2273
+ if (versions.length > 1) {
505
2274
  let claudeSize = 0;
506
2275
  versions.slice(1).forEach(v => {
507
2276
  claudeSize += getSize(path.join(claudeVersionsPath, v));
@@ -545,8 +2314,10 @@ async function quickClean() {
545
2314
  });
546
2315
  }
547
2316
 
2317
+ spinner.stop();
2318
+
548
2319
  if (items.length === 0) {
549
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
2320
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
550
2321
  await pressEnter();
551
2322
  return;
552
2323
  }
@@ -557,11 +2328,11 @@ async function quickClean() {
557
2328
  if (catItems.length === 0) return;
558
2329
 
559
2330
  const catSize = catItems.reduce((sum, i) => sum + i.size, 0);
560
- console.log(` ${c.white}📁 ${catName}${c.reset} ${c.dim}(${formatSize(catSize)})${c.reset}`);
2331
+ console.log(` ${chalk.white(' ' + catName)} ${chalk.dim('(' + formatSize(catSize) + ')')}`);
561
2332
 
562
2333
  catItems.forEach(item => {
563
2334
  const idx = items.indexOf(item) + 1;
564
- console.log(` ${c.cyan}[${String(idx).padStart(2)}]${c.reset} ${item.name.padEnd(35)} ${c.dim}${String(item.size).padStart(6)} MB${c.reset}`);
2335
+ console.log(` ${chalk.cyan('[' + String(idx).padStart(2) + ']')} ${item.name.padEnd(35)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
565
2336
  });
566
2337
  console.log();
567
2338
  }
@@ -573,94 +2344,712 @@ async function quickClean() {
573
2344
  showCategory('apps', MSG.APPS_CAT);
574
2345
  showCategory('custom', 'Custom');
575
2346
 
576
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalSize)}${c.reset}`);
2347
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalSize))}`);
577
2348
  console.log();
578
2349
 
579
- // Selection
580
- sectionHeader(MSG.SELECT_DELETE);
581
- console.log(` ${c.dim}a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10${c.reset}`);
2350
+ // Selection - build checkbox choices
2351
+ const itemChoices = items.map((item, i) => ({
2352
+ name: `${item.name.padEnd(35)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`,
2353
+ value: item.path,
2354
+ checked: true,
2355
+ short: item.name
2356
+ }));
2357
+
2358
+ // Fallback for text-based selection
2359
+ const fallbackQuickSelect = async () => {
2360
+ sectionHeader(MSG.SELECT_DELETE);
2361
+ console.log(chalk.dim(` a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10`));
2362
+ console.log();
2363
+
2364
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2365
+
2366
+ if (choice.toLowerCase() === 'n' || choice === '') {
2367
+ return [];
2368
+ }
2369
+
2370
+ if (choice.toLowerCase() === 'a') {
2371
+ return items.map(item => item.path);
2372
+ }
2373
+
2374
+ const selected = [];
2375
+ const parts = choice.split(',');
2376
+ parts.forEach(part => {
2377
+ part = part.trim();
2378
+ if (part.includes('-')) {
2379
+ const [start, end] = part.split('-').map(n => parseInt(n));
2380
+ for (let j = start; j <= end; j++) {
2381
+ if (j > 0 && j <= items.length) selected.push(items[j - 1].path);
2382
+ }
2383
+ } else {
2384
+ const idx = parseInt(part) - 1;
2385
+ if (idx >= 0 && idx < items.length) selected.push(items[idx].path);
2386
+ }
2387
+ });
2388
+ return selected;
2389
+ };
2390
+
2391
+ const selectedPaths = await selectMultiple(
2392
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to toggle, enter to confirm)')}`,
2393
+ itemChoices,
2394
+ fallbackQuickSelect
2395
+ );
2396
+
2397
+ if (selectedPaths.length === 0) {
2398
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
2399
+ await pressEnter();
2400
+ return;
2401
+ }
2402
+
2403
+ // Mark selected items
2404
+ items.forEach(item => {
2405
+ item.selected = selectedPaths.includes(item.path);
2406
+ });
2407
+
2408
+ // Create backup
2409
+ const selectedItems = items.filter(i => i.selected);
2410
+ if (selectedItems.length === 0) {
2411
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
2412
+ await pressEnter();
2413
+ return;
2414
+ }
2415
+
2416
+ // Ask confirmation mode
2417
+ const confirmMode = await askConfirmationMode();
2418
+
2419
+ if (confirmMode !== 'dryrun' && !DRY_RUN) {
2420
+ const backupFile = createBackup(selectedItems);
2421
+ console.log(chalk.dim(` ${figures.info} ${MSG.BACKUP_CREATED}: ${path.basename(backupFile)}`));
2422
+ console.log();
2423
+ }
2424
+
2425
+ // Clean with confirmation mode support
2426
+ console.log();
2427
+ sectionHeader(MSG.CLEANING);
2428
+
2429
+ let cleanedSize = 0;
2430
+ let deletedCount = 0;
2431
+ let skippedCount = 0;
2432
+ let autoMode = confirmMode === 'auto';
2433
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
2434
+ const claudeVersionsPathLocal = path.join(HOME, '.local/share/claude/versions');
2435
+
2436
+ if (autoMode && !isDryRun) {
2437
+ const progressBar = createProgressBar(selectedItems.length);
2438
+ progressBar.start(selectedItems.length, 0);
2439
+
2440
+ for (let i = 0; i < items.length; i++) {
2441
+ const item = items[i];
2442
+ if (!item.selected) continue;
2443
+
2444
+ if (item.path === 'CLAUDE_VERSIONS') {
2445
+ const versions = fs.readdirSync(claudeVersionsPathLocal).sort().reverse();
2446
+ versions.slice(1).forEach(v => {
2447
+ try {
2448
+ deleteItem(path.join(claudeVersionsPathLocal, v));
2449
+ } catch (e) {}
2450
+ });
2451
+ cleanedSize += item.size;
2452
+ deletedCount++;
2453
+ } else if (item.path === 'OLD_DOWNLOADS') {
2454
+ exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
2455
+ exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
2456
+ cleanedSize += item.size;
2457
+ deletedCount++;
2458
+ } else if (fs.existsSync(item.path)) {
2459
+ if (deleteItem(item.path)) {
2460
+ cleanedSize += item.size;
2461
+ deletedCount++;
2462
+ }
2463
+ }
2464
+
2465
+ progressBar.increment();
2466
+ }
2467
+
2468
+ progressBar.stop();
2469
+ } else {
2470
+ // Manual or dry-run mode
2471
+ for (let i = 0; i < items.length; i++) {
2472
+ const item = items[i];
2473
+ if (!item.selected) continue;
2474
+
2475
+ let shouldDelete = autoMode;
2476
+
2477
+ if (!autoMode && confirmMode === 'manual') {
2478
+ const displayPath = item.path === 'CLAUDE_VERSIONS' ? claudeVersionsPathLocal :
2479
+ item.path === 'OLD_DOWNLOADS' ? '~/Downloads (old files)' : item.path;
2480
+ const answer = await confirmItem(displayPath, item.size);
2481
+
2482
+ if (answer === 'all') {
2483
+ autoMode = true;
2484
+ shouldDelete = true;
2485
+ } else if (answer === 'yes') {
2486
+ shouldDelete = true;
2487
+ } else {
2488
+ shouldDelete = false;
2489
+ }
2490
+ }
2491
+
2492
+ if (isDryRun) {
2493
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${item.name} ${chalk.dim('(' + formatSize(item.size) + ')')}`);
2494
+ cleanedSize += item.size;
2495
+ deletedCount++;
2496
+ } else if (shouldDelete) {
2497
+ if (item.path === 'CLAUDE_VERSIONS') {
2498
+ const versions = fs.readdirSync(claudeVersionsPathLocal).sort().reverse();
2499
+ versions.slice(1).forEach(v => {
2500
+ try {
2501
+ deleteItem(path.join(claudeVersionsPathLocal, v));
2502
+ } catch (e) {}
2503
+ });
2504
+ cleanedSize += item.size;
2505
+ deletedCount++;
2506
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
2507
+ } else if (item.path === 'OLD_DOWNLOADS') {
2508
+ exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
2509
+ exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
2510
+ cleanedSize += item.size;
2511
+ deletedCount++;
2512
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
2513
+ } else if (fs.existsSync(item.path)) {
2514
+ if (deleteItem(item.path)) {
2515
+ cleanedSize += item.size;
2516
+ deletedCount++;
2517
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
2518
+ }
2519
+ }
2520
+ } else {
2521
+ skippedCount++;
2522
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${item.name}`);
2523
+ }
2524
+ }
2525
+ }
2526
+
2527
+ // npm cache
2528
+ if (!isDryRun) {
2529
+ try {
2530
+ execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
2531
+ } catch (e) {}
2532
+ }
2533
+
2534
+ // Show summary
2535
+ if (autoMode && !isDryRun) {
2536
+ console.log();
2537
+ selectedItems.forEach(item => {
2538
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
2539
+ });
2540
+ }
2541
+
2542
+ showCleanupSummary(deletedCount, skippedCount, cleanedSize, confirmMode);
2543
+ getSystemStats();
2544
+ showFinalReport(cleanedSize);
2545
+ await pressEnter();
2546
+ }
2547
+
2548
+ // ============================================================================
2549
+ // DEEP CLEAN (AGGRESSIVE)
2550
+ // ============================================================================
2551
+
2552
+ async function deepClean() {
2553
+ header();
2554
+ sectionHeader(`${MSG.DEEP_CLEAN} ${chalk.red('\uD83D\uDD25')}`);
2555
+
2556
+ // Warning message
2557
+ console.log(chalk.red.bold(` ${figures.warning} ${MSG.DEEP_CLEAN_WARNING}`));
2558
+ console.log();
2559
+ console.log(chalk.yellow(` ${MSG.DEEP_CLEAN_TIME}`));
582
2560
  console.log();
583
2561
 
584
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
2562
+ const fallbackDeepConfirm = async () => {
2563
+ const answer = await prompt(` ${chalk.white(MSG.DEEP_CLEAN_CONFIRM + ' [y/n]:')} `);
2564
+ return answer.toLowerCase() === 'y';
2565
+ };
585
2566
 
586
- if (choice.toLowerCase() === 'n' || choice === '') {
587
- console.log(` ${c.yellow}${MSG.CANCEL}${c.reset}`);
2567
+ const confirmDeep = await confirmAction(MSG.DEEP_CLEAN_CONFIRM, false, fallbackDeepConfirm);
2568
+ if (!confirmDeep) {
2569
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
588
2570
  await pressEnter();
589
2571
  return;
590
2572
  }
591
2573
 
592
- // Process selection
593
- if (choice.toLowerCase() !== 'a') {
594
- items.forEach(item => item.selected = false);
2574
+ // Ask confirmation mode
2575
+ const confirmMode = await askConfirmationMode();
2576
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
2577
+ let autoMode = confirmMode === 'auto';
2578
+
2579
+ console.log();
2580
+ const startTime = Date.now();
2581
+
2582
+ let totalCleaned = 0;
2583
+ let deletedCount = 0;
2584
+ let skippedCount = 0;
2585
+ const results = [];
2586
+
2587
+ // Helper function to process item with confirmation
2588
+ async function processItemWithConfirm(itemPath, itemSize, categoryName) {
2589
+ if (isDryRun) {
2590
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${itemPath.replace(HOME, '~')} ${chalk.dim('(' + formatSize(itemSize) + ')')}`);
2591
+ return { cleaned: itemSize, deleted: true };
2592
+ }
2593
+
2594
+ if (autoMode) {
2595
+ if (deleteItem(itemPath)) {
2596
+ return { cleaned: itemSize, deleted: true };
2597
+ }
2598
+ return { cleaned: 0, deleted: false };
2599
+ }
2600
+
2601
+ // Manual mode
2602
+ const answer = await confirmItem(itemPath, itemSize);
2603
+ if (answer === 'all') {
2604
+ autoMode = true;
2605
+ if (deleteItem(itemPath)) {
2606
+ console.log(` ${chalk.green(figures.tick)} ${categoryName}: ${itemPath.replace(HOME, '~')}`);
2607
+ return { cleaned: itemSize, deleted: true };
2608
+ }
2609
+ } else if (answer === 'yes') {
2610
+ if (deleteItem(itemPath)) {
2611
+ console.log(` ${chalk.green(figures.tick)} ${categoryName}: ${itemPath.replace(HOME, '~')}`);
2612
+ return { cleaned: itemSize, deleted: true };
2613
+ }
2614
+ } else {
2615
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${itemPath.replace(HOME, '~')}`);
2616
+ return { cleaned: 0, deleted: false, skipped: true };
2617
+ }
2618
+ return { cleaned: 0, deleted: false };
2619
+ }
2620
+
2621
+ // Helper to clean a category
2622
+ async function cleanCategory(categoryName, paths, description) {
2623
+ let categorySize = 0;
2624
+ let catDeleted = 0;
2625
+ let catSkipped = 0;
2626
+
2627
+ if (autoMode && !isDryRun) {
2628
+ const spinner = ora(`${description}...`).start();
2629
+
2630
+ for (const p of paths) {
2631
+ const fullPath = p.replace(/^~/, HOME);
2632
+ if (isExcluded(fullPath)) continue;
2633
+
2634
+ const size = getSize(p);
2635
+ if (size > 0) {
2636
+ if (deleteItem(fullPath)) {
2637
+ categorySize += size;
2638
+ catDeleted++;
2639
+ }
2640
+ }
2641
+ }
2642
+
2643
+ if (categorySize > 0) {
2644
+ spinner.succeed(`${description}: ${chalk.green(formatSize(categorySize))}`);
2645
+ } else {
2646
+ spinner.succeed(`${description}: ${chalk.dim('0 MB')}`);
2647
+ }
2648
+ } else {
2649
+ console.log(chalk.white(` ${description}:`));
2650
+
2651
+ for (const p of paths) {
2652
+ const fullPath = p.replace(/^~/, HOME);
2653
+ if (isExcluded(fullPath)) continue;
2654
+
2655
+ const size = getSize(p);
2656
+ if (size > 0) {
2657
+ const result = await processItemWithConfirm(fullPath, size, categoryName);
2658
+ categorySize += result.cleaned;
2659
+ if (result.deleted) catDeleted++;
2660
+ if (result.skipped) catSkipped++;
2661
+ }
2662
+ }
2663
+ }
2664
+
2665
+ if (categorySize > 0) {
2666
+ results.push({ category: categoryName, size: categorySize });
2667
+ totalCleaned += categorySize;
2668
+ }
2669
+ deletedCount += catDeleted;
2670
+ skippedCount += catSkipped;
2671
+
2672
+ return categorySize;
2673
+ }
2674
+
2675
+ // 1. System Caches
2676
+ await cleanCategory(MSG.CAT_SYSTEM_CACHES, [
2677
+ '~/Library/Caches/com.apple.Safari',
2678
+ '~/Library/Caches/com.apple.dt.Xcode',
2679
+ '~/Library/Logs',
2680
+ '/Library/Logs',
2681
+ '/private/var/log'
2682
+ ], MSG.CAT_SYSTEM_CACHES);
2683
+
2684
+ // 2. ALL Application Caches
2685
+ let appCacheSize = 0;
2686
+ const cachesDir = path.join(HOME, 'Library/Caches');
2687
+ if (fs.existsSync(cachesDir)) {
2688
+ if (autoMode && !isDryRun) {
2689
+ const spinner2 = ora(`${MSG.CAT_APP_CACHES}...`).start();
2690
+ try {
2691
+ const cacheItems = fs.readdirSync(cachesDir);
2692
+ for (const item of cacheItems) {
2693
+ const itemPath = path.join(cachesDir, item);
2694
+ if (isExcluded(itemPath)) continue;
2695
+ const size = getSize(itemPath);
2696
+ if (size > 0 && deleteItem(itemPath)) {
2697
+ appCacheSize += size;
2698
+ deletedCount++;
2699
+ }
2700
+ }
2701
+ } catch (e) {}
2702
+ if (appCacheSize > 0) {
2703
+ spinner2.succeed(`${MSG.CAT_APP_CACHES}: ${chalk.green(formatSize(appCacheSize))}`);
2704
+ } else {
2705
+ spinner2.succeed(`${MSG.CAT_APP_CACHES}: ${chalk.dim('0 MB')}`);
2706
+ }
2707
+ } else {
2708
+ console.log(chalk.white(` ${MSG.CAT_APP_CACHES}:`));
2709
+ try {
2710
+ const cacheItems = fs.readdirSync(cachesDir);
2711
+ for (const item of cacheItems) {
2712
+ const itemPath = path.join(cachesDir, item);
2713
+ if (isExcluded(itemPath)) continue;
2714
+ const size = getSize(itemPath);
2715
+ if (size > 0) {
2716
+ const result = await processItemWithConfirm(itemPath, size, MSG.CAT_APP_CACHES);
2717
+ appCacheSize += result.cleaned;
2718
+ if (result.deleted) deletedCount++;
2719
+ if (result.skipped) skippedCount++;
2720
+ }
2721
+ }
2722
+ } catch (e) {}
2723
+ }
2724
+ }
2725
+ if (appCacheSize > 0) {
2726
+ results.push({ category: MSG.CAT_APP_CACHES, size: appCacheSize });
2727
+ totalCleaned += appCacheSize;
2728
+ }
2729
+
2730
+ // 3. Xcode
2731
+ await cleanCategory(MSG.CAT_XCODE, [
2732
+ '~/Library/Developer/Xcode/DerivedData',
2733
+ '~/Library/Developer/Xcode/Archives',
2734
+ '~/Library/Developer/Xcode/iOS DeviceSupport',
2735
+ '~/Library/Developer/Xcode/watchOS DeviceSupport',
2736
+ '~/Library/Developer/Xcode/tvOS DeviceSupport',
2737
+ '~/Library/Developer/CoreSimulator/Caches',
2738
+ '~/Library/Developer/CoreSimulator/Devices'
2739
+ ], MSG.CAT_XCODE);
2740
+
2741
+ // 4. Dev Caches
2742
+ await cleanCategory(MSG.CAT_DEV_CACHES, [
2743
+ '~/.npm',
2744
+ '~/.yarn/cache',
2745
+ '~/.cache/yarn',
2746
+ '~/Library/Caches/pip',
2747
+ '~/.pip/cache',
2748
+ '~/Library/Caches/Homebrew',
2749
+ '~/Library/Caches/CocoaPods',
2750
+ '~/.cocoapods/repos',
2751
+ '~/.gradle/caches',
2752
+ '~/.gradle/wrapper',
2753
+ '~/Library/Caches/typescript',
2754
+ '~/Library/Caches/node-gyp',
2755
+ '~/.cache/pip',
2756
+ '~/Library/Caches/pnpm',
2757
+ '~/.pnpm-store',
2758
+ '~/.cargo/registry/cache',
2759
+ '~/.rustup/tmp',
2760
+ '~/go/pkg/mod/cache'
2761
+ ], MSG.CAT_DEV_CACHES);
2762
+
2763
+ // 5. Docker
2764
+ let dockerSize = 0;
2765
+ if (exec('which docker')) {
2766
+ if (isDryRun) {
2767
+ console.log(chalk.blue(` ${figures.info} [DRY-RUN] docker system prune -af --volumes`));
2768
+ dockerSize = 500;
2769
+ deletedCount++;
2770
+ } else if (autoMode) {
2771
+ const spinner5 = ora(`${MSG.CAT_DOCKER}...`).start();
2772
+ try {
2773
+ exec('docker system prune -af --volumes 2>/dev/null');
2774
+ dockerSize = 500;
2775
+ deletedCount++;
2776
+ } catch (e) {}
2777
+ spinner5.succeed(`${MSG.CAT_DOCKER}: ${dockerSize > 0 ? chalk.green(formatSize(dockerSize)) : chalk.dim('0 MB')}`);
2778
+ } else {
2779
+ const answer = await confirmItem('Docker (images, containers, volumes)', 500);
2780
+ if (answer === 'all') {
2781
+ autoMode = true;
2782
+ exec('docker system prune -af --volumes 2>/dev/null');
2783
+ dockerSize = 500;
2784
+ deletedCount++;
2785
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_DOCKER}`);
2786
+ } else if (answer === 'yes') {
2787
+ exec('docker system prune -af --volumes 2>/dev/null');
2788
+ dockerSize = 500;
2789
+ deletedCount++;
2790
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_DOCKER}`);
2791
+ } else {
2792
+ skippedCount++;
2793
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${MSG.CAT_DOCKER}`);
2794
+ }
2795
+ }
2796
+ }
2797
+ if (dockerSize > 0) {
2798
+ results.push({ category: MSG.CAT_DOCKER, size: dockerSize });
2799
+ totalCleaned += dockerSize;
2800
+ }
2801
+
2802
+ // 6. iOS Backups older than 30 days
2803
+ let iosBackupSize = 0;
2804
+ const backupsDir = path.join(HOME, 'Library/Application Support/MobileSync/Backup');
2805
+ if (fs.existsSync(backupsDir)) {
2806
+ if (autoMode && !isDryRun) {
2807
+ const spinner6 = ora(`${MSG.CAT_IOS_BACKUPS}...`).start();
2808
+ try {
2809
+ const backups = fs.readdirSync(backupsDir);
2810
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
2811
+ for (const backup of backups) {
2812
+ const backupPath = path.join(backupsDir, backup);
2813
+ const stats = fs.statSync(backupPath);
2814
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
2815
+ const size = getSize(backupPath);
2816
+ if (size > 0 && deleteItem(backupPath)) {
2817
+ iosBackupSize += size;
2818
+ deletedCount++;
2819
+ }
2820
+ }
2821
+ }
2822
+ } catch (e) {}
2823
+ if (iosBackupSize > 0) {
2824
+ spinner6.succeed(`${MSG.CAT_IOS_BACKUPS}: ${chalk.green(formatSize(iosBackupSize))}`);
2825
+ } else {
2826
+ spinner6.succeed(`${MSG.CAT_IOS_BACKUPS}: ${chalk.dim('0 MB')}`);
2827
+ }
2828
+ } else {
2829
+ console.log(chalk.white(` ${MSG.CAT_IOS_BACKUPS}:`));
2830
+ try {
2831
+ const backups = fs.readdirSync(backupsDir);
2832
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
2833
+ for (const backup of backups) {
2834
+ const backupPath = path.join(backupsDir, backup);
2835
+ const stats = fs.statSync(backupPath);
2836
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
2837
+ const size = getSize(backupPath);
2838
+ if (size > 0) {
2839
+ const result = await processItemWithConfirm(backupPath, size, MSG.CAT_IOS_BACKUPS);
2840
+ iosBackupSize += result.cleaned;
2841
+ if (result.deleted) deletedCount++;
2842
+ if (result.skipped) skippedCount++;
2843
+ }
2844
+ }
2845
+ }
2846
+ } catch (e) {}
2847
+ }
2848
+ }
2849
+ if (iosBackupSize > 0) {
2850
+ results.push({ category: MSG.CAT_IOS_BACKUPS, size: iosBackupSize });
2851
+ totalCleaned += iosBackupSize;
2852
+ }
2853
+
2854
+ // 7. Downloads older than 30 days
2855
+ let oldDownloadsSize = 0;
2856
+ const downloadsDir = path.join(HOME, 'Downloads');
2857
+ if (fs.existsSync(downloadsDir)) {
2858
+ if (autoMode && !isDryRun) {
2859
+ const spinner7 = ora(`${MSG.CAT_OLD_DOWNLOADS}...`).start();
2860
+ try {
2861
+ const items = fs.readdirSync(downloadsDir);
2862
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
2863
+ for (const item of items) {
2864
+ const itemPath = path.join(downloadsDir, item);
2865
+ try {
2866
+ const stats = fs.statSync(itemPath);
2867
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
2868
+ const size = getSize(itemPath);
2869
+ if (size > 0 && deleteItem(itemPath)) {
2870
+ oldDownloadsSize += size;
2871
+ deletedCount++;
2872
+ }
2873
+ }
2874
+ } catch (e) {}
2875
+ }
2876
+ } catch (e) {}
2877
+ if (oldDownloadsSize > 0) {
2878
+ spinner7.succeed(`${MSG.CAT_OLD_DOWNLOADS}: ${chalk.green(formatSize(oldDownloadsSize))}`);
2879
+ } else {
2880
+ spinner7.succeed(`${MSG.CAT_OLD_DOWNLOADS}: ${chalk.dim('0 MB')}`);
2881
+ }
2882
+ } else {
2883
+ console.log(chalk.white(` ${MSG.CAT_OLD_DOWNLOADS}:`));
2884
+ try {
2885
+ const items = fs.readdirSync(downloadsDir);
2886
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
2887
+ for (const item of items) {
2888
+ const itemPath = path.join(downloadsDir, item);
2889
+ try {
2890
+ const stats = fs.statSync(itemPath);
2891
+ if (stats.mtime.getTime() < thirtyDaysAgo) {
2892
+ const size = getSize(itemPath);
2893
+ if (size > 0) {
2894
+ const result = await processItemWithConfirm(itemPath, size, MSG.CAT_OLD_DOWNLOADS);
2895
+ oldDownloadsSize += result.cleaned;
2896
+ if (result.deleted) deletedCount++;
2897
+ if (result.skipped) skippedCount++;
2898
+ }
2899
+ }
2900
+ } catch (e) {}
2901
+ }
2902
+ } catch (e) {}
2903
+ }
2904
+ }
2905
+ if (oldDownloadsSize > 0) {
2906
+ results.push({ category: MSG.CAT_OLD_DOWNLOADS, size: oldDownloadsSize });
2907
+ totalCleaned += oldDownloadsSize;
2908
+ }
2909
+
2910
+ // 8. Mail Attachments
2911
+ await cleanCategory(MSG.CAT_MAIL, [
2912
+ '~/Library/Mail Downloads',
2913
+ '~/Library/Containers/com.apple.mail/Data/Library/Mail Downloads'
2914
+ ], MSG.CAT_MAIL);
2915
+
2916
+ // 9. Trash from all volumes
2917
+ let trashSize = 0;
2918
+ const userTrash = path.join(HOME, '.Trash');
2919
+ trashSize = getSize(userTrash);
2920
+ if (trashSize > 0) {
2921
+ if (isDryRun) {
2922
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${MSG.CAT_TRASH} ${chalk.dim('(' + formatSize(trashSize) + ')')}`);
2923
+ deletedCount++;
2924
+ } else if (autoMode) {
2925
+ const spinner9 = ora(`${MSG.CAT_TRASH}...`).start();
2926
+ exec('rm -rf ~/.Trash/* 2>/dev/null');
2927
+ exec('rm -rf /Volumes/*/.Trashes/* 2>/dev/null');
2928
+ spinner9.succeed(`${MSG.CAT_TRASH}: ${chalk.green(formatSize(trashSize))}`);
2929
+ deletedCount++;
2930
+ } else {
2931
+ const answer = await confirmItem(userTrash, trashSize);
2932
+ if (answer === 'all') {
2933
+ autoMode = true;
2934
+ exec('rm -rf ~/.Trash/* 2>/dev/null');
2935
+ exec('rm -rf /Volumes/*/.Trashes/* 2>/dev/null');
2936
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_TRASH}`);
2937
+ deletedCount++;
2938
+ } else if (answer === 'yes') {
2939
+ exec('rm -rf ~/.Trash/* 2>/dev/null');
2940
+ exec('rm -rf /Volumes/*/.Trashes/* 2>/dev/null');
2941
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_TRASH}`);
2942
+ deletedCount++;
2943
+ } else {
2944
+ trashSize = 0;
2945
+ skippedCount++;
2946
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${MSG.CAT_TRASH}`);
2947
+ }
2948
+ }
2949
+ }
2950
+ if (trashSize > 0) {
2951
+ results.push({ category: MSG.CAT_TRASH, size: trashSize });
2952
+ totalCleaned += trashSize;
2953
+ }
595
2954
 
596
- const parts = choice.split(',');
597
- parts.forEach(part => {
598
- part = part.trim();
599
- if (part.includes('-')) {
600
- const [start, end] = part.split('-').map(n => parseInt(n));
601
- for (let j = start; j <= end; j++) {
602
- if (j > 0 && j <= items.length) items[j - 1].selected = true;
603
- }
2955
+ // 10. Font Caches
2956
+ await cleanCategory(MSG.CAT_FONTS, [
2957
+ '~/Library/Caches/com.apple.FontRegistry',
2958
+ '/Library/Caches/com.apple.ATS'
2959
+ ], MSG.CAT_FONTS);
2960
+
2961
+ // 11. Adobe Caches
2962
+ await cleanCategory(MSG.CAT_ADOBE, [
2963
+ '~/Library/Caches/Adobe',
2964
+ '~/Library/Application Support/Adobe/Common/Media Cache',
2965
+ '~/Library/Application Support/Adobe/Common/Media Cache Files'
2966
+ ], MSG.CAT_ADOBE);
2967
+
2968
+ // 12. Spotify/Music Caches
2969
+ await cleanCategory(MSG.CAT_MUSIC, [
2970
+ '~/Library/Caches/com.spotify.client',
2971
+ '~/Library/Application Support/Spotify/PersistentCache',
2972
+ '~/Library/Caches/com.apple.Music'
2973
+ ], MSG.CAT_MUSIC);
2974
+
2975
+ // 13. Old Log Files
2976
+ let oldLogsSize = 0;
2977
+ oldLogsSize = parseInt(exec('find ~/Library/Logs -type f -mtime +7 -exec du -sm {} \\; 2>/dev/null | awk \'{sum+=$1} END {print sum}\'')) || 0;
2978
+ if (oldLogsSize > 0) {
2979
+ if (isDryRun) {
2980
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${MSG.CAT_LOGS} ${chalk.dim('(' + formatSize(oldLogsSize) + ')')}`);
2981
+ deletedCount++;
2982
+ } else if (autoMode) {
2983
+ const spinner13 = ora(`${MSG.CAT_LOGS}...`).start();
2984
+ exec('find ~/Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2985
+ exec('find /Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2986
+ spinner13.succeed(`${MSG.CAT_LOGS}: ${chalk.green(formatSize(oldLogsSize))}`);
2987
+ deletedCount++;
2988
+ } else {
2989
+ const answer = await confirmItem('~/Library/Logs (old files)', oldLogsSize);
2990
+ if (answer === 'all') {
2991
+ autoMode = true;
2992
+ exec('find ~/Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2993
+ exec('find /Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2994
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_LOGS}`);
2995
+ deletedCount++;
2996
+ } else if (answer === 'yes') {
2997
+ exec('find ~/Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2998
+ exec('find /Library/Logs -type f -mtime +7 -delete 2>/dev/null');
2999
+ console.log(` ${chalk.green(figures.tick)} ${MSG.CAT_LOGS}`);
3000
+ deletedCount++;
604
3001
  } else {
605
- const idx = parseInt(part) - 1;
606
- if (idx >= 0 && idx < items.length) items[idx].selected = true;
3002
+ oldLogsSize = 0;
3003
+ skippedCount++;
3004
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${MSG.CAT_LOGS}`);
607
3005
  }
608
- });
3006
+ }
3007
+ }
3008
+ if (oldLogsSize > 0) {
3009
+ results.push({ category: MSG.CAT_LOGS, size: oldLogsSize });
3010
+ totalCleaned += oldLogsSize;
609
3011
  }
610
3012
 
611
- // Clean
612
- console.log();
613
- sectionHeader(MSG.CLEANING);
3013
+ // Clean npm cache
3014
+ if (!isDryRun) {
3015
+ try {
3016
+ execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
3017
+ } catch (e) {}
3018
+ }
614
3019
 
615
- let cleanedSize = 0;
3020
+ const endTime = Date.now();
3021
+ const duration = Math.round((endTime - startTime) / 1000);
616
3022
 
617
- for (const item of items) {
618
- if (!item.selected) continue;
3023
+ // Final Report
3024
+ showCleanupSummary(deletedCount, skippedCount, totalCleaned, confirmMode);
619
3025
 
620
- if (item.path === 'CLAUDE_VERSIONS') {
621
- const versions = fs.readdirSync(claudeVersionsPath).sort().reverse();
622
- versions.slice(1).forEach(v => {
623
- try {
624
- fs.rmSync(path.join(claudeVersionsPath, v), { recursive: true, force: true });
625
- } catch (e) {}
626
- });
627
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
628
- cleanedSize += item.size;
629
- } else if (item.path === 'OLD_DOWNLOADS') {
630
- exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
631
- exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
632
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
633
- cleanedSize += item.size;
634
- } else if (fs.existsSync(item.path)) {
635
- try {
636
- fs.rmSync(item.path, { recursive: true, force: true });
637
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
638
- cleanedSize += item.size;
639
- } catch (e) {}
640
- }
3026
+ if (results.length > 0) {
3027
+ console.log(chalk.white(' Breakdown:'));
3028
+ results.sort((a, b) => b.size - a.size);
3029
+ results.forEach(r => {
3030
+ console.log(` ${figures.bullet} ${r.category.padEnd(25)} ${chalk.green(formatSize(r.size))}`);
3031
+ });
3032
+ console.log();
641
3033
  }
642
3034
 
643
- // npm cache
644
- try {
645
- execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
646
- } catch (e) {}
647
-
3035
+ console.log(` ${chalk.white(MSG.TOTAL + ':')} ${chalk.green.bold(formatSize(totalCleaned))}`);
3036
+ console.log(` ${chalk.dim('Duration: ' + duration + 's')}`);
648
3037
  console.log();
3038
+
649
3039
  getSystemStats();
650
- showFinalReport(cleanedSize);
3040
+ showFinalReport(totalCleaned);
651
3041
  await pressEnter();
652
3042
  }
653
3043
 
654
- // ══════════════════════════════════════════════════════════════════════════════
655
- // OPTION 2: LARGE FILES
656
- // ══════════════════════════════════════════════════════════════════════════════
3044
+ // ============================================================================
3045
+ // FIND LARGE FILES
3046
+ // ============================================================================
657
3047
 
658
3048
  async function findLargeFiles() {
659
3049
  header();
660
3050
  sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
661
3051
 
662
- console.log(` ${c.dim}${MSG.SCANNING}${c.reset}`);
663
- console.log();
3052
+ const spinner = ora(MSG.SCANNING).start();
664
3053
 
665
3054
  const files = [];
666
3055
 
@@ -681,8 +3070,10 @@ async function findLargeFiles() {
681
3070
  files.push({ path: file, size });
682
3071
  });
683
3072
 
3073
+ spinner.stop();
3074
+
684
3075
  if (files.length === 0) {
685
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
3076
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
686
3077
  await pressEnter();
687
3078
  return;
688
3079
  }
@@ -692,74 +3083,132 @@ async function findLargeFiles() {
692
3083
 
693
3084
  const totalLarge = files.reduce((sum, f) => sum + f.size, 0);
694
3085
 
695
- files.forEach((file, i) => {
3086
+ // Build checkbox choices for inquirer
3087
+ const fileChoices = files.map((file, i) => {
696
3088
  let displayFile = file.path.replace(HOME, '~');
697
- if (displayFile.length > 50) {
698
- displayFile = '...' + displayFile.slice(-47);
3089
+ if (displayFile.length > 45) {
3090
+ displayFile = '...' + displayFile.slice(-42);
699
3091
  }
700
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${c.dim}${String(file.size).padStart(6)} MB${c.reset} ${displayFile}`);
3092
+ return {
3093
+ name: `${chalk.dim(String(file.size).padStart(6) + ' MB')} ${displayFile}`,
3094
+ value: file.path,
3095
+ short: path.basename(file.path)
3096
+ };
701
3097
  });
702
3098
 
703
- console.log();
704
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalLarge)}${c.reset} ${c.dim}(${files.length} files)${c.reset}`);
3099
+ // Fallback for text-based selection
3100
+ const fallbackSelect = async () => {
3101
+ files.forEach((file, i) => {
3102
+ let displayFile = file.path.replace(HOME, '~');
3103
+ if (displayFile.length > 50) {
3104
+ displayFile = '...' + displayFile.slice(-47);
3105
+ }
3106
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${chalk.dim(String(file.size).padStart(6) + ' MB')} ${displayFile}`);
3107
+ });
3108
+ console.log();
3109
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalLarge))} ${chalk.dim('(' + files.length + ' files)')}`);
3110
+ console.log();
3111
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
3112
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3113
+
3114
+ if (choice.toLowerCase() === 'n' || choice === '') return [];
3115
+
3116
+ const selected = new Array(files.length).fill(false);
3117
+ if (choice.toLowerCase() === 'a') {
3118
+ selected.fill(true);
3119
+ } else {
3120
+ choice.split(',').forEach(part => {
3121
+ part = part.trim();
3122
+ if (part.includes('-')) {
3123
+ const [start, end] = part.split('-').map(n => parseInt(n));
3124
+ for (let j = start; j <= end; j++) {
3125
+ if (j > 0 && j <= files.length) selected[j - 1] = true;
3126
+ }
3127
+ } else {
3128
+ const idx = parseInt(part) - 1;
3129
+ if (idx >= 0 && idx < files.length) selected[idx] = true;
3130
+ }
3131
+ });
3132
+ }
3133
+ return files.filter((f, i) => selected[i]).map(f => f.path);
3134
+ };
3135
+
3136
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalLarge))} ${chalk.dim('(' + files.length + ' files)')}`);
705
3137
  console.log();
706
3138
 
707
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/a/n]:${c.reset}`);
708
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
3139
+ const selectedPaths = await selectMultiple(
3140
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to select, enter to confirm)')}`,
3141
+ fileChoices,
3142
+ fallbackSelect
3143
+ );
709
3144
 
710
- if (choice.toLowerCase() === 'n' || choice === '') {
3145
+ if (selectedPaths.length === 0) {
711
3146
  await pressEnter();
712
3147
  return;
713
3148
  }
714
3149
 
715
- const selected = new Array(files.length).fill(false);
716
-
717
- if (choice.toLowerCase() === 'a') {
718
- selected.fill(true);
719
- } else {
720
- const parts = choice.split(',');
721
- parts.forEach(part => {
722
- part = part.trim();
723
- if (part.includes('-')) {
724
- const [start, end] = part.split('-').map(n => parseInt(n));
725
- for (let j = start; j <= end; j++) {
726
- if (j > 0 && j <= files.length) selected[j - 1] = true;
727
- }
728
- } else {
729
- const idx = parseInt(part) - 1;
730
- if (idx >= 0 && idx < files.length) selected[idx] = true;
731
- }
732
- });
3150
+ const selectedFiles = files.filter(f => selectedPaths.includes(f.path));
3151
+ if (selectedFiles.length === 0) {
3152
+ await pressEnter();
3153
+ return;
733
3154
  }
734
3155
 
3156
+ // Ask confirmation mode
3157
+ const confirmMode = await askConfirmationMode();
3158
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
3159
+ let autoMode = confirmMode === 'auto';
3160
+
735
3161
  let deletedSize = 0;
3162
+ let deletedCount = 0;
3163
+ let skippedCount = 0;
736
3164
  console.log();
737
3165
 
738
- for (let i = 0; i < files.length; i++) {
739
- if (!selected[i]) continue;
3166
+ for (const file of selectedFiles) {
3167
+ let shouldDelete = autoMode;
740
3168
 
741
- try {
742
- fs.unlinkSync(files[i].path);
743
- console.log(` ${c.green}${CHECK}${c.reset} Deleted: ${path.basename(files[i].path)}`);
744
- deletedSize += files[i].size;
745
- } catch (e) {}
3169
+ if (!autoMode && confirmMode === 'manual') {
3170
+ const answer = await confirmItem(file.path, file.size);
3171
+ if (answer === 'all') {
3172
+ autoMode = true;
3173
+ shouldDelete = true;
3174
+ } else if (answer === 'yes') {
3175
+ shouldDelete = true;
3176
+ } else {
3177
+ shouldDelete = false;
3178
+ }
3179
+ }
3180
+
3181
+ if (isDryRun) {
3182
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${path.basename(file.path)} ${chalk.dim('(' + formatSize(file.size) + ')')}`);
3183
+ deletedSize += file.size;
3184
+ deletedCount++;
3185
+ } else if (shouldDelete) {
3186
+ if (deleteItem(file.path)) {
3187
+ console.log(` ${chalk.green(figures.tick)} ${MSG.SUMMARY_DELETED}: ${path.basename(file.path)}`);
3188
+ deletedSize += file.size;
3189
+ deletedCount++;
3190
+ }
3191
+ } else {
3192
+ skippedCount++;
3193
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${path.basename(file.path)}`);
3194
+ }
746
3195
  }
747
3196
 
748
- console.log();
749
- console.log(` ${c.green}${MSG.FREED}: ${formatSize(deletedSize)}${c.reset}`);
3197
+ showCleanupSummary(deletedCount, skippedCount, deletedSize, confirmMode);
3198
+
3199
+ playNotificationSound();
750
3200
  await pressEnter();
751
3201
  }
752
3202
 
753
- // ══════════════════════════════════════════════════════════════════════════════
754
- // OPTION 3: FIND DUPLICATES
755
- // ══════════════════════════════════════════════════════════════════════════════
3203
+ // ============================================================================
3204
+ // FIND DUPLICATES
3205
+ // ============================================================================
756
3206
 
757
3207
  async function findDuplicates() {
758
3208
  header();
759
3209
  sectionHeader(MSG.DUPLICATES);
760
3210
 
761
- console.log(` ${c.dim}${MSG.SCANNING} (this may take a while)...${c.reset}`);
762
- console.log();
3211
+ const spinner = ora(`${MSG.SCANNING} (this may take a while)...`).start();
763
3212
 
764
3213
  // Find files > 1MB, group by size
765
3214
  const sizeMap = new Map();
@@ -776,7 +3225,7 @@ async function findDuplicates() {
776
3225
  } catch (e) {}
777
3226
  });
778
3227
 
779
- console.log(` ${c.dim}${MSG.HASHING}${c.reset}`);
3228
+ spinner.text = MSG.HASHING;
780
3229
 
781
3230
  // Find duplicates by hash for files with same size
782
3231
  const hashGroups = new Map();
@@ -795,59 +3244,117 @@ async function findDuplicates() {
795
3244
  }
796
3245
  }
797
3246
 
798
- // Display duplicate groups
799
- header();
800
- sectionHeader(MSG.DUPLICATES);
3247
+ spinner.stop();
801
3248
 
802
- let groupNum = 0;
3249
+ // Collect duplicate groups
3250
+ const duplicateGroups = [];
803
3251
  let totalDupSize = 0;
804
- const allDups = [];
805
3252
 
806
3253
  for (const [hash, fileList] of hashGroups) {
807
3254
  if (fileList.length < 2) continue;
808
3255
 
809
- groupNum++;
810
3256
  const fileSize = Math.round(getSize(fileList[0]));
811
3257
  const dupSpace = (fileList.length - 1) * fileSize;
812
3258
  totalDupSize += dupSpace;
813
3259
 
814
- console.log(` ${c.white}${MSG.GROUP} ${groupNum}${c.reset} ${c.dim}(${fileSize} MB ${MSG.EACH})${c.reset}`);
3260
+ duplicateGroups.push({ hash, files: fileList, fileSize });
3261
+ if (duplicateGroups.length >= 10) break;
3262
+ }
3263
+
3264
+ if (duplicateGroups.length === 0) {
3265
+ header();
3266
+ sectionHeader(MSG.DUPLICATES);
3267
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
3268
+ await pressEnter();
3269
+ return;
3270
+ }
3271
+
3272
+ // Process each duplicate group
3273
+ let totalDeletedSize = 0;
3274
+ let totalDeletedCount = 0;
3275
+ const isDryRun = DRY_RUN;
3276
+
3277
+ for (let groupIdx = 0; groupIdx < duplicateGroups.length; groupIdx++) {
3278
+ const group = duplicateGroups[groupIdx];
3279
+ header();
3280
+ sectionHeader(`${MSG.DUPLICATES} - ${MSG.GROUP} ${groupIdx + 1}/${duplicateGroups.length}`);
3281
+
3282
+ console.log(` ${chalk.dim(group.fileSize + ' MB ' + MSG.EACH + ' - ' + group.files.length + ' files')}`);
3283
+ console.log();
815
3284
 
816
- fileList.forEach((file, idx) => {
3285
+ // Build checkbox choices - select files to KEEP (unselected will be deleted)
3286
+ const fileChoices = group.files.map((file, i) => {
817
3287
  let display = file.replace(HOME, '~');
818
3288
  if (display.length > 50) display = '...' + display.slice(-47);
819
- console.log(` ${c.cyan}[${idx + 1}]${c.reset} ${display}`);
820
- allDups.push(file);
3289
+ return {
3290
+ name: display,
3291
+ value: file,
3292
+ checked: i === 0 // First file is selected (kept) by default
3293
+ };
821
3294
  });
822
- console.log();
823
3295
 
824
- if (groupNum >= 10) break;
825
- }
3296
+ const fallbackKeep = async () => {
3297
+ group.files.forEach((file, idx) => {
3298
+ let display = file.replace(HOME, '~');
3299
+ if (display.length > 50) display = '...' + display.slice(-47);
3300
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${display}`);
3301
+ });
3302
+ console.log();
3303
+ console.log(chalk.dim(` ${MSG.SELECT_KEEP} [numbers, default: 1]:`));
3304
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3305
+ if (!choice || choice === '1') return [group.files[0]];
3306
+ const indices = choice.split(',').map(n => parseInt(n.trim()) - 1).filter(n => n >= 0 && n < group.files.length);
3307
+ return indices.length > 0 ? indices.map(i => group.files[i]) : [group.files[0]];
3308
+ };
3309
+
3310
+ const keepFiles = await selectMultiple(
3311
+ `${MSG.SELECT_KEEP} ${chalk.dim('(space to select files to KEEP)')}`,
3312
+ fileChoices,
3313
+ fallbackKeep
3314
+ );
3315
+
3316
+ // Files not in keepFiles will be deleted
3317
+ const filesToDelete = group.files.filter(f => !keepFiles.includes(f));
3318
+
3319
+ if (filesToDelete.length === 0) {
3320
+ console.log(` ${chalk.yellow(figures.info)} ${LANG_CODE === 'pt' ? 'Nenhum ficheiro a apagar' : 'No files to delete'}`);
3321
+ continue;
3322
+ }
826
3323
 
827
- if (groupNum === 0) {
828
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
829
- await pressEnter();
830
- return;
3324
+ console.log();
3325
+ for (const file of filesToDelete) {
3326
+ if (isDryRun) {
3327
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${path.basename(file)}`);
3328
+ totalDeletedSize += group.fileSize;
3329
+ totalDeletedCount++;
3330
+ } else {
3331
+ if (deleteItem(file)) {
3332
+ console.log(` ${chalk.green(figures.tick)} ${MSG.SUMMARY_DELETED}: ${path.basename(file)}`);
3333
+ totalDeletedSize += group.fileSize;
3334
+ totalDeletedCount++;
3335
+ }
3336
+ }
3337
+ }
831
3338
  }
832
3339
 
833
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalDupSize)}${c.reset} ${MSG.IN_DUPLICATES}`);
834
- console.log();
835
- console.log(` ${c.dim}${MSG.SELECT_KEEP}${c.reset}`);
836
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
3340
+ // Show summary
3341
+ header();
3342
+ sectionHeader(MSG.DUPLICATES);
3343
+ showCleanupSummary(totalDeletedCount, 0, totalDeletedSize, isDryRun ? 'dryrun' : 'auto');
837
3344
 
838
- console.log(` ${c.yellow}${MSG.FEATURE_PROGRESS}${c.reset}`);
3345
+ playNotificationSound();
839
3346
  await pressEnter();
840
3347
  }
841
3348
 
842
- // ══════════════════════════════════════════════════════════════════════════════
843
- // OPTION 4: UNINSTALL APPS
844
- // ══════════════════════════════════════════════════════════════════════════════
3349
+ // ============================================================================
3350
+ // UNINSTALL APPS
3351
+ // ============================================================================
845
3352
 
846
3353
  async function uninstallApps() {
847
3354
  header();
848
3355
  sectionHeader(MSG.UNINSTALL);
849
3356
 
850
- console.log(` ${c.dim}${MSG.SCANNING}${c.reset}`);
3357
+ const spinner = ora(MSG.SCANNING).start();
851
3358
 
852
3359
  const apps = [];
853
3360
 
@@ -893,78 +3400,148 @@ async function uninstallApps() {
893
3400
  if (apps.length >= 25) break;
894
3401
  }
895
3402
 
3403
+ spinner.stop();
3404
+
896
3405
  header();
897
3406
  sectionHeader(MSG.UNINSTALL);
898
3407
 
899
3408
  if (apps.length === 0) {
900
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
3409
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
901
3410
  await pressEnter();
902
3411
  return;
903
3412
  }
904
3413
 
905
- apps.forEach((app, i) => {
906
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${app.name.padEnd(25)} ${c.dim}(${String(app.appSize).padStart(3)} MB ${MSG.APP_SIZE} + ${String(app.dataSize).padStart(3)} MB ${MSG.DATA_SIZE})${c.reset}`);
907
- });
3414
+ // Build checkbox choices for inquirer
3415
+ const appChoices = apps.map((app, i) => ({
3416
+ name: `${app.name.padEnd(25)} ${chalk.dim('(' + String(app.appSize).padStart(3) + ' MB ' + MSG.APP_SIZE + ' + ' + String(app.dataSize).padStart(3) + ' MB ' + MSG.DATA_SIZE + ')')}`,
3417
+ value: app.path,
3418
+ short: app.name
3419
+ }));
3420
+
3421
+ // Fallback for text-based selection
3422
+ const fallbackAppSelect = async () => {
3423
+ apps.forEach((app, i) => {
3424
+ 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 + ')')}`);
3425
+ });
908
3426
 
909
- console.log();
910
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/n]:${c.reset}`);
911
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
3427
+ console.log();
3428
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/n]:`));
3429
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3430
+
3431
+ if (choice.toLowerCase() === 'n' || choice === '') {
3432
+ return [];
3433
+ }
912
3434
 
913
- if (choice.toLowerCase() === 'n' || choice === '') {
3435
+ const selected = [];
3436
+ const parts = choice.split(',');
3437
+ for (const part of parts) {
3438
+ const idx = parseInt(part.trim()) - 1;
3439
+ if (idx >= 0 && idx < apps.length) {
3440
+ selected.push(apps[idx].path);
3441
+ }
3442
+ }
3443
+ return selected;
3444
+ };
3445
+
3446
+ const selectedPaths = await selectMultiple(
3447
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to select, enter to confirm)')}`,
3448
+ appChoices,
3449
+ fallbackAppSelect
3450
+ );
3451
+
3452
+ if (selectedPaths.length === 0) {
914
3453
  await pressEnter();
915
3454
  return;
916
3455
  }
917
3456
 
918
- const parts = choice.split(',');
919
- for (const part of parts) {
920
- const idx = parseInt(part.trim()) - 1;
921
- if (idx < 0 || idx >= apps.length) continue;
3457
+ // Collect selected apps
3458
+ const selectedApps = apps.filter(app => selectedPaths.includes(app.path));
922
3459
 
923
- const app = apps[idx];
924
- console.log(` ${c.yellow}${MSG.DELETING} ${app.name}...${c.reset}`);
925
-
926
- // Remove app data
927
- if (app.bundleId) {
928
- const dataPaths = [
929
- path.join(HOME, 'Library/Application Support', app.name),
930
- path.join(HOME, 'Library/Application Support', app.bundleId),
931
- path.join(HOME, 'Library/Preferences', `${app.bundleId}.plist`),
932
- path.join(HOME, 'Library/Caches', app.bundleId),
933
- path.join(HOME, 'Library/Caches', app.name),
934
- path.join(HOME, 'Library/Containers', app.bundleId),
935
- path.join(HOME, 'Library/Saved Application State', `${app.bundleId}.savedState`),
936
- path.join(HOME, 'Library/Logs', app.name)
937
- ];
3460
+ if (selectedApps.length === 0) {
3461
+ await pressEnter();
3462
+ return;
3463
+ }
938
3464
 
939
- dataPaths.forEach(dp => {
940
- try {
941
- if (fs.existsSync(dp)) fs.rmSync(dp, { recursive: true, force: true });
942
- } catch (e) {}
943
- });
3465
+ // Ask confirmation mode
3466
+ const confirmMode = await askConfirmationMode();
3467
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
3468
+ let autoMode = confirmMode === 'auto';
3469
+
3470
+ let deletedCount = 0;
3471
+ let skippedCount = 0;
3472
+ let freedSize = 0;
3473
+
3474
+ for (const app of selectedApps) {
3475
+ const totalSize = app.appSize + app.dataSize;
3476
+ let shouldDelete = autoMode;
3477
+
3478
+ if (!autoMode && confirmMode === 'manual') {
3479
+ const answer = await confirmItem(app.path, totalSize);
3480
+ if (answer === 'all') {
3481
+ autoMode = true;
3482
+ shouldDelete = true;
3483
+ } else if (answer === 'yes') {
3484
+ shouldDelete = true;
3485
+ } else {
3486
+ shouldDelete = false;
3487
+ }
944
3488
  }
945
3489
 
946
- // Remove app
947
- try {
948
- fs.rmSync(app.path, { recursive: true, force: true });
949
- console.log(` ${c.green}${CHECK}${c.reset} ${app.name} ${MSG.REMOVED}`);
950
- } catch (e) {
951
- console.log(` ${c.red}${MSG.NEED_SUDO}${c.reset}`);
952
- console.log(` ${c.dim}sudo rm -rf "${app.path}"${c.reset}`);
3490
+ if (isDryRun) {
3491
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${app.name} ${chalk.dim('(' + formatSize(totalSize) + ')')}`);
3492
+ freedSize += totalSize;
3493
+ deletedCount++;
3494
+ } else if (shouldDelete) {
3495
+ console.log(` ${chalk.yellow(MSG.DELETING + ' ' + app.name + '...')}`);
3496
+
3497
+ // Remove app data
3498
+ if (app.bundleId) {
3499
+ const dataPaths = [
3500
+ path.join(HOME, 'Library/Application Support', app.name),
3501
+ path.join(HOME, 'Library/Application Support', app.bundleId),
3502
+ path.join(HOME, 'Library/Preferences', `${app.bundleId}.plist`),
3503
+ path.join(HOME, 'Library/Caches', app.bundleId),
3504
+ path.join(HOME, 'Library/Caches', app.name),
3505
+ path.join(HOME, 'Library/Containers', app.bundleId),
3506
+ path.join(HOME, 'Library/Saved Application State', `${app.bundleId}.savedState`),
3507
+ path.join(HOME, 'Library/Logs', app.name)
3508
+ ];
3509
+
3510
+ dataPaths.forEach(dp => {
3511
+ try {
3512
+ if (fs.existsSync(dp)) deleteItem(dp);
3513
+ } catch (e) {}
3514
+ });
3515
+ }
3516
+
3517
+ // Remove app
3518
+ if (deleteItem(app.path)) {
3519
+ console.log(` ${chalk.green(figures.tick)} ${app.name} ${MSG.REMOVED}`);
3520
+ freedSize += totalSize;
3521
+ deletedCount++;
3522
+ } else {
3523
+ console.log(` ${chalk.red(MSG.NEED_SUDO)}`);
3524
+ console.log(` ${chalk.dim('sudo rm -rf "' + app.path + '"')}`);
3525
+ }
3526
+ } else {
3527
+ skippedCount++;
3528
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${app.name}`);
953
3529
  }
954
3530
  }
955
3531
 
3532
+ showCleanupSummary(deletedCount, skippedCount, freedSize, confirmMode);
956
3533
  await pressEnter();
957
3534
  }
958
3535
 
959
- // ══════════════════════════════════════════════════════════════════════════════
960
- // OPTION 5: MANAGE STARTUP
961
- // ══════════════════════════════════════════════════════════════════════════════
3536
+ // ============================================================================
3537
+ // MANAGE STARTUP
3538
+ // ============================================================================
962
3539
 
963
3540
  async function manageStartup() {
964
3541
  header();
965
3542
  sectionHeader(MSG.STARTUP_ITEMS);
966
3543
 
967
- console.log(` ${c.white}${MSG.STARTUP_ITEMS}:${c.reset}`);
3544
+ console.log(` ${chalk.white(MSG.STARTUP_ITEMS + ':')}`);
968
3545
 
969
3546
  // Get login items via AppleScript
970
3547
  const loginItems = exec(`osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null`);
@@ -972,14 +3549,14 @@ async function manageStartup() {
972
3549
  if (loginItems) {
973
3550
  const items = loginItems.split(', ').filter(i => i.trim());
974
3551
  items.forEach((item, idx) => {
975
- console.log(` ${c.cyan}[${idx + 1}]${c.reset} ${c.green}${CHECK}${c.reset} ${item} ${c.dim}(${MSG.ENABLED})${c.reset}`);
3552
+ console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${chalk.green(figures.tick)} ${item} ${chalk.dim('(' + MSG.ENABLED + ')')}`);
976
3553
  });
977
3554
  } else {
978
- console.log(` ${c.dim}No login items found${c.reset}`);
3555
+ console.log(chalk.dim(' No login items found'));
979
3556
  }
980
3557
 
981
3558
  console.log();
982
- console.log(` ${c.white}${MSG.LAUNCH_AGENTS}:${c.reset}`);
3559
+ console.log(` ${chalk.white(MSG.LAUNCH_AGENTS + ':')}`);
983
3560
 
984
3561
  // User Launch Agents
985
3562
  const launchAgentsDir = path.join(HOME, 'Library/LaunchAgents');
@@ -993,29 +3570,41 @@ async function manageStartup() {
993
3570
  const loaded = exec(`launchctl list 2>/dev/null | grep "${name}"`);
994
3571
 
995
3572
  const status = loaded ?
996
- `${c.green}${CHECK}${c.reset} ${MSG.ENABLED}` :
997
- `${c.red}${CROSS}${c.reset} ${MSG.DISABLED}`;
3573
+ `${chalk.green(figures.tick)} ${MSG.ENABLED}` :
3574
+ `${chalk.red(figures.cross)} ${MSG.DISABLED}`;
998
3575
 
999
- console.log(` ${c.cyan}[${idx}]${c.reset} ${status} ${c.dim}${name}${c.reset}`);
3576
+ console.log(` ${chalk.cyan('[' + idx + ']')} ${status} ${chalk.dim(name)}`);
1000
3577
  idx++;
1001
3578
  }
1002
3579
  }
1003
3580
 
1004
3581
  console.log();
1005
- console.log(` ${c.dim}Enter number to toggle, 'd' to disable all, or 'n' to go back${c.reset}`);
1006
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
1007
3582
 
1008
- if (choice.toLowerCase() === 'd') {
3583
+ const startupChoices = [
3584
+ { name: 'Disable all login items', value: 'disable_all' },
3585
+ { name: chalk.yellow(MSG.BACK), value: 'back' }
3586
+ ];
3587
+
3588
+ const fallbackStartup = async () => {
3589
+ console.log(chalk.dim(" Enter 'd' to disable all, or 'n' to go back"));
3590
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3591
+ if (choice.toLowerCase() === 'd') return 'disable_all';
3592
+ return 'back';
3593
+ };
3594
+
3595
+ const choice = await selectOption(MSG.STARTUP_ITEMS, startupChoices, fallbackStartup);
3596
+
3597
+ if (choice === 'disable_all') {
1009
3598
  exec(`osascript -e 'tell application "System Events" to delete every login item' 2>/dev/null`);
1010
- console.log(` ${c.green}${CHECK}${c.reset} All login items disabled`);
3599
+ console.log(` ${chalk.green(figures.tick)} All login items disabled`);
1011
3600
  }
1012
3601
 
1013
3602
  await pressEnter();
1014
3603
  }
1015
3604
 
1016
- // ══════════════════════════════════════════════════════════════════════════════
1017
- // OPTION 6: CLEAN BROWSERS
1018
- // ══════════════════════════════════════════════════════════════════════════════
3605
+ // ============================================================================
3606
+ // CLEAN BROWSERS
3607
+ // ============================================================================
1019
3608
 
1020
3609
  async function cleanBrowsers() {
1021
3610
  header();
@@ -1033,79 +3622,144 @@ async function cleanBrowsers() {
1033
3622
  }
1034
3623
  }
1035
3624
 
1036
- console.log(` ${c.white}Chrome:${c.reset}`);
3625
+ console.log(` ${chalk.white('Chrome:')}`);
1037
3626
  addBrowserItem('~/Library/Caches/Google/Chrome', 'Chrome Cache');
1038
3627
  addBrowserItem('~/Library/Application Support/Google/Chrome/Default/Service Worker', 'Chrome Service Workers');
1039
3628
  addBrowserItem('~/Library/Application Support/Google/Chrome/Default/GPUCache', 'Chrome GPU Cache');
1040
3629
 
1041
- console.log(` ${c.white}Safari:${c.reset}`);
3630
+ console.log(` ${chalk.white('Safari:')}`);
1042
3631
  addBrowserItem('~/Library/Caches/com.apple.Safari', 'Safari Cache');
1043
3632
  addBrowserItem('~/Library/Safari/LocalStorage', 'Safari Local Storage');
1044
3633
 
1045
- console.log(` ${c.white}Firefox:${c.reset}`);
3634
+ console.log(` ${chalk.white('Firefox:')}`);
1046
3635
  const ffProfile = exec('find ~/Library/Application\\ Support/Firefox/Profiles -maxdepth 1 -name "*.default*" 2>/dev/null | head -1');
1047
3636
  if (ffProfile) {
1048
3637
  addBrowserItem(`${ffProfile}/cache2`, 'Firefox Cache');
1049
3638
  }
1050
3639
 
1051
- items.forEach((item, i) => {
1052
- console.log(` ${c.cyan}[${String(i + 1).padStart(2)}]${c.reset} ${item.name.padEnd(30)} ${c.dim}${String(item.size).padStart(6)} MB${c.reset}`);
1053
- });
1054
-
1055
3640
  if (items.length === 0) {
1056
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
3641
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1057
3642
  await pressEnter();
1058
3643
  return;
1059
3644
  }
1060
3645
 
1061
3646
  console.log();
1062
- console.log(` ${ARROW} ${c.white}${MSG.TOTAL}:${c.reset} ${c.green}${formatSize(totalBrowser)}${c.reset}`);
3647
+ console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalBrowser))}`);
1063
3648
  console.log();
1064
3649
 
1065
- console.log(` ${c.dim}${MSG.SELECT_DELETE} [numbers/a/n]:${c.reset}`);
1066
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
3650
+ // Build checkbox choices for inquirer
3651
+ const browserChoices = items.map((item, i) => ({
3652
+ name: `${item.name.padEnd(30)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`,
3653
+ value: item.path,
3654
+ short: item.name
3655
+ }));
3656
+
3657
+ // Fallback for text-based selection
3658
+ const fallbackBrowserSelect = async () => {
3659
+ items.forEach((item, i) => {
3660
+ console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
3661
+ });
3662
+ console.log();
3663
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
3664
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1067
3665
 
1068
- if (choice.toLowerCase() === 'n' || choice === '') {
1069
- await pressEnter();
1070
- return;
1071
- }
3666
+ if (choice.toLowerCase() === 'n' || choice === '') {
3667
+ return [];
3668
+ }
1072
3669
 
1073
- if (choice.toLowerCase() === 'a') {
1074
- items.forEach(item => item.selected = true);
1075
- } else {
3670
+ if (choice.toLowerCase() === 'a') {
3671
+ return items.map(item => item.path);
3672
+ }
3673
+
3674
+ const selected = [];
1076
3675
  const parts = choice.split(',');
1077
3676
  parts.forEach(part => {
1078
3677
  const idx = parseInt(part.trim()) - 1;
1079
- if (idx >= 0 && idx < items.length) items[idx].selected = true;
3678
+ if (idx >= 0 && idx < items.length) selected.push(items[idx].path);
1080
3679
  });
3680
+ return selected;
3681
+ };
3682
+
3683
+ const selectedPaths = await selectMultiple(
3684
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to select, enter to confirm)')}`,
3685
+ browserChoices,
3686
+ fallbackBrowserSelect
3687
+ );
3688
+
3689
+ if (selectedPaths.length === 0) {
3690
+ await pressEnter();
3691
+ return;
3692
+ }
3693
+
3694
+ // Mark selected items
3695
+ items.forEach(item => {
3696
+ item.selected = selectedPaths.includes(item.path);
3697
+ });
3698
+
3699
+ const selectedItems = items.filter(i => i.selected);
3700
+ if (selectedItems.length === 0) {
3701
+ await pressEnter();
3702
+ return;
1081
3703
  }
1082
3704
 
3705
+ // Ask confirmation mode
3706
+ const confirmMode = await askConfirmationMode();
3707
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
3708
+ let autoMode = confirmMode === 'auto';
3709
+
1083
3710
  let cleaned = 0;
3711
+ let deletedCount = 0;
3712
+ let skippedCount = 0;
1084
3713
  console.log();
1085
3714
 
1086
3715
  for (const item of items) {
1087
3716
  if (!item.selected) continue;
1088
- try {
1089
- fs.rmSync(item.path, { recursive: true, force: true });
1090
- console.log(` ${c.green}${CHECK}${c.reset} ${item.name}`);
3717
+
3718
+ let shouldDelete = autoMode;
3719
+
3720
+ if (!autoMode && confirmMode === 'manual') {
3721
+ const answer = await confirmItem(item.path, item.size);
3722
+ if (answer === 'all') {
3723
+ autoMode = true;
3724
+ shouldDelete = true;
3725
+ } else if (answer === 'yes') {
3726
+ shouldDelete = true;
3727
+ } else {
3728
+ shouldDelete = false;
3729
+ }
3730
+ }
3731
+
3732
+ if (isDryRun) {
3733
+ console.log(` ${chalk.blue(figures.info)} [DRY-RUN] ${item.name} ${chalk.dim('(' + formatSize(item.size) + ')')}`);
1091
3734
  cleaned += item.size;
1092
- } catch (e) {}
3735
+ deletedCount++;
3736
+ } else if (shouldDelete) {
3737
+ if (deleteItem(item.path)) {
3738
+ console.log(` ${chalk.green(figures.tick)} ${item.name}`);
3739
+ cleaned += item.size;
3740
+ deletedCount++;
3741
+ }
3742
+ } else {
3743
+ skippedCount++;
3744
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${item.name}`);
3745
+ }
1093
3746
  }
1094
3747
 
1095
- console.log();
1096
- console.log(` ${c.green}${MSG.FREED}: ${formatSize(cleaned)}${c.reset}`);
3748
+ showCleanupSummary(deletedCount, skippedCount, cleaned, confirmMode);
3749
+
3750
+ playNotificationSound();
1097
3751
  await pressEnter();
1098
3752
  }
1099
3753
 
1100
- // ══════════════════════════════════════════════════════════════════════════════
1101
- // OPTION 7: KILL PROCESSES
1102
- // ══════════════════════════════════════════════════════════════════════════════
3754
+ // ============================================================================
3755
+ // KILL PROCESSES
3756
+ // ============================================================================
1103
3757
 
1104
3758
  async function killProcesses() {
1105
3759
  header();
1106
3760
  sectionHeader(MSG.PROCESS_MEM);
1107
3761
 
1108
- console.log(` ${c.white}${MSG.PROCESS_MEM}:${c.reset}`);
3762
+ console.log(` ${chalk.white(MSG.PROCESS_MEM + ':')}`);
1109
3763
 
1110
3764
  const procs = [];
1111
3765
  const totalMemBytes = parseInt(exec("sysctl hw.memsize 2>/dev/null | awk '{print $2}'")) || 8589934592;
@@ -1130,89 +3784,70 @@ async function killProcesses() {
1130
3784
 
1131
3785
  procs.push({ pid, name, memMB });
1132
3786
 
1133
- console.log(` ${c.cyan}[${String(procs.length).padStart(2)}]${c.reset} ${name.padEnd(30)} ${c.red}${memMB} MB RAM${c.reset}`);
3787
+ console.log(` ${chalk.cyan('[' + String(procs.length).padStart(2) + ']')} ${name.padEnd(30)} ${chalk.red(memMB + ' MB RAM')}`);
1134
3788
 
1135
3789
  if (procs.length >= 10) break;
1136
3790
  }
1137
3791
 
1138
3792
  if (procs.length === 0) {
1139
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.NO_ITEMS}`);
3793
+ console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
1140
3794
  await pressEnter();
1141
3795
  return;
1142
3796
  }
1143
3797
 
1144
3798
  console.log();
1145
- console.log(` ${c.dim}${MSG.KILL} process [number/n]:${c.reset}`);
1146
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
1147
3799
 
1148
- if (choice.toLowerCase() === 'n' || choice === '') {
1149
- await pressEnter();
1150
- return;
1151
- }
3800
+ // Build list choices for inquirer
3801
+ const processChoices = procs.map((proc, i) => ({
3802
+ name: `${proc.name.padEnd(30)} ${chalk.red(proc.memMB + ' MB RAM')}`,
3803
+ value: proc.pid,
3804
+ short: proc.name
3805
+ }));
3806
+ processChoices.push({ name: chalk.yellow(MSG.BACK), value: 'back' });
1152
3807
 
1153
- const idx = parseInt(choice) - 1;
1154
- if (idx >= 0 && idx < procs.length) {
1155
- const proc = procs[idx];
1156
- try {
1157
- process.kill(parseInt(proc.pid), 'SIGKILL');
1158
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.KILLED}: ${proc.name} (PID: ${proc.pid})`);
1159
- } catch (e) {
1160
- console.log(` ${c.red}${CROSS}${c.reset} ${MSG.FAILED_KILL} ${proc.name}`);
1161
- }
1162
- }
3808
+ const fallbackProcess = async () => {
3809
+ console.log(chalk.dim(` ${MSG.KILL} process [number/n]:`));
3810
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1163
3811
 
1164
- await pressEnter();
1165
- }
3812
+ if (choice.toLowerCase() === 'n' || choice === '') {
3813
+ return 'back';
3814
+ }
1166
3815
 
1167
- // ══════════════════════════════════════════════════════════════════════════════
1168
- // OPTION 8: STATISTICS
1169
- // ══════════════════════════════════════════════════════════════════════════════
3816
+ const idx = parseInt(choice) - 1;
3817
+ if (idx >= 0 && idx < procs.length) {
3818
+ return procs[idx].pid;
3819
+ }
3820
+ return 'back';
3821
+ };
1170
3822
 
1171
- async function showStatistics() {
1172
- header();
1173
- sectionHeader(MSG.OPT_STATS);
3823
+ const selectedPid = await selectOption(`${MSG.KILL} process`, processChoices, fallbackProcess);
1174
3824
 
1175
- if (!fs.existsSync(HISTORY_FILE)) {
1176
- console.log(` ${c.dim}${MSG.NO_HISTORY}${c.reset}`);
3825
+ if (selectedPid === 'back' || !selectedPid) {
1177
3826
  await pressEnter();
1178
3827
  return;
1179
3828
  }
1180
3829
 
1181
- const history = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(l => l.trim());
1182
- const currentMonth = new Date().toISOString().slice(0, 7);
1183
-
1184
- const monthLines = history.filter(l => l.startsWith(currentMonth));
1185
- const monthCleanups = monthLines.length;
1186
-
1187
- let monthFreed = 0;
1188
- let monthScoreSum = 0;
1189
-
1190
- monthLines.forEach(line => {
1191
- const freedMatch = line.match(/(\d+)\s*MB/);
1192
- const scoreMatch = line.match(/Score:\s*(\d+)/);
1193
- if (freedMatch) monthFreed += parseInt(freedMatch[1]);
1194
- if (scoreMatch) monthScoreSum += parseInt(scoreMatch[1]);
1195
- });
1196
-
1197
- const monthAvg = monthCleanups > 0 ? Math.round(monthScoreSum / monthCleanups) : 0;
1198
-
1199
- console.log(` ${c.white}${MSG.THIS_MONTH}:${c.reset}`);
1200
- console.log(` ${BULLET} ${MSG.CLEANINGS}: ${monthCleanups}`);
1201
- console.log(` ${BULLET} ${MSG.TOTAL} ${MSG.FREED}: ${formatSize(monthFreed)}`);
1202
- console.log(` ${BULLET} ${MSG.AVG_SCORE}: ${monthAvg}/100`);
1203
- console.log();
3830
+ const proc = procs.find(p => p.pid === selectedPid);
3831
+ if (proc) {
1204
3832
 
1205
- console.log(` ${c.white}${MSG.HISTORY}:${c.reset}`);
1206
- history.slice(-10).forEach(line => {
1207
- console.log(` ${c.dim}${line}${c.reset}`);
1208
- });
3833
+ if (DRY_RUN) {
3834
+ console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would kill: ${proc.name} (PID: ${proc.pid})`));
3835
+ } else {
3836
+ try {
3837
+ process.kill(parseInt(proc.pid), 'SIGKILL');
3838
+ console.log(` ${chalk.green(figures.tick)} ${MSG.KILLED}: ${proc.name} (PID: ${proc.pid})`);
3839
+ } catch (e) {
3840
+ console.log(` ${chalk.red(figures.cross)} ${MSG.FAILED_KILL} ${proc.name}`);
3841
+ }
3842
+ }
3843
+ }
1209
3844
 
1210
3845
  await pressEnter();
1211
3846
  }
1212
3847
 
1213
- // ══════════════════════════════════════════════════════════════════════════════
1214
- // OPTION 9: SETTINGS
1215
- // ══════════════════════════════════════════════════════════════════════════════
3848
+ // ============================================================================
3849
+ // SETTINGS
3850
+ // ============================================================================
1216
3851
 
1217
3852
  async function showSettings() {
1218
3853
  while (true) {
@@ -1221,97 +3856,145 @@ async function showSettings() {
1221
3856
 
1222
3857
  const currentLang = LANG_CODE === 'en' ? 'EN' : 'PT';
1223
3858
 
1224
- console.log(` ${c.cyan}[1]${c.reset} ${MSG.EXCLUSIONS}`);
1225
- console.log(` ${c.cyan}[2]${c.reset} ${MSG.CUSTOM_PATHS}`);
1226
- console.log(` ${c.cyan}[3]${c.reset} ${MSG.CHANGE_LANG} (current: ${currentLang})`);
1227
- console.log(` ${c.cyan}[4]${c.reset} ${MSG.RESET_DEFAULTS}`);
1228
- console.log();
1229
- console.log(` ${c.cyan}[0]${c.reset} ${MSG.BACK}`);
1230
- console.log();
1231
-
1232
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
3859
+ const settingsChoices = [
3860
+ { name: MSG.EXCLUSIONS, value: 'exclusions' },
3861
+ { name: MSG.CUSTOM_PATHS, value: 'paths' },
3862
+ { name: `${MSG.CHANGE_LANG} ${chalk.dim('(' + currentLang + ')')}`, value: 'lang' },
3863
+ { name: MSG.RESET_DEFAULTS, value: 'reset' },
3864
+ new inquirer.Separator(' '),
3865
+ { name: `${DRY_RUN ? chalk.green(figures.tick) : figures.radioOff} Dry-Run Mode`, value: 'dryrun' },
3866
+ { name: `${SECURE_DELETE ? chalk.green(figures.tick) : figures.radioOff} Secure Delete Mode`, value: 'secure' },
3867
+ new inquirer.Separator(' '),
3868
+ { name: 'View Backups', value: 'backups' },
3869
+ new inquirer.Separator(' '),
3870
+ { name: chalk.yellow(MSG.BACK), value: 'back' }
3871
+ ];
3872
+
3873
+ const fallbackSettings = async () => {
3874
+ console.log(` ${chalk.cyan('[1]')} ${MSG.EXCLUSIONS}`);
3875
+ console.log(` ${chalk.cyan('[2]')} ${MSG.CUSTOM_PATHS}`);
3876
+ console.log(` ${chalk.cyan('[3]')} ${MSG.CHANGE_LANG} (current: ${currentLang})`);
3877
+ console.log(` ${chalk.cyan('[4]')} ${MSG.RESET_DEFAULTS}`);
3878
+ console.log(` ${chalk.cyan('[5]')} ${DRY_RUN ? chalk.green(figures.tick) : figures.radioOff} Dry-Run Mode`);
3879
+ console.log(` ${chalk.cyan('[6]')} ${SECURE_DELETE ? chalk.green(figures.tick) : figures.radioOff} Secure Delete Mode`);
3880
+ console.log(` ${chalk.cyan('[7]')} View Backups`);
3881
+ console.log();
3882
+ console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
3883
+ console.log();
3884
+ const c = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3885
+ const map = { '1': 'exclusions', '2': 'paths', '3': 'lang', '4': 'reset', '5': 'dryrun', '6': 'secure', '7': 'backups', '0': 'back' };
3886
+ return map[c] || null;
3887
+ };
3888
+
3889
+ const choice = await selectOption(MSG.OPT_SETTINGS, settingsChoices, fallbackSettings);
1233
3890
 
1234
3891
  switch (choice) {
1235
- case '1':
3892
+ case 'exclusions':
1236
3893
  // Manage exclusions
1237
3894
  header();
1238
3895
  sectionHeader(MSG.EXCLUSIONS);
1239
3896
 
1240
- console.log(` ${c.white}${MSG.CURRENT_EXCLUSIONS}:${c.reset}`);
3897
+ console.log(` ${chalk.white(MSG.CURRENT_EXCLUSIONS + ':')}`);
1241
3898
  if (fs.existsSync(EXCLUSIONS_FILE)) {
1242
3899
  const exclusions = fs.readFileSync(EXCLUSIONS_FILE, 'utf8').split('\n').filter(l => l.trim());
1243
3900
  exclusions.forEach((ex, i) => console.log(` ${i + 1}. ${ex}`));
1244
3901
  } else {
1245
- console.log(` ${c.dim}None${c.reset}`);
3902
+ console.log(chalk.dim(' None'));
1246
3903
  }
1247
3904
  console.log();
1248
- console.log(` ${c.dim}${MSG.ENTER_PATH}:${c.reset}`);
3905
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
1249
3906
 
1250
- const newExclusion = await prompt(` ${c.white}${MSG.PATH}:${c.reset} `);
3907
+ const newExclusion = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
1251
3908
 
1252
3909
  if (newExclusion === 'c') {
1253
3910
  if (fs.existsSync(EXCLUSIONS_FILE)) fs.unlinkSync(EXCLUSIONS_FILE);
1254
- console.log(` ${c.green}${CHECK}${c.reset} Exclusions ${MSG.CLEARED}`);
3911
+ console.log(` ${chalk.green(figures.tick)} Exclusions ${MSG.CLEARED}`);
1255
3912
  } else if (newExclusion !== 'n' && newExclusion) {
1256
3913
  fs.appendFileSync(EXCLUSIONS_FILE, newExclusion + '\n');
1257
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.ADDED}: ${newExclusion}`);
3914
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newExclusion}`);
1258
3915
  }
1259
3916
  await pressEnter();
1260
3917
  break;
1261
3918
 
1262
- case '2':
3919
+ case 'paths':
1263
3920
  // Custom paths
1264
3921
  header();
1265
3922
  sectionHeader(MSG.CUSTOM_PATHS);
1266
3923
 
1267
- console.log(` ${c.white}${MSG.CURRENT_PATHS}:${c.reset}`);
3924
+ console.log(` ${chalk.white(MSG.CURRENT_PATHS + ':')}`);
1268
3925
  if (fs.existsSync(CUSTOM_PATHS_FILE)) {
1269
3926
  const paths = fs.readFileSync(CUSTOM_PATHS_FILE, 'utf8').split('\n').filter(l => l.trim() && !l.startsWith('#'));
1270
3927
  paths.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
1271
3928
  } else {
1272
- console.log(` ${c.dim}None${c.reset}`);
3929
+ console.log(chalk.dim(' None'));
1273
3930
  }
1274
3931
  console.log();
1275
- console.log(` ${c.dim}${MSG.ENTER_PATH}:${c.reset}`);
3932
+ console.log(chalk.dim(` ${MSG.ENTER_PATH}:`));
1276
3933
 
1277
- const newPath = await prompt(` ${c.white}${MSG.PATH}:${c.reset} `);
3934
+ const newPath = await prompt(` ${chalk.white(MSG.PATH + ':')} `);
1278
3935
 
1279
3936
  if (newPath === 'c') {
1280
3937
  if (fs.existsSync(CUSTOM_PATHS_FILE)) fs.unlinkSync(CUSTOM_PATHS_FILE);
1281
- console.log(` ${c.green}${CHECK}${c.reset} Custom paths ${MSG.CLEARED}`);
3938
+ console.log(` ${chalk.green(figures.tick)} Custom paths ${MSG.CLEARED}`);
1282
3939
  } else if (newPath !== 'n' && newPath) {
1283
3940
  fs.appendFileSync(CUSTOM_PATHS_FILE, newPath + '\n');
1284
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.ADDED}: ${newPath}`);
3941
+ console.log(` ${chalk.green(figures.tick)} ${MSG.ADDED}: ${newPath}`);
1285
3942
  }
1286
3943
  await pressEnter();
1287
3944
  break;
1288
3945
 
1289
- case '3':
3946
+ case 'lang':
1290
3947
  // Change language
1291
3948
  const newLang = LANG_CODE === 'pt' ? 'en' : 'pt';
1292
3949
  fs.writeFileSync(LANGUAGE_FILE, newLang);
1293
- console.log(` ${c.green}${CHECK}${c.reset} ${newLang === 'en' ? MSG.LANG_CHANGED_EN : MSG.LANG_CHANGED_PT}`);
3950
+ console.log(` ${chalk.green(figures.tick)} ${newLang === 'en' ? MSG.LANG_CHANGED_EN : MSG.LANG_CHANGED_PT}`);
1294
3951
  await pressEnter();
1295
3952
  // Restart to apply language change
1296
- const { spawn } = require('child_process');
1297
3953
  spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1298
3954
  process.exit(0);
1299
3955
  break;
1300
3956
 
1301
- case '4':
3957
+ case 'reset':
1302
3958
  // Reset defaults
1303
3959
  [EXCLUSIONS_FILE, CUSTOM_PATHS_FILE, LANGUAGE_FILE].forEach(f => {
1304
3960
  if (fs.existsSync(f)) fs.unlinkSync(f);
1305
3961
  });
1306
- console.log(` ${c.green}${CHECK}${c.reset} ${MSG.SETTINGS_RESET}`);
3962
+ console.log(` ${chalk.green(figures.tick)} ${MSG.SETTINGS_RESET}`);
1307
3963
  await pressEnter();
1308
3964
  // Restart to apply changes
1309
- const { spawn: spawn2 } = require('child_process');
1310
- spawn2(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
3965
+ spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1311
3966
  process.exit(0);
1312
3967
  break;
1313
3968
 
1314
- case '0':
3969
+ case 'dryrun':
3970
+ DRY_RUN = !DRY_RUN;
3971
+ console.log(` ${chalk.green(figures.tick)} Dry-Run Mode: ${DRY_RUN ? 'ON' : 'OFF'}`);
3972
+ await pressEnter();
3973
+ break;
3974
+
3975
+ case 'secure':
3976
+ SECURE_DELETE = !SECURE_DELETE;
3977
+ console.log(` ${chalk.green(figures.tick)} Secure Delete Mode: ${SECURE_DELETE ? 'ON' : 'OFF'}`);
3978
+ await pressEnter();
3979
+ break;
3980
+
3981
+ case 'backups':
3982
+ // View backups
3983
+ header();
3984
+ sectionHeader('Backups');
3985
+
3986
+ const backups = listBackups();
3987
+ if (backups.length === 0) {
3988
+ console.log(chalk.dim(' No backups found'));
3989
+ } else {
3990
+ backups.forEach((b, i) => {
3991
+ console.log(` ${chalk.cyan('[' + (i + 1) + ']')} ${b}`);
3992
+ });
3993
+ }
3994
+ await pressEnter();
3995
+ break;
3996
+
3997
+ case 'back':
1315
3998
  return;
1316
3999
 
1317
4000
  default:
@@ -1320,56 +4003,132 @@ async function showSettings() {
1320
4003
  }
1321
4004
  }
1322
4005
 
1323
- // ══════════════════════════════════════════════════════════════════════════════
4006
+ // ============================================================================
1324
4007
  // MAIN MENU
1325
- // ══════════════════════════════════════════════════════════════════════════════
4008
+ // ============================================================================
1326
4009
 
1327
4010
  async function mainMenu() {
1328
4011
  while (true) {
1329
4012
  header();
1330
4013
 
1331
- console.log(` ${c.cyan}[1]${c.reset} 🧹 ${MSG.OPT_QUICK}`);
1332
- console.log(` ${c.cyan}[2]${c.reset} 📦 ${MSG.OPT_LARGE}`);
1333
- console.log(` ${c.cyan}[3]${c.reset} 🔄 ${MSG.OPT_DUPLICATES}`);
1334
- console.log(` ${c.cyan}[4]${c.reset} 🗑️ ${MSG.OPT_APPS}`);
1335
- console.log(` ${c.cyan}[5]${c.reset} 🚀 ${MSG.OPT_STARTUP}`);
1336
- console.log(` ${c.cyan}[6]${c.reset} 🌐 ${MSG.OPT_BROWSERS}`);
1337
- console.log(` ${c.cyan}[7]${c.reset} 💀 ${MSG.OPT_PROCESSES}`);
1338
- console.log(` ${c.cyan}[8]${c.reset} 📊 ${MSG.OPT_STATS}`);
1339
- console.log(` ${c.cyan}[9]${c.reset} ⚙️ ${MSG.OPT_SETTINGS}`);
1340
- console.log();
1341
- console.log(` ${c.cyan}[0]${c.reset} ${MSG.OPT_EXIT}`);
1342
- console.log();
1343
-
1344
- const choice = await prompt(` ${c.white}${MSG.CHOOSE}:${c.reset} `);
4014
+ // Show update notification
4015
+ await showUpdateNotification();
4016
+
4017
+ // Build menu choices with separators for sections
4018
+ const menuChoices = [
4019
+ new inquirer.Separator(chalk.yellow(`\u2500\u2500 \uD83E\uDDF9 ${MSG.SECTION_CLEANING} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`)),
4020
+ { name: MSG.OPT_QUICK, value: 'quick' },
4021
+ { name: `${MSG.OPT_DEEP} ${chalk.red('\uD83D\uDD25')}`, value: 'deep' },
4022
+ { name: MSG.OPT_LARGE, value: 'large' },
4023
+ { name: MSG.OPT_DUPLICATES, value: 'duplicates' },
4024
+ { name: MSG.OPT_APPS, value: 'apps' },
4025
+ { name: MSG.OPT_BROWSERS, value: 'browsers' },
4026
+ { name: MSG.OPT_PRIVACY, value: 'privacy' },
4027
+ { name: MSG.OPT_MEMORY, value: 'memory' },
4028
+ new inquirer.Separator(chalk.yellow(`\u2500\u2500 \uD83D\uDCCA ${MSG.SECTION_ANALYSIS} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`)),
4029
+ { name: MSG.OPT_STATS, value: 'stats' },
4030
+ { name: MSG.OPT_THERMAL, value: 'thermal' },
4031
+ { name: MSG.OPT_DISK, value: 'disk' },
4032
+ { name: MSG.OPT_BENCHMARK, value: 'benchmark' },
4033
+ { name: MSG.OPT_PROCESSES, value: 'processes' },
4034
+ new inquirer.Separator(chalk.yellow(`\u2500\u2500 \u2699\uFE0F ${MSG.SECTION_SETTINGS} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`)),
4035
+ { name: MSG.OPT_STARTUP, value: 'startup' },
4036
+ { name: MSG.OPT_SCHEDULER, value: 'scheduler' },
4037
+ { name: MSG.OPT_SETTINGS, value: 'settings' },
4038
+ { name: MSG.OPT_EXPORT, value: 'export' },
4039
+ new inquirer.Separator(' '),
4040
+ { name: chalk.red(MSG.OPT_EXIT), value: 'exit' }
4041
+ ];
4042
+
4043
+ // Fallback for when inquirer is not available
4044
+ const fallbackMenu = async () => {
4045
+ // Box drawing characters
4046
+ const boxChars = {
4047
+ topLeft: '\u256D', topRight: '\u256E', bottomLeft: '\u2570', bottomRight: '\u256F',
4048
+ horizontal: '\u2500', vertical: '\u2502', horizontalDouble: '\u2550', cross: '\u2560', crossRight: '\u2563'
4049
+ };
4050
+ console.log(chalk.cyan(`${boxChars.topLeft}${''.padEnd(38, boxChars.horizontal)}${boxChars.topRight}`));
4051
+ console.log(chalk.cyan(boxChars.vertical) + chalk.white.bold(' \uD83E\uDDF9 ARIS MAC CLEANER v' + VERSION) + ' '.repeat(11) + chalk.cyan(boxChars.vertical));
4052
+ console.log(chalk.cyan(boxChars.cross) + chalk.cyan(''.padEnd(38, boxChars.horizontalDouble)) + chalk.cyan(boxChars.crossRight));
4053
+ console.log(chalk.cyan(boxChars.vertical) + ' '.repeat(38) + chalk.cyan(boxChars.vertical));
4054
+ console.log(chalk.cyan(boxChars.vertical) + chalk.yellow(' \u2500\u2500 \uD83E\uDDF9 ' + MSG.SECTION_CLEANING + ' ') + '\u2500'.repeat(38 - 10 - MSG.SECTION_CLEANING.length) + chalk.cyan(boxChars.vertical));
4055
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[1]')} ${MSG.OPT_QUICK}` + ' '.repeat(38 - 7 - MSG.OPT_QUICK.length) + chalk.cyan(boxChars.vertical));
4056
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[2]')} ${MSG.OPT_DEEP} ${chalk.red('\uD83D\uDD25')}` + ' '.repeat(38 - 11 - MSG.OPT_DEEP.length) + chalk.cyan(boxChars.vertical));
4057
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[3]')} ${MSG.OPT_LARGE}` + ' '.repeat(38 - 7 - MSG.OPT_LARGE.length) + chalk.cyan(boxChars.vertical));
4058
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[4]')} ${MSG.OPT_DUPLICATES}` + ' '.repeat(38 - 7 - MSG.OPT_DUPLICATES.length) + chalk.cyan(boxChars.vertical));
4059
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[5]')} ${MSG.OPT_APPS}` + ' '.repeat(38 - 7 - MSG.OPT_APPS.length) + chalk.cyan(boxChars.vertical));
4060
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[6]')} ${MSG.OPT_BROWSERS}` + ' '.repeat(38 - 7 - MSG.OPT_BROWSERS.length) + chalk.cyan(boxChars.vertical));
4061
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[7]')} ${MSG.OPT_PRIVACY}` + ' '.repeat(38 - 7 - MSG.OPT_PRIVACY.length) + chalk.cyan(boxChars.vertical));
4062
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[8]')} ${MSG.OPT_MEMORY}` + ' '.repeat(38 - 7 - MSG.OPT_MEMORY.length) + chalk.cyan(boxChars.vertical));
4063
+ console.log(chalk.cyan(boxChars.vertical) + ' '.repeat(38) + chalk.cyan(boxChars.vertical));
4064
+ console.log(chalk.cyan(boxChars.vertical) + chalk.yellow(' \u2500\u2500 \uD83D\uDCCA ' + MSG.SECTION_ANALYSIS + ' ') + '\u2500'.repeat(38 - 10 - MSG.SECTION_ANALYSIS.length) + chalk.cyan(boxChars.vertical));
4065
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[9]')} ${MSG.OPT_STATS}` + ' '.repeat(38 - 7 - MSG.OPT_STATS.length) + chalk.cyan(boxChars.vertical));
4066
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[10]')} ${MSG.OPT_THERMAL}` + ' '.repeat(38 - 8 - MSG.OPT_THERMAL.length) + chalk.cyan(boxChars.vertical));
4067
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[11]')} ${MSG.OPT_DISK}` + ' '.repeat(38 - 8 - MSG.OPT_DISK.length) + chalk.cyan(boxChars.vertical));
4068
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[12]')} ${MSG.OPT_BENCHMARK}` + ' '.repeat(38 - 8 - MSG.OPT_BENCHMARK.length) + chalk.cyan(boxChars.vertical));
4069
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[13]')} ${MSG.OPT_PROCESSES}` + ' '.repeat(38 - 8 - MSG.OPT_PROCESSES.length) + chalk.cyan(boxChars.vertical));
4070
+ console.log(chalk.cyan(boxChars.vertical) + ' '.repeat(38) + chalk.cyan(boxChars.vertical));
4071
+ console.log(chalk.cyan(boxChars.vertical) + chalk.yellow(' \u2500\u2500 \u2699\uFE0F ' + MSG.SECTION_SETTINGS + ' ') + '\u2500'.repeat(38 - 11 - MSG.SECTION_SETTINGS.length) + chalk.cyan(boxChars.vertical));
4072
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[14]')} ${MSG.OPT_STARTUP}` + ' '.repeat(38 - 8 - MSG.OPT_STARTUP.length) + chalk.cyan(boxChars.vertical));
4073
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[15]')} ${MSG.OPT_SCHEDULER}` + ' '.repeat(38 - 8 - MSG.OPT_SCHEDULER.length) + chalk.cyan(boxChars.vertical));
4074
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[16]')} ${MSG.OPT_SETTINGS}` + ' '.repeat(38 - 8 - MSG.OPT_SETTINGS.length) + chalk.cyan(boxChars.vertical));
4075
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[17]')} ${MSG.OPT_EXPORT}` + ' '.repeat(38 - 8 - MSG.OPT_EXPORT.length) + chalk.cyan(boxChars.vertical));
4076
+ console.log(chalk.cyan(boxChars.vertical) + ' '.repeat(38) + chalk.cyan(boxChars.vertical));
4077
+ console.log(chalk.cyan(boxChars.vertical) + ` ${chalk.cyan('[0]')} ${MSG.OPT_EXIT}` + ' '.repeat(38 - 7 - MSG.OPT_EXIT.length) + chalk.cyan(boxChars.vertical));
4078
+ console.log(chalk.cyan(`${boxChars.bottomLeft}${''.padEnd(38, boxChars.horizontal)}${boxChars.bottomRight}`));
4079
+ console.log();
4080
+ const c = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
4081
+ const map = { '1': 'quick', '2': 'deep', '3': 'large', '4': 'duplicates', '5': 'apps', '6': 'browsers', '7': 'privacy', '8': 'memory', '9': 'stats', '10': 'thermal', '11': 'disk', '12': 'benchmark', '13': 'processes', '14': 'startup', '15': 'scheduler', '16': 'settings', '17': 'export', '0': 'exit' };
4082
+ return map[c] || null;
4083
+ };
4084
+
4085
+ const choice = await selectOption(
4086
+ chalk.cyan('\uD83E\uDDF9 ARIS MAC CLEANER v' + VERSION),
4087
+ menuChoices,
4088
+ fallbackMenu
4089
+ );
1345
4090
 
1346
4091
  switch (choice) {
1347
- case '1': await quickClean(); break;
1348
- case '2': await findLargeFiles(); break;
1349
- case '3': await findDuplicates(); break;
1350
- case '4': await uninstallApps(); break;
1351
- case '5': await manageStartup(); break;
1352
- case '6': await cleanBrowsers(); break;
1353
- case '7': await killProcesses(); break;
1354
- case '8': await showStatistics(); break;
1355
- case '9': await showSettings(); break;
1356
- case '0':
4092
+ case 'quick': await quickClean(); break;
4093
+ case 'deep': await deepClean(); break;
4094
+ case 'large': await findLargeFiles(); break;
4095
+ case 'duplicates': await findDuplicates(); break;
4096
+ case 'apps': await uninstallApps(); break;
4097
+ case 'browsers': await cleanBrowsers(); break;
4098
+ case 'privacy': await privacySweep(); break;
4099
+ case 'memory': await memoryOptimizer(); break;
4100
+ case 'stats': await showStatistics(); break;
4101
+ case 'thermal': await thermalMonitor(); break;
4102
+ case 'disk': await diskAnalyzer(); break;
4103
+ case 'benchmark': await benchmarkMode(); break;
4104
+ case 'processes': await killProcesses(); break;
4105
+ case 'startup': await manageStartup(); break;
4106
+ case 'scheduler': await scheduler(); break;
4107
+ case 'settings': await showSettings(); break;
4108
+ case 'export': await exportHtmlReport(); break;
4109
+ case 'exit':
1357
4110
  clearScreen();
1358
4111
  console.log();
1359
- console.log(`${c.dim}por Salvador Reis ${BULLET} github.com/salvadorreis${c.reset}`);
4112
+ console.log(gradient.pastel.multiline(`
4113
+ Thank you for using Aris Mac Cleaner!
4114
+
4115
+ por Salvador Reis ${figures.bullet} github.com/salvadorreis
4116
+ `));
1360
4117
  console.log();
1361
4118
  process.exit(0);
1362
4119
  break;
1363
4120
  default:
1364
- console.log(` ${c.red}${MSG.INVALID}${c.reset}`);
1365
- await new Promise(r => setTimeout(r, 1000));
4121
+ if (choice === null) {
4122
+ console.log(` ${chalk.red(MSG.INVALID)}`);
4123
+ await new Promise(r => setTimeout(r, 1000));
4124
+ }
1366
4125
  }
1367
4126
  }
1368
4127
  }
1369
4128
 
1370
- // ══════════════════════════════════════════════════════════════════════════════
4129
+ // ============================================================================
1371
4130
  // MAIN
1372
- // ══════════════════════════════════════════════════════════════════════════════
4131
+ // ============================================================================
1373
4132
 
1374
4133
  // Check if macOS
1375
4134
  if (os.platform() !== 'darwin') {
@@ -1380,32 +4139,64 @@ if (os.platform() !== 'darwin') {
1380
4139
  // Command line arguments
1381
4140
  const args = process.argv.slice(2);
1382
4141
 
1383
- if (args.includes('--preview') || args.includes('--dry-run')) {
1384
- console.log('Preview mode - nothing will be deleted');
1385
- process.exit(0);
4142
+ // Parse flags
4143
+ if (args.includes('--dry-run')) {
4144
+ DRY_RUN = true;
1386
4145
  }
1387
4146
 
1388
- if (args.includes('--quick') || args.includes('-q')) {
1389
- (async () => {
1390
- getSystemStats();
1391
- await quickClean();
1392
- })();
1393
- } else if (args.includes('--version') || args.includes('-v')) {
4147
+ if (args.includes('--secure')) {
4148
+ SECURE_DELETE = true;
4149
+ }
4150
+
4151
+ if (args.includes('--version') || args.includes('-v')) {
1394
4152
  console.log(`Aris Mac Cleaner v${VERSION}`);
1395
4153
  process.exit(0);
1396
- } else if (args.includes('--help') || args.includes('-h')) {
4154
+ }
4155
+
4156
+ if (args.includes('--help') || args.includes('-h')) {
1397
4157
  console.log(`
1398
- Aris Mac Cleaner v${VERSION}
4158
+ ${gradient.pastel.multiline(ASCII_LOGO)}
1399
4159
 
1400
4160
  Usage:
1401
- aris-mac-cleaner Start interactive menu
1402
- aris-mac-cleaner -q Quick clean without menu
1403
- aris-mac-cleaner -v Show version
1404
- aris-mac-cleaner -h Show this help
4161
+ aris-mac-cleaner Start interactive menu
4162
+ aris-mac-cleaner -q Quick clean without menu
4163
+ aris-mac-cleaner --dry-run Preview mode (nothing deleted)
4164
+ aris-mac-cleaner --secure Secure delete (overwrite before delete)
4165
+ aris-mac-cleaner -v Show version
4166
+ aris-mac-cleaner -h Show this help
1405
4167
 
1406
4168
  Config directory: ~/.aris-mac-cleaner/
4169
+
4170
+ Premium Features:
4171
+ - Animated logo with gradient colors
4172
+ - Progress bars and spinners
4173
+ - Auto-update checker
4174
+ - Backup before clean
4175
+ - Cleanup history with ASCII charts
4176
+ - Disk analyzer with visual tree
4177
+ - Smart recommendations
4178
+ - HTML report export
4179
+ - Benchmark mode
4180
+ - Privacy sweep
4181
+ - Memory optimizer
4182
+ - Thermal monitor with power metrics
4183
+ - Scheduled cleanups (launchd)
4184
+ - Sound notifications
4185
+ - Dry-run and secure delete modes
1407
4186
  `);
1408
4187
  process.exit(0);
4188
+ }
4189
+
4190
+ if (args.includes('--quick') || args.includes('-q')) {
4191
+ (async () => {
4192
+ getSystemStats();
4193
+ await quickClean();
4194
+ })();
1409
4195
  } else {
1410
- mainMenu();
4196
+ // Show animated logo and start menu
4197
+ (async () => {
4198
+ await animateLogo();
4199
+ getSystemStats();
4200
+ mainMenu();
4201
+ })();
1411
4202
  }