@tukuyomil032/broom 1.0.0 ā 1.0.5
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/dist/commands/analyze.js +432 -149
- package/dist/commands/clean.js +23 -2
- package/dist/commands/config.js +16 -46
- package/dist/commands/status.js +39 -211
- package/dist/commands/uninstall.js +51 -28
- package/dist/index.js +10 -9
- package/dist/types/index.js +0 -1
- package/dist/ui/prompts.js +21 -11
- package/dist/utils/icon.js +67 -0
- package/package.json +8 -4
package/dist/commands/clean.js
CHANGED
|
@@ -60,14 +60,35 @@ export async function cleanCommand(options) {
|
|
|
60
60
|
// Scan phase
|
|
61
61
|
const scanners = getAllScanners();
|
|
62
62
|
const progress = createProgress(scanners.length);
|
|
63
|
-
|
|
63
|
+
// Display scan targets
|
|
64
|
+
console.log(chalk.bold('\nš Scan Targets:'));
|
|
65
|
+
console.log(chalk.dim('The following locations will be scanned:\n'));
|
|
66
|
+
const scanTargets = [
|
|
67
|
+
{ icon: 'š', name: 'User Caches', path: '~/Library/Caches' },
|
|
68
|
+
{ icon: 'š', name: 'User Logs', path: '~/Library/Logs' },
|
|
69
|
+
{ icon: 'š', name: 'Trash', path: '~/.Trash' },
|
|
70
|
+
{ icon: 'š', name: 'Browser Caches', path: '~/Library/**/Cache' },
|
|
71
|
+
{ icon: 'š¦', name: 'Development Caches', path: 'Various dev tool caches' },
|
|
72
|
+
{ icon: 'š ', name: 'Xcode Caches', path: '~/Library/Developer' },
|
|
73
|
+
{ icon: 'ā¬ļø', name: 'Old Downloads', path: '~/Downloads' },
|
|
74
|
+
{ icon: 'šŗ', name: 'Homebrew Caches', path: '/Library/Caches/Homebrew' },
|
|
75
|
+
{ icon: 'š³', name: 'Docker Artifacts', path: '~/.docker' },
|
|
76
|
+
{ icon: 'š±', name: 'iOS Backups', path: '~/Library/Caches/com.apple.bird*' },
|
|
77
|
+
{ icon: 'āļø', name: 'Temporary Files', path: '/var/tmp, /tmp' },
|
|
78
|
+
{ icon: 'š', name: 'Node Modules', path: 'Large node_modules directories' },
|
|
79
|
+
{ icon: 'š§', name: 'Old Installers', path: 'Failed/old installer files' },
|
|
80
|
+
];
|
|
81
|
+
for (const target of scanTargets) {
|
|
82
|
+
console.log(` ${target.icon} ${target.name.padEnd(25)} ${chalk.dim(target.path)}`);
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk.cyan('\nScanning for cleanable files...\n'));
|
|
64
85
|
debug(`Starting scan with ${scanners.length} scanners`);
|
|
65
86
|
const summary = await runAllScans({
|
|
66
87
|
parallel: true,
|
|
67
88
|
concurrency: 4,
|
|
68
89
|
onProgress: (completed, total, scanner) => {
|
|
69
90
|
debug(`Scan progress: ${completed}/${total} - ${scanner.category.name}`);
|
|
70
|
-
progress.update(completed, `Scanning ${scanner.category.name}
|
|
91
|
+
progress.update(completed, `Scanning ${scanner.category.name}... (${completed}/${total})`);
|
|
71
92
|
},
|
|
72
93
|
});
|
|
73
94
|
progress.finish('Scan complete');
|
package/dist/commands/config.js
CHANGED
|
@@ -225,50 +225,46 @@ async function interactiveConfigEdit() {
|
|
|
225
225
|
console.log(chalk.bold('āļø Configuration Editor'));
|
|
226
226
|
console.log(chalk.dim('Select an option to configure:'));
|
|
227
227
|
console.log();
|
|
228
|
-
console.log(` ${chalk.cyan('1')}
|
|
229
|
-
console.log(` ${chalk.cyan('2')}
|
|
230
|
-
console.log(` ${chalk.cyan('3')}
|
|
231
|
-
console.log(` ${chalk.cyan('4')}
|
|
232
|
-
console.log(` ${chalk.cyan('5')}
|
|
233
|
-
console.log(` ${chalk.cyan('6')} Manage
|
|
234
|
-
console.log(` ${chalk.cyan('7')}
|
|
235
|
-
console.log(` ${chalk.cyan('8')}
|
|
236
|
-
console.log(` ${chalk.cyan('9')} Reset to Defaults`);
|
|
228
|
+
console.log(` ${chalk.cyan('1')} Safety Level (Current: ${config.safetyLevel})`);
|
|
229
|
+
console.log(` ${chalk.cyan('2')} Auto Confirm (Current: ${config.autoConfirm ? 'Yes' : 'No'})`);
|
|
230
|
+
console.log(` ${chalk.cyan('3')} Dry Run (Current: ${config.dryRun ? 'Yes' : 'No'})`);
|
|
231
|
+
console.log(` ${chalk.cyan('4')} Verbose (Current: ${config.verbose ? 'Yes' : 'No'})`);
|
|
232
|
+
console.log(` ${chalk.cyan('5')} Manage Whitelist (${config.whitelist.length} items)`);
|
|
233
|
+
console.log(` ${chalk.cyan('6')} Manage Blacklist (${config.blacklist.length} items)`);
|
|
234
|
+
console.log(` ${chalk.cyan('7')} View All Settings`);
|
|
235
|
+
console.log(` ${chalk.cyan('8')} Reset to Defaults`);
|
|
237
236
|
console.log(` ${chalk.cyan('0')} Exit`);
|
|
238
237
|
console.log();
|
|
239
|
-
const choice = await inputPrompt('Choose an option (0-
|
|
238
|
+
const choice = await inputPrompt('Choose an option (0-8):');
|
|
240
239
|
switch (choice) {
|
|
241
240
|
case '1':
|
|
242
|
-
await configureMonitorPreset(config);
|
|
243
|
-
break;
|
|
244
|
-
case '2':
|
|
245
241
|
await configureSafetyLevel(config);
|
|
246
242
|
break;
|
|
247
|
-
case '
|
|
243
|
+
case '2':
|
|
248
244
|
config.autoConfirm = !config.autoConfirm;
|
|
249
245
|
await saveConfig(config);
|
|
250
246
|
success(`Auto Confirm set to: ${config.autoConfirm ? 'Yes' : 'No'}`);
|
|
251
247
|
break;
|
|
252
|
-
case '
|
|
248
|
+
case '3':
|
|
253
249
|
config.dryRun = !config.dryRun;
|
|
254
250
|
await saveConfig(config);
|
|
255
251
|
success(`Dry Run set to: ${config.dryRun ? 'Yes' : 'No'}`);
|
|
256
252
|
break;
|
|
257
|
-
case '
|
|
253
|
+
case '4':
|
|
258
254
|
config.verbose = !config.verbose;
|
|
259
255
|
await saveConfig(config);
|
|
260
256
|
success(`Verbose set to: ${config.verbose ? 'Yes' : 'No'}`);
|
|
261
257
|
break;
|
|
262
|
-
case '
|
|
258
|
+
case '5':
|
|
263
259
|
await manageWhitelist(config);
|
|
264
260
|
break;
|
|
265
|
-
case '
|
|
261
|
+
case '6':
|
|
266
262
|
await manageBlacklist(config);
|
|
267
263
|
break;
|
|
268
|
-
case '
|
|
264
|
+
case '7':
|
|
269
265
|
await listConfig();
|
|
270
266
|
break;
|
|
271
|
-
case '
|
|
267
|
+
case '8':
|
|
272
268
|
const confirmed = await confirmAction('Reset configuration to defaults?', false);
|
|
273
269
|
if (confirmed) {
|
|
274
270
|
await saveConfig({ ...DEFAULT_CONFIG });
|
|
@@ -288,32 +284,6 @@ async function interactiveConfigEdit() {
|
|
|
288
284
|
}
|
|
289
285
|
}
|
|
290
286
|
}
|
|
291
|
-
/**
|
|
292
|
-
* Configure monitor preset
|
|
293
|
-
*/
|
|
294
|
-
async function configureMonitorPreset(config) {
|
|
295
|
-
console.log();
|
|
296
|
-
console.log(chalk.bold('Monitor Presets:'));
|
|
297
|
-
console.log(` ${chalk.cyan('1')} Classic Grid Layout (CPU, Memory, Disk, Network in grid)`);
|
|
298
|
-
console.log(` ${chalk.cyan('2')} Minimal Compact (Simple single-panel layout)`);
|
|
299
|
-
console.log(` ${chalk.cyan('3')} Detailed Information (Comprehensive system info)`);
|
|
300
|
-
console.log(` ${chalk.cyan('4')} Linux-style Dashboard (Like htop/top)`);
|
|
301
|
-
console.log(` ${chalk.cyan('5')} Modern Colorful Dashboard (Color-rich modern design)`);
|
|
302
|
-
console.log();
|
|
303
|
-
const choice = await inputPrompt('Select preset (1-5):');
|
|
304
|
-
if (!choice) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const preset = parseInt(choice);
|
|
308
|
-
if (preset >= 1 && preset <= 5) {
|
|
309
|
-
config.monitorPreset = preset;
|
|
310
|
-
await saveConfig(config);
|
|
311
|
-
success(`Monitor preset set to: ${preset}`);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
error('Invalid preset selection');
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
287
|
/**
|
|
318
288
|
* Configure safety level
|
|
319
289
|
*/
|
package/dist/commands/status.js
CHANGED
|
@@ -8,147 +8,6 @@ import { exec } from 'child_process';
|
|
|
8
8
|
import { promisify } from 'util';
|
|
9
9
|
import { enhanceCommandHelp } from '../utils/help.js';
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
|
-
// Broom character ASCII art animation frames (like mole's cat)
|
|
12
|
-
const BROOM_FRAMES = [
|
|
13
|
-
// Frame 0: Idle
|
|
14
|
-
`
|
|
15
|
-
āāāāāāāāāāāāāāāāāāā®
|
|
16
|
-
ā āāāāāāāāā® ā
|
|
17
|
-
ā ā ā ā ā ā
|
|
18
|
-
ā ā āāā ā ā
|
|
19
|
-
ā ā°āāāāāāā⯠ā
|
|
20
|
-
ā āāāāāāāāā® ā
|
|
21
|
-
ā /ā BROOM ā\\ ā
|
|
22
|
-
ā / ā°āāāāāāā⯠\\ ā
|
|
23
|
-
ā / ||||||| \\ ā
|
|
24
|
-
ā ||||||| ā
|
|
25
|
-
ā°āāāāāāāāāāāāāāāāāāÆ
|
|
26
|
-
`,
|
|
27
|
-
// Frame 1: Sweeping right
|
|
28
|
-
`
|
|
29
|
-
āāāāāāāāāāāāāāāāāāā®
|
|
30
|
-
ā āāāāāāāāā® ā
|
|
31
|
-
ā ā ā ā ā ā
|
|
32
|
-
ā ā āāā āāāā ā
|
|
33
|
-
ā ā°āāāāāāāāÆāāā ā
|
|
34
|
-
ā āāāāāāāāā®āā ā
|
|
35
|
-
ā /ā BROOM ā\\ ā
|
|
36
|
-
ā / ā°āāāāāāā⯠\\ ā
|
|
37
|
-
ā /|||||||\\ ā
|
|
38
|
-
ā ||||||| ā
|
|
39
|
-
ā°āāāāāāāāāāāāāāāāāāÆ
|
|
40
|
-
`,
|
|
41
|
-
// Frame 2: Sweeping
|
|
42
|
-
`
|
|
43
|
-
āāāāāāāāāāāāāāāāāāā®
|
|
44
|
-
ā āāāāāāāāā® ā
|
|
45
|
-
ā ā āį“ā ā ā
|
|
46
|
-
ā āā ā ~~~ ā ā
|
|
47
|
-
āāāā ā°āāāāāāā⯠ā
|
|
48
|
-
āāā āāāāāāāāā® ā
|
|
49
|
-
ā /ā BROOM ā\\ ā
|
|
50
|
-
ā / ā°āāāāāāā⯠\\ ā
|
|
51
|
-
ā / ||||||| \\ ā
|
|
52
|
-
ā ||||||| ā
|
|
53
|
-
ā°āāāāāāāāāāāāāāāāāāÆ
|
|
54
|
-
`,
|
|
55
|
-
// Frame 3: Dust cloud
|
|
56
|
-
`
|
|
57
|
-
āāāāāāāāāāāāāāāāāāā®
|
|
58
|
-
ā āāāāāāāāā® Ā· ā
|
|
59
|
-
ā Ā· ā ^ ^ ā Ā· ā
|
|
60
|
-
ā Ā· ā ā”ā” ā Ā· ā
|
|
61
|
-
ā Ā· Ā·ā°āāāāāāāāÆĀ· Ā· ā
|
|
62
|
-
ā Ā· āāāāāāāāā® Ā· ā
|
|
63
|
-
ā /ā BROOM ā\\ ā
|
|
64
|
-
ā / ā°āāāāāāā⯠\\ ā
|
|
65
|
-
ā /|||||||\\ ā
|
|
66
|
-
ā ||||||| ā
|
|
67
|
-
ā°āāāāāāāāāāāāāāāāāāÆ
|
|
68
|
-
`,
|
|
69
|
-
// Frame 4: Sparkle clean
|
|
70
|
-
`
|
|
71
|
-
āāāāāāāāāāāāāāāāāāā®
|
|
72
|
-
ā āāāāāāāā⮠⨠ā
|
|
73
|
-
ā ⨠ā ā§ ā§ ā ā
|
|
74
|
-
ā ā ā”ā”ā” ā ⨠ā
|
|
75
|
-
ā ā°āāāāāāā⯠ā
|
|
76
|
-
ā ⨠āāāāāāāāā® ā
|
|
77
|
-
ā /ā CLEAN ā\\ ā
|
|
78
|
-
ā / ā°āāāāāāā⯠\\ āØā
|
|
79
|
-
ā /|||||||\\ ā
|
|
80
|
-
ā ||||||| ā
|
|
81
|
-
ā°āāāāāāāāāāāāāāāāāāÆ
|
|
82
|
-
`,
|
|
83
|
-
];
|
|
84
|
-
// Simpler frames for better compatibility
|
|
85
|
-
const BROOM_SIMPLE_FRAMES = [
|
|
86
|
-
// Frame 0: Idle
|
|
87
|
-
[
|
|
88
|
-
' āāāāāāāāāāā® ',
|
|
89
|
-
' ā ā ā ā ',
|
|
90
|
-
' ā āā ā ',
|
|
91
|
-
' ā°āāāāā¬āāā⯠',
|
|
92
|
-
' āāāāāā“āāāā® ',
|
|
93
|
-
' ā BROOM ā ',
|
|
94
|
-
' ā°āāāāāāāā⯠',
|
|
95
|
-
' āā ',
|
|
96
|
-
' āāāā ',
|
|
97
|
-
' āāāāāā ',
|
|
98
|
-
],
|
|
99
|
-
// Frame 1: Sweeping right
|
|
100
|
-
[
|
|
101
|
-
' āāāāāāāāāāā® ',
|
|
102
|
-
' ā ā ā ā ā ',
|
|
103
|
-
' ā āā āāāā',
|
|
104
|
-
' ā°āāāāā¬āāā⯠āā',
|
|
105
|
-
' āāāāāā“āāāā® ā ',
|
|
106
|
-
' ā BROOM ā ',
|
|
107
|
-
' ā°āāāāāāāā⯠',
|
|
108
|
-
' āā ',
|
|
109
|
-
' āāāā\\ ',
|
|
110
|
-
' āāāāāā\\ ',
|
|
111
|
-
],
|
|
112
|
-
// Frame 2: Sweeping left
|
|
113
|
-
[
|
|
114
|
-
' āāāāāāāāāāā® ',
|
|
115
|
-
' ā ā āį“ā ā ',
|
|
116
|
-
'āāāā ~~ ā ',
|
|
117
|
-
'āā ā°āāāāā¬āāā⯠',
|
|
118
|
-
' ā āāāāāā“āāāā® ',
|
|
119
|
-
' ā BROOM ā ',
|
|
120
|
-
' ā°āāāāāāāā⯠',
|
|
121
|
-
' āā ',
|
|
122
|
-
' /āāāā ',
|
|
123
|
-
' /āāāāāā ',
|
|
124
|
-
],
|
|
125
|
-
// Frame 3: Dust cloud
|
|
126
|
-
[
|
|
127
|
-
' āāāāāāāāāāā® Ā· ',
|
|
128
|
-
' Ā· ā ^ ^ ā Ā· ',
|
|
129
|
-
' Ā·ā ā”ā” āĀ· ',
|
|
130
|
-
' Ā· ā°āāāāā¬āāā⯠· ',
|
|
131
|
-
' Ā·āāāāāā“āāāā®Ā· ',
|
|
132
|
-
' ā BROOM ā ',
|
|
133
|
-
' ā°āāāāāāāā⯠',
|
|
134
|
-
' āā ',
|
|
135
|
-
' āāāā ',
|
|
136
|
-
' āāāāāā ',
|
|
137
|
-
],
|
|
138
|
-
// Frame 4: Sparkle
|
|
139
|
-
[
|
|
140
|
-
' āāāāāāāāāāā® āØ',
|
|
141
|
-
' āØā ā§ ā§ ā ',
|
|
142
|
-
' ā ā”ā”ā” ā āØ',
|
|
143
|
-
' ā°āāāāā¬āāā⯠',
|
|
144
|
-
' āØāāāāāā“āāāā® ',
|
|
145
|
-
' ā CLEAN! ā āØ',
|
|
146
|
-
' ā°āāāāāāāā⯠',
|
|
147
|
-
' āā ',
|
|
148
|
-
' āāāā ',
|
|
149
|
-
' āāāāāā ',
|
|
150
|
-
],
|
|
151
|
-
];
|
|
152
11
|
/**
|
|
153
12
|
* Format bytes to human readable
|
|
154
13
|
*/
|
|
@@ -239,43 +98,29 @@ function calculateHealth(cpuUsage, memUsage, diskUsage, batteryPercent) {
|
|
|
239
98
|
*/
|
|
240
99
|
export async function statusCommand(options) {
|
|
241
100
|
const interval = (options.interval ?? 2) * 1000;
|
|
242
|
-
const showBroom = options.broom !== false;
|
|
243
|
-
let broomFrame = 0;
|
|
244
101
|
// Create blessed screen
|
|
245
102
|
const screen = blessed.screen({
|
|
246
103
|
smartCSR: true,
|
|
247
104
|
title: 'Broom System Status',
|
|
248
105
|
fullUnicode: true,
|
|
249
106
|
});
|
|
250
|
-
// Animation box at top
|
|
251
|
-
const animBox = blessed.box({
|
|
252
|
-
parent: screen,
|
|
253
|
-
top: 0,
|
|
254
|
-
left: 'center',
|
|
255
|
-
width: 25,
|
|
256
|
-
height: showBroom ? 12 : 0,
|
|
257
|
-
tags: true,
|
|
258
|
-
style: { fg: 'cyan' },
|
|
259
|
-
});
|
|
260
107
|
// Header
|
|
261
108
|
const headerBox = blessed.box({
|
|
262
109
|
parent: screen,
|
|
263
|
-
top:
|
|
110
|
+
top: 0,
|
|
264
111
|
left: 0,
|
|
265
112
|
width: '100%',
|
|
266
|
-
height:
|
|
113
|
+
height: 2,
|
|
267
114
|
tags: true,
|
|
268
|
-
|
|
269
|
-
style: { border: { fg: 'cyan' } },
|
|
270
|
-
label: ' {bold}š§¹ Broom System Status{/bold} ',
|
|
115
|
+
style: { fg: 'cyan' },
|
|
271
116
|
});
|
|
272
117
|
// CPU Box
|
|
273
118
|
const cpuBox = blessed.box({
|
|
274
119
|
parent: screen,
|
|
275
|
-
top:
|
|
120
|
+
top: 2,
|
|
276
121
|
left: 0,
|
|
277
|
-
width: '
|
|
278
|
-
height:
|
|
122
|
+
width: '33%-1',
|
|
123
|
+
height: 8,
|
|
279
124
|
label: ' {yellow-fg}ā{/yellow-fg} CPU ',
|
|
280
125
|
tags: true,
|
|
281
126
|
border: { type: 'line' },
|
|
@@ -284,10 +129,10 @@ export async function statusCommand(options) {
|
|
|
284
129
|
// Memory Box
|
|
285
130
|
const memBox = blessed.box({
|
|
286
131
|
parent: screen,
|
|
287
|
-
top:
|
|
288
|
-
left: '
|
|
289
|
-
width: '
|
|
290
|
-
height:
|
|
132
|
+
top: 2,
|
|
133
|
+
left: '33%',
|
|
134
|
+
width: '34%-1',
|
|
135
|
+
height: 8,
|
|
291
136
|
label: ' {red-fg}ā£{/red-fg} Memory ',
|
|
292
137
|
tags: true,
|
|
293
138
|
border: { type: 'line' },
|
|
@@ -296,10 +141,10 @@ export async function statusCommand(options) {
|
|
|
296
141
|
// Disk Box
|
|
297
142
|
const diskBox = blessed.box({
|
|
298
143
|
parent: screen,
|
|
299
|
-
top:
|
|
300
|
-
left:
|
|
301
|
-
width: '
|
|
302
|
-
height:
|
|
144
|
+
top: 2,
|
|
145
|
+
left: '67%-1',
|
|
146
|
+
width: '33%',
|
|
147
|
+
height: 8,
|
|
303
148
|
label: ' {blue-fg}ā£{/blue-fg} Disk ',
|
|
304
149
|
tags: true,
|
|
305
150
|
border: { type: 'line' },
|
|
@@ -308,9 +153,9 @@ export async function statusCommand(options) {
|
|
|
308
153
|
// Network Box
|
|
309
154
|
const netBox = blessed.box({
|
|
310
155
|
parent: screen,
|
|
311
|
-
top:
|
|
312
|
-
left:
|
|
313
|
-
width: '50
|
|
156
|
+
top: 10,
|
|
157
|
+
left: 0,
|
|
158
|
+
width: '50%-1',
|
|
314
159
|
height: 6,
|
|
315
160
|
label: ' {cyan-fg}ā{/cyan-fg} Network ',
|
|
316
161
|
tags: true,
|
|
@@ -320,10 +165,10 @@ export async function statusCommand(options) {
|
|
|
320
165
|
// Processes Box
|
|
321
166
|
const procBox = blessed.box({
|
|
322
167
|
parent: screen,
|
|
323
|
-
top:
|
|
324
|
-
left:
|
|
325
|
-
width: '
|
|
326
|
-
height:
|
|
168
|
+
top: 10,
|
|
169
|
+
left: '50%',
|
|
170
|
+
width: '50%',
|
|
171
|
+
height: 6,
|
|
327
172
|
label: ' {magenta-fg}ā{/magenta-fg} Top Processes ',
|
|
328
173
|
tags: true,
|
|
329
174
|
border: { type: 'line' },
|
|
@@ -344,16 +189,6 @@ export async function statusCommand(options) {
|
|
|
344
189
|
screen.key(['escape', 'q', 'C-c'], () => {
|
|
345
190
|
process.exit(0);
|
|
346
191
|
});
|
|
347
|
-
/**
|
|
348
|
-
* Update animation
|
|
349
|
-
*/
|
|
350
|
-
function updateAnimation() {
|
|
351
|
-
if (!showBroom)
|
|
352
|
-
return;
|
|
353
|
-
const frame = BROOM_SIMPLE_FRAMES[broomFrame % BROOM_SIMPLE_FRAMES.length];
|
|
354
|
-
animBox.setContent('{cyan-fg}' + frame.join('\n') + '{/cyan-fg}');
|
|
355
|
-
broomFrame++;
|
|
356
|
-
}
|
|
357
192
|
/**
|
|
358
193
|
* Update all data
|
|
359
194
|
*/
|
|
@@ -383,51 +218,52 @@ export async function statusCommand(options) {
|
|
|
383
218
|
// Header content
|
|
384
219
|
const gpu = graphics.controllers?.[0];
|
|
385
220
|
const gpuName = gpu?.model ? `(${gpu.model.replace('Apple ', '').split(' ')[0]})` : '';
|
|
386
|
-
const headerContent = `
|
|
387
|
-
`
|
|
388
|
-
`
|
|
221
|
+
const headerContent = `{bold}š§¹ Broom System Status{/bold}\n` +
|
|
222
|
+
`Health: {${health > 70 ? 'green' : health > 40 ? 'yellow' : 'red'}-fg}${health}%{/} | ` +
|
|
223
|
+
`Host: {cyan-fg}${osInfo.hostname.split('.')[0]}{/} | ` +
|
|
224
|
+
`CPU: ${cpuInfo.manufacturer} ${cpuInfo.brand} ${gpuName}`;
|
|
389
225
|
headerBox.setContent(headerContent);
|
|
390
226
|
// CPU content
|
|
391
227
|
const cpuCores = cpuLoad.cpus || [];
|
|
392
228
|
let cpuContent = '';
|
|
393
|
-
const coresToShow = cpuCores.slice(0,
|
|
229
|
+
const coresToShow = cpuCores.slice(0, 4);
|
|
394
230
|
coresToShow.forEach((core, i) => {
|
|
395
|
-
const bar = createColoredBar(core.load,
|
|
231
|
+
const bar = createColoredBar(core.load, 12);
|
|
396
232
|
cpuContent += ` Core${(i + 1).toString().padStart(2)} ${bar} ${core.load.toFixed(1).padStart(5)}%\n`;
|
|
397
233
|
});
|
|
398
234
|
const temp = cpuTemp.main > 0 ? ` @ ${cpuTemp.main.toFixed(0)}°C` : '';
|
|
399
|
-
cpuContent += `\n
|
|
235
|
+
cpuContent += `\n{gray-fg}Load: ${cpuLoad.currentLoad.toFixed(1)}%${temp}{/gray-fg}`;
|
|
400
236
|
cpuBox.setContent(cpuContent);
|
|
401
237
|
// Memory content
|
|
402
238
|
const memUsedPercent = (mem.used / mem.total) * 100;
|
|
403
239
|
const memFreePercent = (mem.free / mem.total) * 100;
|
|
404
240
|
const swapUsedPercent = mem.swaptotal > 0 ? (mem.swapused / mem.swaptotal) * 100 : 0;
|
|
405
241
|
let memContent = '';
|
|
406
|
-
memContent += ` Used ${createColoredBar(memUsedPercent,
|
|
407
|
-
memContent += ` Free ${createColoredBar(100 - memFreePercent,
|
|
408
|
-
memContent += ` Swap ${createColoredBar(swapUsedPercent,
|
|
409
|
-
memContent += `\n
|
|
242
|
+
memContent += ` Used ${createColoredBar(memUsedPercent, 12)} ${memUsedPercent.toFixed(1).padStart(5)}%\n`;
|
|
243
|
+
memContent += ` Free ${createColoredBar(100 - memFreePercent, 12)} ${memFreePercent.toFixed(1).padStart(5)}%\n`;
|
|
244
|
+
memContent += ` Swap ${createColoredBar(swapUsedPercent, 12)} ${swapUsedPercent.toFixed(1).padStart(5)}%\n`;
|
|
245
|
+
memContent += `\n{gray-fg}Total: ${formatBytes(mem.total)}{/gray-fg}`;
|
|
410
246
|
memBox.setContent(memContent);
|
|
411
247
|
// Disk content
|
|
412
|
-
const diskBar = createColoredBar(diskUsage,
|
|
248
|
+
const diskBar = createColoredBar(diskUsage, 15);
|
|
413
249
|
let diskContent = '';
|
|
414
250
|
diskContent += ` Usage ${diskBar} ${diskUsage.toFixed(1).padStart(5)}%\n`;
|
|
415
|
-
diskContent += ` {gray-fg}Used: ${formatBytes(mainDisk?.used ?? 0)}
|
|
251
|
+
diskContent += ` {gray-fg}Used: ${formatBytes(mainDisk?.used ?? 0)}{/gray-fg}\n`;
|
|
416
252
|
diskContent += ` {gray-fg}Free: ${formatBytes((mainDisk?.size ?? 0) - (mainDisk?.used ?? 0))}{/gray-fg}`;
|
|
417
253
|
diskBox.setContent(diskContent);
|
|
418
254
|
// Network content
|
|
419
255
|
const rxSpeed = activeNet?.rx_sec ?? 0;
|
|
420
256
|
const txSpeed = activeNet?.tx_sec ?? 0;
|
|
421
257
|
let netContent = '';
|
|
422
|
-
netContent += ` {green-fg}ā{/green-fg}
|
|
423
|
-
netContent += ` {red-fg}ā{/red-fg}
|
|
258
|
+
netContent += ` {green-fg}ā{/green-fg} ${formatSpeed(rxSpeed).padStart(12)}\n`;
|
|
259
|
+
netContent += ` {red-fg}ā{/red-fg} ${formatSpeed(txSpeed).padStart(12)}\n`;
|
|
424
260
|
netContent += ` {gray-fg}IP: ${localIP}{/gray-fg}`;
|
|
425
261
|
netBox.setContent(netContent);
|
|
426
262
|
// Processes content
|
|
427
263
|
let procContent = '';
|
|
428
|
-
topProcs.slice(0,
|
|
429
|
-
const bar = createColoredBar(Math.min(proc.cpu, 100),
|
|
430
|
-
procContent += ` ${(i + 1).toString().padStart(2)}. ${proc.name.padEnd(
|
|
264
|
+
topProcs.slice(0, 4).forEach((proc, i) => {
|
|
265
|
+
const bar = createColoredBar(Math.min(proc.cpu, 100), 8);
|
|
266
|
+
procContent += ` ${(i + 1).toString().padStart(2)}. ${proc.name.padEnd(18)} ${bar} ${proc.cpu.toFixed(1).padStart(5)}%\n`;
|
|
431
267
|
});
|
|
432
268
|
procBox.setContent(procContent);
|
|
433
269
|
screen.render();
|
|
@@ -437,19 +273,12 @@ export async function statusCommand(options) {
|
|
|
437
273
|
}
|
|
438
274
|
}
|
|
439
275
|
// Initial update
|
|
440
|
-
updateAnimation();
|
|
441
276
|
await update();
|
|
442
277
|
// Set interval for updates
|
|
443
278
|
const updateInterval = setInterval(update, interval);
|
|
444
|
-
// Animation interval (faster for smoother animation)
|
|
445
|
-
const animInterval = setInterval(() => {
|
|
446
|
-
updateAnimation();
|
|
447
|
-
screen.render();
|
|
448
|
-
}, 500);
|
|
449
279
|
// Cleanup on exit
|
|
450
280
|
screen.on('destroy', () => {
|
|
451
281
|
clearInterval(updateInterval);
|
|
452
|
-
clearInterval(animInterval);
|
|
453
282
|
});
|
|
454
283
|
screen.render();
|
|
455
284
|
}
|
|
@@ -460,7 +289,6 @@ export function createStatusCommand() {
|
|
|
460
289
|
const cmd = new Command('status')
|
|
461
290
|
.description('Real-time system monitoring dashboard')
|
|
462
291
|
.option('-i, --interval <seconds>', 'Update interval in seconds (default: 2)', parseInt)
|
|
463
|
-
.option('--no-broom', 'Disable broom animation')
|
|
464
292
|
.action(async (options) => {
|
|
465
293
|
await statusCommand(options);
|
|
466
294
|
});
|
|
@@ -211,42 +211,61 @@ export async function uninstallCommand(options) {
|
|
|
211
211
|
warning('No applications found');
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
|
-
// Select
|
|
214
|
+
// Select apps (multiple selection via space)
|
|
215
215
|
console.log();
|
|
216
|
-
const
|
|
217
|
-
if (!
|
|
216
|
+
const selectedApps = await selectApp(apps);
|
|
217
|
+
if (!selectedApps || selectedApps.length === 0) {
|
|
218
218
|
warning('No application selected');
|
|
219
219
|
return;
|
|
220
220
|
}
|
|
221
|
-
// Find related files
|
|
221
|
+
// Find related files for all selected apps
|
|
222
222
|
console.log();
|
|
223
|
-
const searchSpinner = createSpinner(`Searching for
|
|
224
|
-
const
|
|
223
|
+
const searchSpinner = createSpinner(`Searching for related files...`);
|
|
224
|
+
const relatedFilesAll = new Map();
|
|
225
|
+
for (const app of selectedApps) {
|
|
226
|
+
const files = await findAppFiles(app);
|
|
227
|
+
for (const f of files) {
|
|
228
|
+
relatedFilesAll.set(f.path, f);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const relatedFiles = Array.from(relatedFilesAll.values());
|
|
225
232
|
succeedSpinner(searchSpinner, `Found ${relatedFiles.length} related files`);
|
|
226
|
-
// Show
|
|
233
|
+
// Show selected apps info
|
|
227
234
|
console.log();
|
|
228
|
-
console.log(chalk.bold(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.log(`
|
|
235
|
+
console.log(chalk.bold('Selected Applications:'));
|
|
236
|
+
let totalAppSize = 0;
|
|
237
|
+
for (const app of selectedApps) {
|
|
238
|
+
totalAppSize += app.size;
|
|
239
|
+
console.log(` - ${chalk.bold(app.name)} ${chalk.dim(app.path)} ${chalk.yellow(formatSize(app.size))}`);
|
|
240
|
+
if (app.bundleId) {
|
|
241
|
+
console.log(` Bundle ID: ${chalk.dim(app.bundleId)}`);
|
|
242
|
+
}
|
|
233
243
|
}
|
|
244
|
+
// Ensure one blank line between Selected Applications and Related Files
|
|
245
|
+
console.log();
|
|
234
246
|
// Show related files
|
|
235
247
|
if (relatedFiles.length > 0) {
|
|
236
248
|
const totalRelatedSize = relatedFiles.reduce((sum, f) => sum + f.size, 0);
|
|
237
|
-
console.log();
|
|
238
249
|
console.log(chalk.bold(`Related Files (${formatSize(totalRelatedSize)}):`));
|
|
239
|
-
|
|
240
|
-
|
|
250
|
+
// Determine column widths for neat alignment
|
|
251
|
+
const filesToShow = relatedFiles.slice(0, 10);
|
|
252
|
+
const maxPath = Math.min(80, Math.max(...filesToShow.map((f) => f.path.length), 20));
|
|
253
|
+
const sizeWidth = 12;
|
|
254
|
+
for (const file of filesToShow) {
|
|
255
|
+
const icon = file.isDirectory ? 'š' : 'š';
|
|
256
|
+
const pathDisp = chalk.dim(file.path.padEnd(maxPath));
|
|
257
|
+
const sizeDisp = chalk.yellow(formatSize(file.size).padStart(sizeWidth));
|
|
258
|
+
console.log(` ${icon} ${pathDisp} ${sizeDisp}`);
|
|
241
259
|
}
|
|
242
|
-
if (relatedFiles.length >
|
|
243
|
-
console.log(chalk.dim(` ... and ${relatedFiles.length -
|
|
260
|
+
if (relatedFiles.length > filesToShow.length) {
|
|
261
|
+
console.log(chalk.dim(` ... and ${relatedFiles.length - filesToShow.length} more`));
|
|
244
262
|
}
|
|
245
263
|
}
|
|
246
264
|
// Confirm uninstall
|
|
247
265
|
console.log();
|
|
248
266
|
if (!options.yes) {
|
|
249
|
-
const
|
|
267
|
+
const names = selectedApps.map((a) => a.name).join(', ');
|
|
268
|
+
const confirmed = await confirmAction(`Uninstall ${selectedApps.length} app(s): ${names} and remove related files?`, false);
|
|
250
269
|
if (!confirmed) {
|
|
251
270
|
warning('Uninstall cancelled');
|
|
252
271
|
return;
|
|
@@ -267,13 +286,17 @@ export async function uninstallCommand(options) {
|
|
|
267
286
|
let freedSpace = 0;
|
|
268
287
|
const errors = [];
|
|
269
288
|
if (!isDryRun) {
|
|
270
|
-
// Remove app
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
// Remove each selected app
|
|
290
|
+
let appsRemovedCount = 0;
|
|
291
|
+
for (const app of selectedApps) {
|
|
292
|
+
const removed = await removeAppToTrash(app.path);
|
|
293
|
+
if (removed) {
|
|
294
|
+
appsRemovedCount++;
|
|
295
|
+
freedSpace += app.size;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
errors.push(`Failed to remove ${app.name}`);
|
|
299
|
+
}
|
|
277
300
|
}
|
|
278
301
|
// Remove related files
|
|
279
302
|
for (const file of filesToRemove) {
|
|
@@ -293,8 +316,8 @@ export async function uninstallCommand(options) {
|
|
|
293
316
|
}
|
|
294
317
|
else {
|
|
295
318
|
// Dry run - just count
|
|
296
|
-
|
|
297
|
-
freedSpace =
|
|
319
|
+
const appsRemovedCount = selectedApps.length;
|
|
320
|
+
freedSpace = selectedApps.reduce((s, a) => s + a.size, 0);
|
|
298
321
|
filesRemoved = filesToRemove.length;
|
|
299
322
|
freedSpace += filesToRemove.reduce((sum, f) => sum + f.size, 0);
|
|
300
323
|
}
|
|
@@ -307,7 +330,7 @@ export async function uninstallCommand(options) {
|
|
|
307
330
|
// Print summary
|
|
308
331
|
const summaryHeading = isDryRun ? 'Dry Run Complete' : 'Uninstall Complete';
|
|
309
332
|
const summaryDetails = [
|
|
310
|
-
`
|
|
333
|
+
`Applications removed: ${chalk.green(String(selectedApps.length - errors.filter((e) => e.startsWith('Failed to remove')).length))}`,
|
|
311
334
|
`Related files removed: ${filesRemoved}`,
|
|
312
335
|
`Space freed: ${chalk.green(formatSize(freedSpace))}`,
|
|
313
336
|
];
|