aris-mac-cleaner 3.5.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 +66 -32
  2. package/bin/cli.js +1840 -278
  3. package/package.json +4 -3
package/bin/cli.js CHANGED
@@ -114,11 +114,85 @@ try {
114
114
  };
115
115
  }
116
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
+ }
171
+
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
+
117
191
  // ============================================================================
118
192
  // CONSTANTS
119
193
  // ============================================================================
120
194
 
121
- const VERSION = '3.5.0';
195
+ const VERSION = '4.0.0';
122
196
  const PACKAGE_NAME = 'aris-mac-cleaner';
123
197
  const HOME = os.homedir();
124
198
  const CONFIG_DIR = path.join(HOME, '.aris-mac-cleaner');
@@ -158,22 +232,49 @@ if (fs.existsSync(LANGUAGE_FILE)) {
158
232
  const MSG = LANG_CODE === 'pt' ? {
159
233
  WELCOME: 'Bem-vindo ao Aris Mac Cleaner',
160
234
  MENU_TITLE: 'MENU PRINCIPAL',
161
- OPT_QUICK: 'Limpeza Rapida',
162
- OPT_LARGE: 'Ficheiros Grandes',
163
- OPT_DUPLICATES: 'Duplicados',
164
- OPT_APPS: 'Desinstalar Apps',
165
- OPT_STARTUP: 'Apps de Arranque',
166
- OPT_BROWSERS: 'Limpar Browsers',
167
- OPT_PROCESSES: 'Processos Pesados',
168
- OPT_STATS: 'Estatisticas',
169
- OPT_SETTINGS: 'Configuracoes',
170
- OPT_DISK: 'Analisador Disco',
171
- OPT_PRIVACY: 'Limpeza Privacidade',
172
- OPT_MEMORY: 'Otimizar Memoria',
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',
173
253
  OPT_BENCHMARK: 'Benchmark',
174
- OPT_SCHEDULER: 'Agendar Limpeza',
175
- OPT_EXPORT: 'Exportar Relatorio',
176
- OPT_EXIT: 'Sair',
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',
177
278
  CHOOSE: 'Escolha',
178
279
  INVALID: 'Opcao invalida',
179
280
  PRESS_ENTER: 'Pressiona ENTER para continuar',
@@ -272,26 +373,89 @@ const MSG = LANG_CODE === 'pt' ? {
272
373
  DISK_ANALYSIS: 'ANALISE DE DISCO',
273
374
  SCHEDULED_DAILY: 'Agendado diariamente',
274
375
  SCHEDULED_WEEKLY: 'Agendado semanalmente',
275
- CLEANUP_COMPLETE: 'Limpeza completa!'
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'
276
413
  } : {
277
414
  WELCOME: 'Welcome to Aris Mac Cleaner',
278
415
  MENU_TITLE: 'MAIN MENU',
416
+ // Section headers
417
+ SECTION_CLEANING: 'CLEANING',
418
+ SECTION_ANALYSIS: 'ANALYSIS',
419
+ SECTION_SETTINGS: 'SETTINGS',
420
+ // Menu options
279
421
  OPT_QUICK: 'Quick Clean',
422
+ OPT_DEEP: 'Deep Clean',
280
423
  OPT_LARGE: 'Large Files',
281
424
  OPT_DUPLICATES: 'Duplicates',
282
- OPT_APPS: 'Uninstall Apps',
283
- OPT_STARTUP: 'Startup Apps',
284
- OPT_BROWSERS: 'Clean Browsers',
285
- OPT_PROCESSES: 'Heavy Processes',
425
+ OPT_APPS: 'App Uninstaller',
426
+ OPT_STARTUP: 'Startup Manager',
427
+ OPT_BROWSERS: 'Browser Cleaner',
428
+ OPT_PROCESSES: 'Process Manager',
286
429
  OPT_STATS: 'Statistics',
287
430
  OPT_SETTINGS: 'Settings',
288
431
  OPT_DISK: 'Disk Analyzer',
289
432
  OPT_PRIVACY: 'Privacy Sweep',
290
433
  OPT_MEMORY: 'Memory Optimizer',
291
434
  OPT_BENCHMARK: 'Benchmark',
292
- OPT_SCHEDULER: 'Schedule Cleanup',
435
+ OPT_THERMAL: 'Thermal Monitor',
436
+ OPT_SCHEDULER: 'Scheduler',
293
437
  OPT_EXPORT: 'Export Report',
294
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',
295
459
  CHOOSE: 'Choose',
296
460
  INVALID: 'Invalid option',
297
461
  PRESS_ENTER: 'Press ENTER to continue',
@@ -390,7 +554,43 @@ const MSG = LANG_CODE === 'pt' ? {
390
554
  DISK_ANALYSIS: 'DISK ANALYSIS',
391
555
  SCHEDULED_DAILY: 'Scheduled daily',
392
556
  SCHEDULED_WEEKLY: 'Scheduled weekly',
393
- CLEANUP_COMPLETE: 'Cleanup complete!'
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'
394
594
  };
395
595
 
396
596
  // ============================================================================
@@ -404,7 +604,7 @@ const ASCII_LOGO = `
404
604
  / ___ \\| _ < | | ___) |
405
605
  /_/ \\_\\_| \\_\\___|____/
406
606
 
407
- MAC CLEANER v${VERSION} PREMIUM
607
+ MAC CLEANER v${VERSION}
408
608
  `;
409
609
 
410
610
  const ASCII_LOGO_FRAMES = [
@@ -510,6 +710,83 @@ async function pressEnter() {
510
710
  await prompt(` ${chalk.dim(MSG.PRESS_ENTER + '...')} `);
511
711
  }
512
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
+
513
790
  // ============================================================================
514
791
  // PREMIUM HEADER WITH GRADIENT
515
792
  // ============================================================================
@@ -523,7 +800,7 @@ function header() {
523
800
 
524
801
  const titleBox = `
525
802
  ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
526
- ${chalk.cyan('|')}${chalk.white(' ARIS MAC CLEANER v' + VERSION + ' PREMIUM ')}${chalk.cyan('|')}
803
+ ${chalk.cyan('|')}${chalk.white(' ARIS MAC CLEANER v' + VERSION + ' ')}${chalk.cyan('|')}
527
804
  ${chalk.cyan('|')}${chalk.gray(' ' + date + ' ' + figures.bullet + ' ' + time + ' ')}${chalk.cyan('|')}
528
805
  ${chalk.cyan('+')}${chalk.cyan('-'.repeat(68))}${chalk.cyan('+')}
529
806
  `;
@@ -1071,28 +1348,113 @@ async function privacySweep() {
1071
1348
  }
1072
1349
 
1073
1350
  console.log();
1074
- console.log(chalk.dim(` ${MSG.SELECT_DELETE} [a=${MSG.ALL}/n=${MSG.CANCEL}]:`));
1075
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1076
1351
 
1077
- if (choice.toLowerCase() === 'n' || choice === '') {
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
+ }));
1358
+
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 + ':')} `);
1363
+
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) {
1078
1397
  await pressEnter();
1079
1398
  return;
1080
1399
  }
1081
1400
 
1401
+ // Ask confirmation mode
1402
+ const confirmMode = await askConfirmationMode();
1403
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
1404
+ let autoMode = confirmMode === 'auto';
1405
+
1082
1406
  let cleaned = 0;
1407
+ let deletedCount = 0;
1408
+ let skippedCount = 0;
1083
1409
 
1084
- if (choice.toLowerCase() === 'a') {
1410
+ if (autoMode && !isDryRun) {
1085
1411
  const spinner = ora(MSG.CLEANING).start();
1086
1412
 
1087
1413
  for (const item of items) {
1414
+ if (!item.selected) continue;
1088
1415
  if (deleteItem(item.fullPath)) {
1089
1416
  cleaned += item.size;
1417
+ deletedCount++;
1090
1418
  }
1091
1419
  }
1092
1420
 
1093
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
+ }
1094
1455
  }
1095
1456
 
1457
+ showCleanupSummary(deletedCount, skippedCount, cleaned, confirmMode);
1096
1458
  await pressEnter();
1097
1459
  }
1098
1460
 
@@ -1114,9 +1476,14 @@ async function memoryOptimizer() {
1114
1476
  console.log(chalk.dim(' You may need to enter your password.'));
1115
1477
  console.log();
1116
1478
 
1117
- const confirm = await prompt(` ${chalk.white('Continue? [y/n]:')} `);
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);
1118
1485
 
1119
- if (confirm.toLowerCase() !== 'y') {
1486
+ if (!confirmed) {
1120
1487
  await pressEnter();
1121
1488
  return;
1122
1489
  }
@@ -1238,6 +1605,227 @@ async function benchmarkMode() {
1238
1605
  await pressEnter();
1239
1606
  }
1240
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
+
1241
1829
  // ============================================================================
1242
1830
  // SCHEDULER (LAUNCHD)
1243
1831
  // ============================================================================
@@ -1252,26 +1840,53 @@ async function scheduler() {
1252
1840
  console.log(chalk.white(' Cleanup Scheduler'));
1253
1841
  console.log();
1254
1842
 
1843
+ let schedulerChoices;
1255
1844
  if (isScheduled) {
1256
1845
  console.log(chalk.green(` ${figures.tick} Scheduled cleanup is ACTIVE`));
1257
1846
  console.log();
1258
- console.log(` ${chalk.cyan('[1]')} Remove schedule`);
1259
- console.log(` ${chalk.cyan('[2]')} View current schedule`);
1847
+ schedulerChoices = [
1848
+ { name: 'Remove schedule', value: 'remove' },
1849
+ { name: 'View current schedule', value: 'view' },
1850
+ { name: chalk.yellow(MSG.BACK), value: 'back' }
1851
+ ];
1260
1852
  } else {
1261
1853
  console.log(chalk.yellow(` ${figures.info} No scheduled cleanup configured`));
1262
1854
  console.log();
1263
- console.log(` ${chalk.cyan('[1]')} Schedule daily cleanup (midnight)`);
1264
- console.log(` ${chalk.cyan('[2]')} Schedule weekly cleanup (Sunday midnight)`);
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
+ ];
1265
1860
  }
1266
1861
 
1267
- console.log();
1268
- console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
1269
- console.log();
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
+ };
1270
1881
 
1271
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1882
+ const choice = await selectOption(MSG.OPT_SCHEDULER, schedulerChoices, fallbackScheduler);
1883
+
1884
+ if (choice === 'back' || !choice) {
1885
+ return;
1886
+ }
1272
1887
 
1273
1888
  if (isScheduled) {
1274
- if (choice === '1') {
1889
+ if (choice === 'remove') {
1275
1890
  // Remove schedule
1276
1891
  try {
1277
1892
  exec(`launchctl unload "${plistPath}" 2>/dev/null`);
@@ -1280,15 +1895,15 @@ async function scheduler() {
1280
1895
  } catch (e) {
1281
1896
  console.log(chalk.red(` ${figures.cross} Failed to remove schedule`));
1282
1897
  }
1283
- } else if (choice === '2') {
1898
+ } else if (choice === 'view') {
1284
1899
  // View schedule
1285
1900
  const content = fs.readFileSync(plistPath, 'utf8');
1286
1901
  console.log();
1287
1902
  console.log(chalk.dim(content));
1288
1903
  }
1289
1904
  } else {
1290
- if (choice === '1' || choice === '2') {
1291
- const isDaily = choice === '1';
1905
+ if (choice === 'daily' || choice === 'weekly') {
1906
+ const isDaily = choice === 'daily';
1292
1907
 
1293
1908
  const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
1294
1909
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -1514,8 +2129,13 @@ async function exportHtmlReport() {
1514
2129
  console.log(chalk.dim(` ${reportPath}`));
1515
2130
  console.log();
1516
2131
 
1517
- const open = await prompt(` Open in browser? [y/n]: `);
1518
- if (open.toLowerCase() === 'y') {
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) {
1519
2139
  exec(`open "${reportPath}"`);
1520
2140
  }
1521
2141
 
@@ -1727,118 +2347,711 @@ async function quickClean() {
1727
2347
  console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalSize))}`);
1728
2348
  console.log();
1729
2349
 
1730
- // Selection
1731
- sectionHeader(MSG.SELECT_DELETE);
1732
- console.log(chalk.dim(` a = ${MSG.ALL} | n = ${MSG.CANCEL} | 1,3,5 | 1-5 | 1-5,8,10`));
1733
- console.log();
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();
1734
2363
 
1735
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2364
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
1736
2365
 
1737
- if (choice.toLowerCase() === 'n' || choice === '') {
1738
- console.log(` ${chalk.yellow(MSG.CANCEL)}`);
1739
- await pressEnter();
1740
- return;
1741
- }
2366
+ if (choice.toLowerCase() === 'n' || choice === '') {
2367
+ return [];
2368
+ }
1742
2369
 
1743
- // Process selection
1744
- if (choice.toLowerCase() !== 'a') {
1745
- items.forEach(item => item.selected = false);
2370
+ if (choice.toLowerCase() === 'a') {
2371
+ return items.map(item => item.path);
2372
+ }
1746
2373
 
2374
+ const selected = [];
1747
2375
  const parts = choice.split(',');
1748
2376
  parts.forEach(part => {
1749
2377
  part = part.trim();
1750
2378
  if (part.includes('-')) {
1751
2379
  const [start, end] = part.split('-').map(n => parseInt(n));
1752
2380
  for (let j = start; j <= end; j++) {
1753
- if (j > 0 && j <= items.length) items[j - 1].selected = true;
2381
+ if (j > 0 && j <= items.length) selected.push(items[j - 1].path);
1754
2382
  }
1755
2383
  } else {
1756
2384
  const idx = parseInt(part) - 1;
1757
- if (idx >= 0 && idx < items.length) items[idx].selected = true;
2385
+ if (idx >= 0 && idx < items.length) selected.push(items[idx].path);
1758
2386
  }
1759
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;
1760
2401
  }
1761
2402
 
2403
+ // Mark selected items
2404
+ items.forEach(item => {
2405
+ item.selected = selectedPaths.includes(item.path);
2406
+ });
2407
+
1762
2408
  // Create backup
1763
2409
  const selectedItems = items.filter(i => i.selected);
1764
- if (selectedItems.length > 0 && !DRY_RUN) {
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) {
1765
2420
  const backupFile = createBackup(selectedItems);
1766
2421
  console.log(chalk.dim(` ${figures.info} ${MSG.BACKUP_CREATED}: ${path.basename(backupFile)}`));
1767
2422
  console.log();
1768
2423
  }
1769
2424
 
1770
- // Clean with progress bar
2425
+ // Clean with confirmation mode support
1771
2426
  console.log();
1772
2427
  sectionHeader(MSG.CLEANING);
1773
2428
 
1774
- const progressBar = createProgressBar(selectedItems.length);
1775
- progressBar.start(selectedItems.length, 0);
1776
-
1777
2429
  let cleanedSize = 0;
2430
+ let deletedCount = 0;
2431
+ let skippedCount = 0;
2432
+ let autoMode = confirmMode === 'auto';
2433
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
1778
2434
  const claudeVersionsPathLocal = path.join(HOME, '.local/share/claude/versions');
1779
2435
 
1780
- for (let i = 0; i < items.length; i++) {
1781
- const item = items[i];
1782
- if (!item.selected) continue;
2436
+ if (autoMode && !isDryRun) {
2437
+ const progressBar = createProgressBar(selectedItems.length);
2438
+ progressBar.start(selectedItems.length, 0);
1783
2439
 
1784
- if (item.path === 'CLAUDE_VERSIONS') {
1785
- if (!DRY_RUN) {
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') {
1786
2445
  const versions = fs.readdirSync(claudeVersionsPathLocal).sort().reverse();
1787
2446
  versions.slice(1).forEach(v => {
1788
2447
  try {
1789
2448
  deleteItem(path.join(claudeVersionsPathLocal, v));
1790
2449
  } catch (e) {}
1791
2450
  });
1792
- }
1793
- cleanedSize += item.size;
1794
- } else if (item.path === 'OLD_DOWNLOADS') {
1795
- if (!DRY_RUN) {
2451
+ cleanedSize += item.size;
2452
+ deletedCount++;
2453
+ } else if (item.path === 'OLD_DOWNLOADS') {
1796
2454
  exec('find ~/Downloads -name "*.dmg" -mtime +7 -delete 2>/dev/null');
1797
2455
  exec('find ~/Downloads -name "*.zip" -mtime +30 -delete 2>/dev/null');
1798
- }
1799
- cleanedSize += item.size;
1800
- } else if (fs.existsSync(item.path)) {
1801
- if (deleteItem(item.path)) {
1802
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
+ }
1803
2463
  }
2464
+
2465
+ progressBar.increment();
1804
2466
  }
1805
2467
 
1806
- progressBar.increment();
1807
- }
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
+ }
1808
2491
 
1809
- progressBar.stop();
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
+ }
1810
2526
 
1811
2527
  // npm cache
1812
- if (!DRY_RUN) {
2528
+ if (!isDryRun) {
1813
2529
  try {
1814
2530
  execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
1815
2531
  } catch (e) {}
1816
2532
  }
1817
2533
 
1818
- console.log();
1819
-
1820
- // Show cleaned items
1821
- selectedItems.forEach(item => {
1822
- console.log(` ${chalk.green(figures.tick)} ${item.name}`);
1823
- });
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
+ }
1824
2541
 
1825
- console.log();
2542
+ showCleanupSummary(deletedCount, skippedCount, cleanedSize, confirmMode);
1826
2543
  getSystemStats();
1827
2544
  showFinalReport(cleanedSize);
1828
2545
  await pressEnter();
1829
2546
  }
1830
2547
 
1831
2548
  // ============================================================================
1832
- // FIND LARGE FILES
2549
+ // DEEP CLEAN (AGGRESSIVE)
1833
2550
  // ============================================================================
1834
2551
 
1835
- async function findLargeFiles() {
2552
+ async function deepClean() {
1836
2553
  header();
1837
- sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
2554
+ sectionHeader(`${MSG.DEEP_CLEAN} ${chalk.red('\uD83D\uDD25')}`);
1838
2555
 
1839
- const spinner = ora(MSG.SCANNING).start();
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}`));
2560
+ console.log();
1840
2561
 
1841
- const files = [];
2562
+ const fallbackDeepConfirm = async () => {
2563
+ const answer = await prompt(` ${chalk.white(MSG.DEEP_CLEAN_CONFIRM + ' [y/n]:')} `);
2564
+ return answer.toLowerCase() === 'y';
2565
+ };
2566
+
2567
+ const confirmDeep = await confirmAction(MSG.DEEP_CLEAN_CONFIRM, false, fallbackDeepConfirm);
2568
+ if (!confirmDeep) {
2569
+ console.log(` ${chalk.yellow(MSG.CANCEL)}`);
2570
+ await pressEnter();
2571
+ return;
2572
+ }
2573
+
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
+ }
2954
+
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++;
3001
+ } else {
3002
+ oldLogsSize = 0;
3003
+ skippedCount++;
3004
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${MSG.CAT_LOGS}`);
3005
+ }
3006
+ }
3007
+ }
3008
+ if (oldLogsSize > 0) {
3009
+ results.push({ category: MSG.CAT_LOGS, size: oldLogsSize });
3010
+ totalCleaned += oldLogsSize;
3011
+ }
3012
+
3013
+ // Clean npm cache
3014
+ if (!isDryRun) {
3015
+ try {
3016
+ execSync('npm cache clean --force 2>/dev/null', { stdio: 'ignore' });
3017
+ } catch (e) {}
3018
+ }
3019
+
3020
+ const endTime = Date.now();
3021
+ const duration = Math.round((endTime - startTime) / 1000);
3022
+
3023
+ // Final Report
3024
+ showCleanupSummary(deletedCount, skippedCount, totalCleaned, confirmMode);
3025
+
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();
3033
+ }
3034
+
3035
+ console.log(` ${chalk.white(MSG.TOTAL + ':')} ${chalk.green.bold(formatSize(totalCleaned))}`);
3036
+ console.log(` ${chalk.dim('Duration: ' + duration + 's')}`);
3037
+ console.log();
3038
+
3039
+ getSystemStats();
3040
+ showFinalReport(totalCleaned);
3041
+ await pressEnter();
3042
+ }
3043
+
3044
+ // ============================================================================
3045
+ // FIND LARGE FILES
3046
+ // ============================================================================
3047
+
3048
+ async function findLargeFiles() {
3049
+ header();
3050
+ sectionHeader(`${MSG.LARGE_FILES} (>100MB)`);
3051
+
3052
+ const spinner = ora(MSG.SCANNING).start();
3053
+
3054
+ const files = [];
1842
3055
 
1843
3056
  const output = exec(`find ~ -type f -size +100M 2>/dev/null | head -50 | xargs -I {} du -m {} 2>/dev/null | sort -rn | head -30`);
1844
3057
 
@@ -1870,60 +3083,118 @@ async function findLargeFiles() {
1870
3083
 
1871
3084
  const totalLarge = files.reduce((sum, f) => sum + f.size, 0);
1872
3085
 
1873
- files.forEach((file, i) => {
3086
+ // Build checkbox choices for inquirer
3087
+ const fileChoices = files.map((file, i) => {
1874
3088
  let displayFile = file.path.replace(HOME, '~');
1875
- if (displayFile.length > 50) {
1876
- displayFile = '...' + displayFile.slice(-47);
3089
+ if (displayFile.length > 45) {
3090
+ displayFile = '...' + displayFile.slice(-42);
1877
3091
  }
1878
- console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${chalk.dim(String(file.size).padStart(6) + ' MB')} ${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
+ };
1879
3097
  });
1880
3098
 
1881
- console.log();
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
+
1882
3136
  console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalLarge))} ${chalk.dim('(' + files.length + ' files)')}`);
1883
3137
  console.log();
1884
3138
 
1885
- console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
1886
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3139
+ const selectedPaths = await selectMultiple(
3140
+ `${MSG.SELECT_DELETE} ${chalk.dim('(space to select, enter to confirm)')}`,
3141
+ fileChoices,
3142
+ fallbackSelect
3143
+ );
1887
3144
 
1888
- if (choice.toLowerCase() === 'n' || choice === '') {
3145
+ if (selectedPaths.length === 0) {
1889
3146
  await pressEnter();
1890
3147
  return;
1891
3148
  }
1892
3149
 
1893
- const selected = new Array(files.length).fill(false);
1894
-
1895
- if (choice.toLowerCase() === 'a') {
1896
- selected.fill(true);
1897
- } else {
1898
- const parts = choice.split(',');
1899
- parts.forEach(part => {
1900
- part = part.trim();
1901
- if (part.includes('-')) {
1902
- const [start, end] = part.split('-').map(n => parseInt(n));
1903
- for (let j = start; j <= end; j++) {
1904
- if (j > 0 && j <= files.length) selected[j - 1] = true;
1905
- }
1906
- } else {
1907
- const idx = parseInt(part) - 1;
1908
- if (idx >= 0 && idx < files.length) selected[idx] = true;
1909
- }
1910
- });
3150
+ const selectedFiles = files.filter(f => selectedPaths.includes(f.path));
3151
+ if (selectedFiles.length === 0) {
3152
+ await pressEnter();
3153
+ return;
1911
3154
  }
1912
3155
 
3156
+ // Ask confirmation mode
3157
+ const confirmMode = await askConfirmationMode();
3158
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
3159
+ let autoMode = confirmMode === 'auto';
3160
+
1913
3161
  let deletedSize = 0;
3162
+ let deletedCount = 0;
3163
+ let skippedCount = 0;
1914
3164
  console.log();
1915
3165
 
1916
- for (let i = 0; i < files.length; i++) {
1917
- if (!selected[i]) continue;
3166
+ for (const file of selectedFiles) {
3167
+ let shouldDelete = autoMode;
3168
+
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
+ }
1918
3180
 
1919
- if (deleteItem(files[i].path)) {
1920
- console.log(` ${chalk.green(figures.tick)} Deleted: ${path.basename(files[i].path)}`);
1921
- deletedSize += files[i].size;
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)}`);
1922
3194
  }
1923
3195
  }
1924
3196
 
1925
- console.log();
1926
- console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(deletedSize))}`);
3197
+ showCleanupSummary(deletedCount, skippedCount, deletedSize, confirmMode);
1927
3198
 
1928
3199
  playNotificationSound();
1929
3200
  await pressEnter();
@@ -1975,47 +3246,103 @@ async function findDuplicates() {
1975
3246
 
1976
3247
  spinner.stop();
1977
3248
 
1978
- // Display duplicate groups
1979
- header();
1980
- sectionHeader(MSG.DUPLICATES);
1981
-
1982
- let groupNum = 0;
3249
+ // Collect duplicate groups
3250
+ const duplicateGroups = [];
1983
3251
  let totalDupSize = 0;
1984
- const allDups = [];
1985
3252
 
1986
3253
  for (const [hash, fileList] of hashGroups) {
1987
3254
  if (fileList.length < 2) continue;
1988
3255
 
1989
- groupNum++;
1990
3256
  const fileSize = Math.round(getSize(fileList[0]));
1991
3257
  const dupSpace = (fileList.length - 1) * fileSize;
1992
3258
  totalDupSize += dupSpace;
1993
3259
 
1994
- console.log(` ${chalk.white(MSG.GROUP + ' ' + groupNum)} ${chalk.dim('(' + fileSize + ' MB ' + MSG.EACH + ')')}`);
3260
+ duplicateGroups.push({ hash, files: fileList, fileSize });
3261
+ if (duplicateGroups.length >= 10) break;
3262
+ }
1995
3263
 
1996
- fileList.forEach((file, idx) => {
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();
3284
+
3285
+ // Build checkbox choices - select files to KEEP (unselected will be deleted)
3286
+ const fileChoices = group.files.map((file, i) => {
1997
3287
  let display = file.replace(HOME, '~');
1998
3288
  if (display.length > 50) display = '...' + display.slice(-47);
1999
- console.log(` ${chalk.cyan('[' + (idx + 1) + ']')} ${display}`);
2000
- allDups.push(file);
3289
+ return {
3290
+ name: display,
3291
+ value: file,
3292
+ checked: i === 0 // First file is selected (kept) by default
3293
+ };
2001
3294
  });
2002
- console.log();
2003
3295
 
2004
- if (groupNum >= 10) break;
2005
- }
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
+ }
2006
3323
 
2007
- if (groupNum === 0) {
2008
- console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2009
- await pressEnter();
2010
- 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
+ }
2011
3338
  }
2012
3339
 
2013
- console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalDupSize))} ${MSG.IN_DUPLICATES}`);
2014
- console.log();
2015
- console.log(chalk.dim(` ${MSG.SELECT_KEEP}`));
2016
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3340
+ // Show summary
3341
+ header();
3342
+ sectionHeader(MSG.DUPLICATES);
3343
+ showCleanupSummary(totalDeletedCount, 0, totalDeletedSize, isDryRun ? 'dryrun' : 'auto');
2017
3344
 
2018
- console.log(` ${chalk.yellow(MSG.FEATURE_PROGRESS)}`);
3345
+ playNotificationSound();
2019
3346
  await pressEnter();
2020
3347
  }
2021
3348
 
@@ -2084,56 +3411,125 @@ async function uninstallApps() {
2084
3411
  return;
2085
3412
  }
2086
3413
 
2087
- apps.forEach((app, i) => {
2088
- console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${app.name.padEnd(25)} ${chalk.dim('(' + String(app.appSize).padStart(3) + ' MB ' + MSG.APP_SIZE + ' + ' + String(app.dataSize).padStart(3) + ' MB ' + MSG.DATA_SIZE + ')')}`);
2089
- });
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
+ });
2090
3426
 
2091
- console.log();
2092
- console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/n]:`));
2093
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3427
+ console.log();
3428
+ console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/n]:`));
3429
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2094
3430
 
2095
- if (choice.toLowerCase() === 'n' || choice === '') {
3431
+ if (choice.toLowerCase() === 'n' || choice === '') {
3432
+ return [];
3433
+ }
3434
+
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) {
2096
3453
  await pressEnter();
2097
3454
  return;
2098
3455
  }
2099
3456
 
2100
- const parts = choice.split(',');
2101
- for (const part of parts) {
2102
- const idx = parseInt(part.trim()) - 1;
2103
- if (idx < 0 || idx >= apps.length) continue;
2104
-
2105
- const app = apps[idx];
2106
- console.log(` ${chalk.yellow(MSG.DELETING + ' ' + app.name + '...')}`);
3457
+ // Collect selected apps
3458
+ const selectedApps = apps.filter(app => selectedPaths.includes(app.path));
2107
3459
 
2108
- // Remove app data
2109
- if (app.bundleId && !DRY_RUN) {
2110
- const dataPaths = [
2111
- path.join(HOME, 'Library/Application Support', app.name),
2112
- path.join(HOME, 'Library/Application Support', app.bundleId),
2113
- path.join(HOME, 'Library/Preferences', `${app.bundleId}.plist`),
2114
- path.join(HOME, 'Library/Caches', app.bundleId),
2115
- path.join(HOME, 'Library/Caches', app.name),
2116
- path.join(HOME, 'Library/Containers', app.bundleId),
2117
- path.join(HOME, 'Library/Saved Application State', `${app.bundleId}.savedState`),
2118
- path.join(HOME, 'Library/Logs', app.name)
2119
- ];
3460
+ if (selectedApps.length === 0) {
3461
+ await pressEnter();
3462
+ return;
3463
+ }
2120
3464
 
2121
- dataPaths.forEach(dp => {
2122
- try {
2123
- if (fs.existsSync(dp)) deleteItem(dp);
2124
- } catch (e) {}
2125
- });
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
+ }
2126
3488
  }
2127
3489
 
2128
- // Remove app
2129
- if (deleteItem(app.path)) {
2130
- console.log(` ${chalk.green(figures.tick)} ${app.name} ${MSG.REMOVED}`);
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
+ }
2131
3526
  } else {
2132
- console.log(` ${chalk.red(MSG.NEED_SUDO)}`);
2133
- console.log(` ${chalk.dim('sudo rm -rf "' + app.path + '"')}`);
3527
+ skippedCount++;
3528
+ console.log(` ${chalk.yellow(figures.cross)} ${MSG.SUMMARY_SKIPPED}: ${app.name}`);
2134
3529
  }
2135
3530
  }
2136
3531
 
3532
+ showCleanupSummary(deletedCount, skippedCount, freedSize, confirmMode);
2137
3533
  await pressEnter();
2138
3534
  }
2139
3535
 
@@ -2183,10 +3579,22 @@ async function manageStartup() {
2183
3579
  }
2184
3580
 
2185
3581
  console.log();
2186
- console.log(chalk.dim(" Enter number to toggle, 'd' to disable all, or 'n' to go back"));
2187
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2188
3582
 
2189
- 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') {
2190
3598
  exec(`osascript -e 'tell application "System Events" to delete every login item' 2>/dev/null`);
2191
3599
  console.log(` ${chalk.green(figures.tick)} All login items disabled`);
2192
3600
  }
@@ -2229,10 +3637,6 @@ async function cleanBrowsers() {
2229
3637
  addBrowserItem(`${ffProfile}/cache2`, 'Firefox Cache');
2230
3638
  }
2231
3639
 
2232
- items.forEach((item, i) => {
2233
- console.log(` ${chalk.cyan('[' + String(i + 1).padStart(2) + ']')} ${item.name.padEnd(30)} ${chalk.dim(String(item.size).padStart(6) + ' MB')}`);
2234
- });
2235
-
2236
3640
  if (items.length === 0) {
2237
3641
  console.log(` ${chalk.green(figures.tick)} ${MSG.NO_ITEMS}`);
2238
3642
  await pressEnter();
@@ -2243,37 +3647,105 @@ async function cleanBrowsers() {
2243
3647
  console.log(` ${figures.arrowRight} ${chalk.white(MSG.TOTAL + ':')} ${chalk.green(formatSize(totalBrowser))}`);
2244
3648
  console.log();
2245
3649
 
2246
- console.log(chalk.dim(` ${MSG.SELECT_DELETE} [numbers/a/n]:`));
2247
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
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 + ':')} `);
3665
+
3666
+ if (choice.toLowerCase() === 'n' || choice === '') {
3667
+ return [];
3668
+ }
2248
3669
 
2249
- if (choice.toLowerCase() === 'n' || choice === '') {
2250
- await pressEnter();
2251
- return;
2252
- }
3670
+ if (choice.toLowerCase() === 'a') {
3671
+ return items.map(item => item.path);
3672
+ }
2253
3673
 
2254
- if (choice.toLowerCase() === 'a') {
2255
- items.forEach(item => item.selected = true);
2256
- } else {
3674
+ const selected = [];
2257
3675
  const parts = choice.split(',');
2258
3676
  parts.forEach(part => {
2259
3677
  const idx = parseInt(part.trim()) - 1;
2260
- if (idx >= 0 && idx < items.length) items[idx].selected = true;
3678
+ if (idx >= 0 && idx < items.length) selected.push(items[idx].path);
2261
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;
2262
3703
  }
2263
3704
 
3705
+ // Ask confirmation mode
3706
+ const confirmMode = await askConfirmationMode();
3707
+ const isDryRun = confirmMode === 'dryrun' || DRY_RUN;
3708
+ let autoMode = confirmMode === 'auto';
3709
+
2264
3710
  let cleaned = 0;
3711
+ let deletedCount = 0;
3712
+ let skippedCount = 0;
2265
3713
  console.log();
2266
3714
 
2267
3715
  for (const item of items) {
2268
3716
  if (!item.selected) continue;
2269
- if (deleteItem(item.path)) {
2270
- console.log(` ${chalk.green(figures.tick)} ${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) + ')')}`);
2271
3734
  cleaned += item.size;
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}`);
2272
3745
  }
2273
3746
  }
2274
3747
 
2275
- console.log();
2276
- console.log(` ${chalk.green(MSG.FREED + ': ' + formatSize(cleaned))}`);
3748
+ showCleanupSummary(deletedCount, skippedCount, cleaned, confirmMode);
2277
3749
 
2278
3750
  playNotificationSound();
2279
3751
  await pressEnter();
@@ -2324,17 +3796,39 @@ async function killProcesses() {
2324
3796
  }
2325
3797
 
2326
3798
  console.log();
2327
- console.log(chalk.dim(` ${MSG.KILL} process [number/n]:`));
2328
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
2329
3799
 
2330
- if (choice.toLowerCase() === 'n' || choice === '') {
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' });
3807
+
3808
+ const fallbackProcess = async () => {
3809
+ console.log(chalk.dim(` ${MSG.KILL} process [number/n]:`));
3810
+ const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3811
+
3812
+ if (choice.toLowerCase() === 'n' || choice === '') {
3813
+ return 'back';
3814
+ }
3815
+
3816
+ const idx = parseInt(choice) - 1;
3817
+ if (idx >= 0 && idx < procs.length) {
3818
+ return procs[idx].pid;
3819
+ }
3820
+ return 'back';
3821
+ };
3822
+
3823
+ const selectedPid = await selectOption(`${MSG.KILL} process`, processChoices, fallbackProcess);
3824
+
3825
+ if (selectedPid === 'back' || !selectedPid) {
2331
3826
  await pressEnter();
2332
3827
  return;
2333
3828
  }
2334
3829
 
2335
- const idx = parseInt(choice) - 1;
2336
- if (idx >= 0 && idx < procs.length) {
2337
- const proc = procs[idx];
3830
+ const proc = procs.find(p => p.pid === selectedPid);
3831
+ if (proc) {
2338
3832
 
2339
3833
  if (DRY_RUN) {
2340
3834
  console.log(chalk.yellow(` ${figures.info} [DRY-RUN] Would kill: ${proc.name} (PID: ${proc.pid})`));
@@ -2362,21 +3856,40 @@ async function showSettings() {
2362
3856
 
2363
3857
  const currentLang = LANG_CODE === 'en' ? 'EN' : 'PT';
2364
3858
 
2365
- console.log(` ${chalk.cyan('[1]')} ${MSG.EXCLUSIONS}`);
2366
- console.log(` ${chalk.cyan('[2]')} ${MSG.CUSTOM_PATHS}`);
2367
- console.log(` ${chalk.cyan('[3]')} ${MSG.CHANGE_LANG} (current: ${currentLang})`);
2368
- console.log(` ${chalk.cyan('[4]')} ${MSG.RESET_DEFAULTS}`);
2369
- console.log(` ${chalk.cyan('[5]')} ${DRY_RUN ? chalk.green(figures.tick) : figures.radioOff} Dry-Run Mode`);
2370
- console.log(` ${chalk.cyan('[6]')} ${SECURE_DELETE ? chalk.green(figures.tick) : figures.radioOff} Secure Delete Mode`);
2371
- console.log(` ${chalk.cyan('[7]')} View Backups`);
2372
- console.log();
2373
- console.log(` ${chalk.cyan('[0]')} ${MSG.BACK}`);
2374
- console.log();
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
+ };
2375
3888
 
2376
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
3889
+ const choice = await selectOption(MSG.OPT_SETTINGS, settingsChoices, fallbackSettings);
2377
3890
 
2378
3891
  switch (choice) {
2379
- case '1':
3892
+ case 'exclusions':
2380
3893
  // Manage exclusions
2381
3894
  header();
2382
3895
  sectionHeader(MSG.EXCLUSIONS);
@@ -2403,7 +3916,7 @@ async function showSettings() {
2403
3916
  await pressEnter();
2404
3917
  break;
2405
3918
 
2406
- case '2':
3919
+ case 'paths':
2407
3920
  // Custom paths
2408
3921
  header();
2409
3922
  sectionHeader(MSG.CUSTOM_PATHS);
@@ -2430,7 +3943,7 @@ async function showSettings() {
2430
3943
  await pressEnter();
2431
3944
  break;
2432
3945
 
2433
- case '3':
3946
+ case 'lang':
2434
3947
  // Change language
2435
3948
  const newLang = LANG_CODE === 'pt' ? 'en' : 'pt';
2436
3949
  fs.writeFileSync(LANGUAGE_FILE, newLang);
@@ -2441,7 +3954,7 @@ async function showSettings() {
2441
3954
  process.exit(0);
2442
3955
  break;
2443
3956
 
2444
- case '4':
3957
+ case 'reset':
2445
3958
  // Reset defaults
2446
3959
  [EXCLUSIONS_FILE, CUSTOM_PATHS_FILE, LANGUAGE_FILE].forEach(f => {
2447
3960
  if (fs.existsSync(f)) fs.unlinkSync(f);
@@ -2453,19 +3966,19 @@ async function showSettings() {
2453
3966
  process.exit(0);
2454
3967
  break;
2455
3968
 
2456
- case '5':
3969
+ case 'dryrun':
2457
3970
  DRY_RUN = !DRY_RUN;
2458
3971
  console.log(` ${chalk.green(figures.tick)} Dry-Run Mode: ${DRY_RUN ? 'ON' : 'OFF'}`);
2459
3972
  await pressEnter();
2460
3973
  break;
2461
3974
 
2462
- case '6':
3975
+ case 'secure':
2463
3976
  SECURE_DELETE = !SECURE_DELETE;
2464
3977
  console.log(` ${chalk.green(figures.tick)} Secure Delete Mode: ${SECURE_DELETE ? 'ON' : 'OFF'}`);
2465
3978
  await pressEnter();
2466
3979
  break;
2467
3980
 
2468
- case '7':
3981
+ case 'backups':
2469
3982
  // View backups
2470
3983
  header();
2471
3984
  sectionHeader('Backups');
@@ -2481,7 +3994,7 @@ async function showSettings() {
2481
3994
  await pressEnter();
2482
3995
  break;
2483
3996
 
2484
- case '0':
3997
+ case 'back':
2485
3998
  return;
2486
3999
 
2487
4000
  default:
@@ -2501,53 +4014,99 @@ async function mainMenu() {
2501
4014
  // Show update notification
2502
4015
  await showUpdateNotification();
2503
4016
 
2504
- // Main options
2505
- console.log(chalk.white(' CLEANING'));
2506
- console.log(` ${chalk.cyan('[1]')} ${MSG.OPT_QUICK}`);
2507
- console.log(` ${chalk.cyan('[2]')} ${MSG.OPT_LARGE}`);
2508
- console.log(` ${chalk.cyan('[3]')} ${MSG.OPT_DUPLICATES}`);
2509
- console.log(` ${chalk.cyan('[4]')} ${MSG.OPT_APPS}`);
2510
- console.log(` ${chalk.cyan('[5]')} ${MSG.OPT_BROWSERS}`);
2511
- console.log(` ${chalk.cyan('[6]')} ${MSG.OPT_PRIVACY}`);
2512
- console.log();
2513
-
2514
- console.log(chalk.white(' SYSTEM'));
2515
- console.log(` ${chalk.cyan('[7]')} ${MSG.OPT_STARTUP}`);
2516
- console.log(` ${chalk.cyan('[8]')} ${MSG.OPT_PROCESSES}`);
2517
- console.log(` ${chalk.cyan('[9]')} ${MSG.OPT_MEMORY}`);
2518
- console.log(` ${chalk.cyan('[10]')} ${MSG.OPT_DISK}`);
2519
- console.log();
2520
-
2521
- console.log(chalk.white(' TOOLS'));
2522
- console.log(` ${chalk.cyan('[11]')} ${MSG.OPT_BENCHMARK}`);
2523
- console.log(` ${chalk.cyan('[12]')} ${MSG.OPT_SCHEDULER}`);
2524
- console.log(` ${chalk.cyan('[13]')} ${MSG.OPT_EXPORT}`);
2525
- console.log(` ${chalk.cyan('[14]')} ${MSG.OPT_STATS}`);
2526
- console.log(` ${chalk.cyan('[15]')} ${MSG.OPT_SETTINGS}`);
2527
- console.log();
2528
-
2529
- console.log(` ${chalk.cyan('[0]')} ${MSG.OPT_EXIT}`);
2530
- console.log();
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
+ };
2531
4084
 
2532
- const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
4085
+ const choice = await selectOption(
4086
+ chalk.cyan('\uD83E\uDDF9 ARIS MAC CLEANER v' + VERSION),
4087
+ menuChoices,
4088
+ fallbackMenu
4089
+ );
2533
4090
 
2534
4091
  switch (choice) {
2535
- case '1': await quickClean(); break;
2536
- case '2': await findLargeFiles(); break;
2537
- case '3': await findDuplicates(); break;
2538
- case '4': await uninstallApps(); break;
2539
- case '5': await cleanBrowsers(); break;
2540
- case '6': await privacySweep(); break;
2541
- case '7': await manageStartup(); break;
2542
- case '8': await killProcesses(); break;
2543
- case '9': await memoryOptimizer(); break;
2544
- case '10': await diskAnalyzer(); break;
2545
- case '11': await benchmarkMode(); break;
2546
- case '12': await scheduler(); break;
2547
- case '13': await exportHtmlReport(); break;
2548
- case '14': await showStatistics(); break;
2549
- case '15': await showSettings(); break;
2550
- case '0':
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':
2551
4110
  clearScreen();
2552
4111
  console.log();
2553
4112
  console.log(gradient.pastel.multiline(`
@@ -2559,8 +4118,10 @@ async function mainMenu() {
2559
4118
  process.exit(0);
2560
4119
  break;
2561
4120
  default:
2562
- console.log(` ${chalk.red(MSG.INVALID)}`);
2563
- 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
+ }
2564
4125
  }
2565
4126
  }
2566
4127
  }
@@ -2618,6 +4179,7 @@ Premium Features:
2618
4179
  - Benchmark mode
2619
4180
  - Privacy sweep
2620
4181
  - Memory optimizer
4182
+ - Thermal monitor with power metrics
2621
4183
  - Scheduled cleanups (launchd)
2622
4184
  - Sound notifications
2623
4185
  - Dry-run and secure delete modes