@tukuyomil032/broom 1.0.0 → 1.0.1
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/config.js +16 -46
- package/dist/commands/uninstall.js +51 -28
- package/dist/index.js +1 -1
- package/dist/types/index.js +0 -1
- package/dist/ui/prompts.js +21 -11
- package/dist/utils/icon.js +67 -0
- package/package.json +1 -1
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
|
*/
|
|
@@ -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
|
];
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import chalk from 'chalk';
|
|
|
10
10
|
import { createCleanCommand, createUninstallCommand, createOptimizeCommand, createAnalyzeCommand, createStatusCommand, createPurgeCommand, createInstallerCommand, createTouchIdCommand, createCompletionCommand, createUpdateCommand, createRemoveCommand, createConfigCommand, createDoctorCommand, createBackupCommand, createRestoreCommand, createDuplicatesCommand, createScheduleCommand, createWatchCommand, createReportsCommand, createHelpCommand, setCommandsList, } from './commands/index.js';
|
|
11
11
|
import { enableDebug, debug } from './utils/debug.js';
|
|
12
12
|
import { getGlobalOptionsTable } from './utils/help.js';
|
|
13
|
-
const VERSION = '1.0.
|
|
13
|
+
const VERSION = '1.0.1';
|
|
14
14
|
// ASCII art logo
|
|
15
15
|
const logo = chalk.cyan(`
|
|
16
16
|
██████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗
|
package/dist/types/index.js
CHANGED
package/dist/ui/prompts.js
CHANGED
|
@@ -75,25 +75,35 @@ export async function selectFiles(items) {
|
|
|
75
75
|
return [];
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
// No app icon rendering here — removed per request. Formatting below aligns columns.
|
|
78
79
|
/**
|
|
79
|
-
* Select an application
|
|
80
|
+
* Select an application with custom navigation (no looping)
|
|
80
81
|
*/
|
|
81
82
|
export async function selectApp(apps) {
|
|
82
|
-
if (apps.length === 0)
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
if (apps.length === 0)
|
|
84
|
+
return [];
|
|
85
|
+
// Align columns: [Name padded] [Size right-aligned] [BundleId dimmed]
|
|
86
|
+
const maxName = Math.min(36, Math.max(...apps.map((a) => a.name.length), 10));
|
|
87
|
+
const sizeWidth = 10;
|
|
88
|
+
const choices = apps.map((app) => {
|
|
89
|
+
const namePad = app.name.padEnd(maxName);
|
|
90
|
+
const sizeStr = formatSize(app.size).padStart(sizeWidth);
|
|
91
|
+
const bundle = app.bundleId ? chalk.dim(` ${app.bundleId}`) : '';
|
|
92
|
+
return {
|
|
93
|
+
name: `${namePad} ${chalk.yellow(sizeStr)}${bundle}`,
|
|
94
|
+
value: app,
|
|
95
|
+
checked: false,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
89
98
|
try {
|
|
90
|
-
return await
|
|
91
|
-
message: 'Select
|
|
99
|
+
return await checkbox({
|
|
100
|
+
message: 'Select applications to uninstall (space to toggle, Enter to confirm):',
|
|
92
101
|
choices,
|
|
102
|
+
loop: false,
|
|
93
103
|
});
|
|
94
104
|
}
|
|
95
105
|
catch {
|
|
96
|
-
return
|
|
106
|
+
return [];
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
/**
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { tmpdir } from 'os';
|
|
3
|
+
import { join, basename, extname } from 'path';
|
|
4
|
+
import { execFile } from 'child_process';
|
|
5
|
+
import fg from 'fast-glob';
|
|
6
|
+
function execFileAsync(cmd, args) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
execFile(cmd, args, (err, stdout, stderr) => {
|
|
9
|
+
if (err)
|
|
10
|
+
return reject(err);
|
|
11
|
+
resolve({ stdout, stderr });
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export async function findIcnsFiles(appPath) {
|
|
16
|
+
const patterns = [
|
|
17
|
+
`${appPath}/Contents/Resources/*.icns`,
|
|
18
|
+
`${appPath}/Contents/Resources/*.*icon*`,
|
|
19
|
+
];
|
|
20
|
+
const results = await fg(patterns, { onlyFiles: true, unique: true });
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
export async function convertIcnsToPng(icnsPath, size, outPath) {
|
|
24
|
+
// Use sips to convert and resize
|
|
25
|
+
// sips -s format png <in> --out <out>
|
|
26
|
+
// then resize with sips -z <height> <width> <out>
|
|
27
|
+
await execFileAsync('sips', ['-s', 'format', 'png', icnsPath, '--out', outPath]);
|
|
28
|
+
await execFileAsync('sips', ['-z', String(size), String(size), outPath]);
|
|
29
|
+
}
|
|
30
|
+
export function supportsKitty() {
|
|
31
|
+
return Boolean(process.env.KITTY_WINDOW_ID);
|
|
32
|
+
}
|
|
33
|
+
export function supportsIterm() {
|
|
34
|
+
return Boolean(process.env.ITERM_SESSION_ID);
|
|
35
|
+
}
|
|
36
|
+
export function supportsWezTerm() {
|
|
37
|
+
return Boolean(process.env.WEZTERM_WINDOW || process.env.WEZTERM_EXECUTABLE || process.env.TERM_PROGRAM === 'WezTerm');
|
|
38
|
+
}
|
|
39
|
+
export async function getAppIconInline(appPath, size = 24) {
|
|
40
|
+
try {
|
|
41
|
+
const icns = await findIcnsFiles(appPath);
|
|
42
|
+
if (icns.length === 0)
|
|
43
|
+
return '';
|
|
44
|
+
const icnsPath = icns[0];
|
|
45
|
+
const tmpName = `broom-icon-${basename(icnsPath, extname(icnsPath))}-${size}.png`;
|
|
46
|
+
const outPath = join(tmpdir(), tmpName);
|
|
47
|
+
await convertIcnsToPng(icnsPath, size, outPath);
|
|
48
|
+
const buf = await fs.readFile(outPath);
|
|
49
|
+
const b64 = buf.toString('base64');
|
|
50
|
+
// iTerm2 / WezTerm protocol (OSC 1337)
|
|
51
|
+
if (supportsIterm() || supportsWezTerm()) {
|
|
52
|
+
// ESC ] 1337;File=...:<base64> BEL
|
|
53
|
+
return `\u001b]1337;File=inline=1;width=${size}px;height=${size}px;preserveAspectRatio=1;:${b64}\u0007 `;
|
|
54
|
+
}
|
|
55
|
+
// Kitty protocol
|
|
56
|
+
if (supportsKitty()) {
|
|
57
|
+
// ESC _ G ... base64 ESC \\
|
|
58
|
+
// Use minimal header; many kitty-compatible terminals accept this
|
|
59
|
+
return `\u001b_Gf=100,a=T;inline=1;width=${size}px;height=${size}px;${b64}\u001b\\ `;
|
|
60
|
+
}
|
|
61
|
+
// No supported inline image protocol
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
CHANGED