@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.
@@ -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
- console.log(chalk.cyan('Scanning for cleanable files...\n'));
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');
@@ -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')} Monitor Preset (Current: ${config.monitorPreset})`);
229
- console.log(` ${chalk.cyan('2')} Safety Level (Current: ${config.safetyLevel})`);
230
- console.log(` ${chalk.cyan('3')} Auto Confirm (Current: ${config.autoConfirm ? 'Yes' : 'No'})`);
231
- console.log(` ${chalk.cyan('4')} Dry Run (Current: ${config.dryRun ? 'Yes' : 'No'})`);
232
- console.log(` ${chalk.cyan('5')} Verbose (Current: ${config.verbose ? 'Yes' : 'No'})`);
233
- console.log(` ${chalk.cyan('6')} Manage Whitelist (${config.whitelist.length} items)`);
234
- console.log(` ${chalk.cyan('7')} Manage Blacklist (${config.blacklist.length} items)`);
235
- console.log(` ${chalk.cyan('8')} View All Settings`);
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-9):');
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 '3':
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 '4':
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 '5':
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 '6':
258
+ case '5':
263
259
  await manageWhitelist(config);
264
260
  break;
265
- case '7':
261
+ case '6':
266
262
  await manageBlacklist(config);
267
263
  break;
268
- case '8':
264
+ case '7':
269
265
  await listConfig();
270
266
  break;
271
- case '9':
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
  */
@@ -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: showBroom ? 12 : 0,
110
+ top: 0,
264
111
  left: 0,
265
112
  width: '100%',
266
- height: 3,
113
+ height: 2,
267
114
  tags: true,
268
- border: { type: 'line' },
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: showBroom ? 15 : 3,
120
+ top: 2,
276
121
  left: 0,
277
- width: '50%-1',
278
- height: 10,
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: showBroom ? 15 : 3,
288
- left: '50%',
289
- width: '50%',
290
- height: 10,
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: showBroom ? 25 : 13,
300
- left: 0,
301
- width: '50%-1',
302
- height: 6,
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: showBroom ? 25 : 13,
312
- left: '50%',
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: showBroom ? 31 : 19,
324
- left: 0,
325
- width: '100%',
326
- height: 9,
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 = ` {bold}Health:{/bold} {${health > 70 ? 'green' : health > 40 ? 'yellow' : 'red'}-fg}${health}%{/} | ` +
387
- `{bold}Host:{/bold} ${osInfo.hostname.split('.')[0]} | ` +
388
- `{bold}CPU:{/bold} ${cpuInfo.manufacturer} ${cpuInfo.brand} ${gpuName}`;
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, 6);
229
+ const coresToShow = cpuCores.slice(0, 4);
394
230
  coresToShow.forEach((core, i) => {
395
- const bar = createColoredBar(core.load, 15);
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 {gray-fg}Load: ${cpuLoad.currentLoad.toFixed(1)}%${temp}{/gray-fg}`;
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, 15)} ${memUsedPercent.toFixed(1).padStart(5)}%\n`;
407
- memContent += ` Free ${createColoredBar(100 - memFreePercent, 15)} ${memFreePercent.toFixed(1).padStart(5)}%\n`;
408
- memContent += ` Swap ${createColoredBar(swapUsedPercent, 15)} ${swapUsedPercent.toFixed(1).padStart(5)}%\n`;
409
- memContent += `\n {gray-fg}Total: ${formatBytes(mem.total)} | Avail: ${formatBytes(mem.available)}{/gray-fg}`;
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, 20);
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)} / ${formatBytes(mainDisk?.size ?? 0)}{/gray-fg}\n`;
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} Download: ${formatSpeed(rxSpeed).padStart(12)}\n`;
423
- netContent += ` {red-fg}↑{/red-fg} Upload: ${formatSpeed(txSpeed).padStart(12)}\n`;
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, 6).forEach((proc, i) => {
429
- const bar = createColoredBar(Math.min(proc.cpu, 100), 10);
430
- procContent += ` ${(i + 1).toString().padStart(2)}. ${proc.name.padEnd(20)} ${bar} ${proc.cpu.toFixed(1).padStart(5)}%\n`;
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 app
214
+ // Select apps (multiple selection via space)
215
215
  console.log();
216
- const selectedApp = await selectApp(apps);
217
- if (!selectedApp) {
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 ${selectedApp.name} related files...`);
224
- const relatedFiles = await findAppFiles(selectedApp);
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 app info
233
+ // Show selected apps info
227
234
  console.log();
228
- console.log(chalk.bold(`Application: ${selectedApp.name}`));
229
- console.log(` Path: ${chalk.dim(selectedApp.path)}`);
230
- console.log(` Size: ${chalk.yellow(formatSize(selectedApp.size))}`);
231
- if (selectedApp.bundleId) {
232
- console.log(` Bundle ID: ${chalk.dim(selectedApp.bundleId)}`);
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
- for (const file of relatedFiles.slice(0, 10)) {
240
- console.log(` ${file.isDirectory ? 'šŸ“' : 'šŸ“„'} ${chalk.dim(file.path)} ${chalk.yellow(formatSize(file.size))}`);
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 > 10) {
243
- console.log(chalk.dim(` ... and ${relatedFiles.length - 10} more`));
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 confirmed = await confirmAction(`Uninstall ${selectedApp.name} and remove related files?`, false);
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
- appRemoved = await removeAppToTrash(selectedApp.path);
272
- if (appRemoved) {
273
- freedSpace += selectedApp.size;
274
- }
275
- else {
276
- errors.push(`Failed to remove ${selectedApp.name}`);
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
- appRemoved = true;
297
- freedSpace = selectedApp.size;
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
- `Application: ${appRemoved ? chalk.green('Removed') : chalk.red('Failed')}`,
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
  ];