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.
- package/README.md +66 -32
- package/bin/cli.js +1840 -278
- 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 = '
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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: '
|
|
283
|
-
OPT_STARTUP: 'Startup
|
|
284
|
-
OPT_BROWSERS: '
|
|
285
|
-
OPT_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
|
-
|
|
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}
|
|
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('
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
1259
|
-
|
|
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
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
|
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 === '
|
|
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 === '
|
|
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 === '
|
|
1291
|
-
const isDaily = choice === '
|
|
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
|
|
1518
|
-
|
|
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
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
-
|
|
2364
|
+
const choice = await prompt(` ${chalk.white(MSG.CHOOSE + ':')} `);
|
|
1736
2365
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return;
|
|
1741
|
-
}
|
|
2366
|
+
if (choice.toLowerCase() === 'n' || choice === '') {
|
|
2367
|
+
return [];
|
|
2368
|
+
}
|
|
1742
2369
|
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
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].
|
|
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].
|
|
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
|
|
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
|
|
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
|
-
|
|
1781
|
-
const
|
|
1782
|
-
|
|
2436
|
+
if (autoMode && !isDryRun) {
|
|
2437
|
+
const progressBar = createProgressBar(selectedItems.length);
|
|
2438
|
+
progressBar.start(selectedItems.length, 0);
|
|
1783
2439
|
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1794
|
-
|
|
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.
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
2542
|
+
showCleanupSummary(deletedCount, skippedCount, cleanedSize, confirmMode);
|
|
1826
2543
|
getSystemStats();
|
|
1827
2544
|
showFinalReport(cleanedSize);
|
|
1828
2545
|
await pressEnter();
|
|
1829
2546
|
}
|
|
1830
2547
|
|
|
1831
2548
|
// ============================================================================
|
|
1832
|
-
//
|
|
2549
|
+
// DEEP CLEAN (AGGRESSIVE)
|
|
1833
2550
|
// ============================================================================
|
|
1834
2551
|
|
|
1835
|
-
async function
|
|
2552
|
+
async function deepClean() {
|
|
1836
2553
|
header();
|
|
1837
|
-
sectionHeader(`${MSG.
|
|
2554
|
+
sectionHeader(`${MSG.DEEP_CLEAN} ${chalk.red('\uD83D\uDD25')}`);
|
|
1838
2555
|
|
|
1839
|
-
|
|
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
|
|
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
|
-
|
|
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 >
|
|
1876
|
-
displayFile = '...' + displayFile.slice(-
|
|
3089
|
+
if (displayFile.length > 45) {
|
|
3090
|
+
displayFile = '...' + displayFile.slice(-42);
|
|
1877
3091
|
}
|
|
1878
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1886
|
-
|
|
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 (
|
|
3145
|
+
if (selectedPaths.length === 0) {
|
|
1889
3146
|
await pressEnter();
|
|
1890
3147
|
return;
|
|
1891
3148
|
}
|
|
1892
3149
|
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
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 (
|
|
1917
|
-
|
|
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 (
|
|
1920
|
-
console.log(` ${chalk.
|
|
1921
|
-
deletedSize +=
|
|
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
|
-
|
|
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
|
-
//
|
|
1979
|
-
|
|
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
|
-
|
|
3260
|
+
duplicateGroups.push({ hash, files: fileList, fileSize });
|
|
3261
|
+
if (duplicateGroups.length >= 10) break;
|
|
3262
|
+
}
|
|
1995
3263
|
|
|
1996
|
-
|
|
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
|
-
|
|
2000
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
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
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
3340
|
+
// Show summary
|
|
3341
|
+
header();
|
|
3342
|
+
sectionHeader(MSG.DUPLICATES);
|
|
3343
|
+
showCleanupSummary(totalDeletedCount, 0, totalDeletedSize, isDryRun ? 'dryrun' : 'auto');
|
|
2017
3344
|
|
|
2018
|
-
|
|
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
|
-
|
|
2088
|
-
|
|
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
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
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
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
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
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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
|
-
|
|
2133
|
-
console.log(` ${chalk.
|
|
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
|
-
|
|
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
|
-
|
|
2247
|
-
const
|
|
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
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
}
|
|
3670
|
+
if (choice.toLowerCase() === 'a') {
|
|
3671
|
+
return items.map(item => item.path);
|
|
3672
|
+
}
|
|
2253
3673
|
|
|
2254
|
-
|
|
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].
|
|
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
|
-
|
|
2270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2336
|
-
if (
|
|
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
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
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
|
|
3889
|
+
const choice = await selectOption(MSG.OPT_SETTINGS, settingsChoices, fallbackSettings);
|
|
2377
3890
|
|
|
2378
3891
|
switch (choice) {
|
|
2379
|
-
case '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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
|
-
//
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
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
|
|
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 '
|
|
2536
|
-
case '
|
|
2537
|
-
case '
|
|
2538
|
-
case '
|
|
2539
|
-
case '
|
|
2540
|
-
case '
|
|
2541
|
-
case '
|
|
2542
|
-
case '
|
|
2543
|
-
case '
|
|
2544
|
-
case '
|
|
2545
|
-
case '
|
|
2546
|
-
case '
|
|
2547
|
-
case '
|
|
2548
|
-
case '
|
|
2549
|
-
case '
|
|
2550
|
-
case '
|
|
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
|
-
|
|
2563
|
-
|
|
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
|