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