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