@tukuyomil032/broom 1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +554 -0
  3. package/dist/commands/analyze.js +371 -0
  4. package/dist/commands/backup.js +257 -0
  5. package/dist/commands/clean.js +255 -0
  6. package/dist/commands/completion.js +714 -0
  7. package/dist/commands/config.js +474 -0
  8. package/dist/commands/doctor.js +280 -0
  9. package/dist/commands/duplicates.js +325 -0
  10. package/dist/commands/help.js +34 -0
  11. package/dist/commands/index.js +22 -0
  12. package/dist/commands/installer.js +266 -0
  13. package/dist/commands/optimize.js +270 -0
  14. package/dist/commands/purge.js +271 -0
  15. package/dist/commands/remove.js +184 -0
  16. package/dist/commands/reports.js +173 -0
  17. package/dist/commands/schedule.js +249 -0
  18. package/dist/commands/status.js +468 -0
  19. package/dist/commands/touchid.js +230 -0
  20. package/dist/commands/uninstall.js +336 -0
  21. package/dist/commands/update.js +182 -0
  22. package/dist/commands/watch.js +258 -0
  23. package/dist/index.js +131 -0
  24. package/dist/scanners/base.js +21 -0
  25. package/dist/scanners/browser-cache.js +111 -0
  26. package/dist/scanners/dev-cache.js +64 -0
  27. package/dist/scanners/docker.js +96 -0
  28. package/dist/scanners/downloads.js +66 -0
  29. package/dist/scanners/homebrew.js +82 -0
  30. package/dist/scanners/index.js +126 -0
  31. package/dist/scanners/installer.js +87 -0
  32. package/dist/scanners/ios-backups.js +82 -0
  33. package/dist/scanners/node-modules.js +75 -0
  34. package/dist/scanners/temp-files.js +65 -0
  35. package/dist/scanners/trash.js +90 -0
  36. package/dist/scanners/user-cache.js +62 -0
  37. package/dist/scanners/user-logs.js +53 -0
  38. package/dist/scanners/xcode.js +124 -0
  39. package/dist/types/index.js +23 -0
  40. package/dist/ui/index.js +5 -0
  41. package/dist/ui/monitors.js +345 -0
  42. package/dist/ui/output.js +304 -0
  43. package/dist/ui/prompts.js +270 -0
  44. package/dist/utils/config.js +133 -0
  45. package/dist/utils/debug.js +119 -0
  46. package/dist/utils/fs.js +283 -0
  47. package/dist/utils/help.js +265 -0
  48. package/dist/utils/index.js +6 -0
  49. package/dist/utils/paths.js +142 -0
  50. package/dist/utils/report.js +404 -0
  51. package/package.json +87 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Terminal output helpers for Broom CLI
3
+ */
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { formatSize } from '../utils/fs.js';
7
+ // Icons
8
+ export const ICONS = {
9
+ success: '✓',
10
+ error: '✗',
11
+ warning: '⚠',
12
+ info: 'ℹ',
13
+ arrow: '→',
14
+ folder: '📁',
15
+ file: '📄',
16
+ trash: '🗑',
17
+ clean: '🧹',
18
+ disk: '💾',
19
+ cpu: '🖥',
20
+ memory: '🧠',
21
+ network: '🌐',
22
+ dryRun: '👁',
23
+ admin: '🔐',
24
+ };
25
+ /**
26
+ * Print success message
27
+ */
28
+ export function success(message) {
29
+ console.log(chalk.green(ICONS.success), message);
30
+ }
31
+ /**
32
+ * Print error message
33
+ */
34
+ export function error(message) {
35
+ console.log(chalk.red(ICONS.error), message);
36
+ }
37
+ /**
38
+ * Print warning message
39
+ */
40
+ export function warning(message) {
41
+ console.log(chalk.yellow(ICONS.warning), message);
42
+ }
43
+ /**
44
+ * Print info message
45
+ */
46
+ export function info(message) {
47
+ console.log(chalk.blue(ICONS.info), message);
48
+ }
49
+ /**
50
+ * Print separator line
51
+ */
52
+ export function separator(char = '─', length = 60) {
53
+ console.log(chalk.gray(char.repeat(length)));
54
+ }
55
+ /**
56
+ * Print header
57
+ */
58
+ export function printHeader(title) {
59
+ console.log();
60
+ console.log(chalk.bold.magenta(title));
61
+ separator();
62
+ }
63
+ /**
64
+ * Print welcome message
65
+ */
66
+ export function printWelcome() {
67
+ console.log();
68
+ console.log(chalk.bold.cyan('🧹 Broom'));
69
+ console.log(chalk.dim('macOS Disk Cleanup Tool'));
70
+ separator();
71
+ }
72
+ /**
73
+ * Print file with details
74
+ */
75
+ export function printFile(item, index) {
76
+ const prefix = index !== undefined ? `${String(index + 1).padStart(2, ' ')}.` : ' ';
77
+ const icon = item.isDirectory ? ICONS.folder : ICONS.file;
78
+ const size = chalk.yellow(formatSize(item.size).padStart(10));
79
+ const path = chalk.dim(item.path);
80
+ console.log(`${prefix} ${icon} ${size} ${path}`);
81
+ }
82
+ /**
83
+ * Print file list
84
+ */
85
+ export function printFiles(items) {
86
+ items.forEach((item, index) => printFile(item, index));
87
+ }
88
+ /**
89
+ * Print scan result
90
+ */
91
+ export function printScanResult(result) {
92
+ const { category, items, totalSize } = result;
93
+ const sizeStr = formatSize(totalSize);
94
+ const countStr = `${items.length} items`;
95
+ if (items.length > 0) {
96
+ console.log(` ${chalk.green(ICONS.success)} ${category.name.padEnd(35)} ${chalk.yellow(sizeStr.padStart(12))} ${chalk.dim(`(${countStr})`)}`);
97
+ }
98
+ else {
99
+ console.log(` ${chalk.gray(ICONS.info)} ${category.name.padEnd(35)} ${chalk.gray('Empty')}`);
100
+ }
101
+ }
102
+ /**
103
+ * Print removal summary
104
+ */
105
+ export function printRemovalSummary(summary) {
106
+ separator();
107
+ console.log(chalk.bold('Removal Summary:'));
108
+ console.log(`Total files: ${chalk.cyan(summary.totalFiles)}`);
109
+ console.log(`Successfully removed: ${chalk.green(summary.successCount)}`);
110
+ if (summary.failureCount > 0) {
111
+ console.log(`Failed: ${chalk.red(summary.failureCount)}`);
112
+ }
113
+ console.log(`Space freed: ${chalk.yellow(formatSize(summary.totalSizeFreed))}`);
114
+ separator();
115
+ if (summary.failureCount > 0) {
116
+ console.log(chalk.bold('\nFailed removals:'));
117
+ summary.results
118
+ .filter((r) => !r.success)
119
+ .forEach((r) => {
120
+ error(`${r.path}: ${r.error || 'Unknown error'}`);
121
+ });
122
+ }
123
+ }
124
+ /**
125
+ * Print summary block
126
+ */
127
+ export function printSummaryBlock(heading, details) {
128
+ console.log();
129
+ separator('═');
130
+ console.log(chalk.bold(heading));
131
+ separator('─');
132
+ details.forEach((detail) => console.log(` ${detail}`));
133
+ separator('═');
134
+ }
135
+ /**
136
+ * Create spinner
137
+ */
138
+ export function createSpinner(text) {
139
+ return ora({
140
+ text,
141
+ spinner: 'dots',
142
+ }).start();
143
+ }
144
+ /**
145
+ * Start spinner
146
+ */
147
+ export function startSpinner(text) {
148
+ return ora(text).start();
149
+ }
150
+ /**
151
+ * Succeed spinner
152
+ */
153
+ export function succeedSpinner(spinner, text) {
154
+ if (text) {
155
+ spinner.succeed(text);
156
+ }
157
+ else {
158
+ spinner.succeed();
159
+ }
160
+ }
161
+ /**
162
+ * Fail spinner
163
+ */
164
+ export function failSpinner(spinner, text) {
165
+ if (text) {
166
+ spinner.fail(text);
167
+ }
168
+ else {
169
+ spinner.fail();
170
+ }
171
+ }
172
+ /**
173
+ * Update spinner text
174
+ */
175
+ export function updateSpinner(spinner, text) {
176
+ spinner.text = text;
177
+ }
178
+ /**
179
+ * Print progress bar inline with improved design
180
+ */
181
+ export function printProgressBar(percent, width = 30, label = '') {
182
+ const filled = Math.round((percent / 100) * width);
183
+ // Build bar without gridlines for progress bars
184
+ let bar = '';
185
+ for (let i = 0; i < width; i++) {
186
+ if (i < filled) {
187
+ const ratio = i / width;
188
+ if (ratio < 0.5) {
189
+ bar += chalk.green('█');
190
+ }
191
+ else if (ratio < 0.75) {
192
+ bar += chalk.yellow('█');
193
+ }
194
+ else {
195
+ bar += chalk.red('█');
196
+ }
197
+ }
198
+ else {
199
+ bar += chalk.gray('░');
200
+ }
201
+ }
202
+ const percentStr = `${percent.toFixed(0)}%`.padStart(4);
203
+ const line = `\r${chalk.gray('│')}${bar}${chalk.gray('│')} ${percentStr}${label ? ` ${chalk.dim(label)}` : ''}`;
204
+ process.stdout.write(line);
205
+ }
206
+ /**
207
+ * Print styled progress bar with message
208
+ */
209
+ export function printStyledProgressBar(percent, width = 35, message = '', showPercent = true) {
210
+ const filled = Math.round((percent / 100) * width);
211
+ // Build bar without gridlines for styled progress bars
212
+ let bar = chalk.gray('╔');
213
+ for (let i = 0; i < width; i++) {
214
+ if (i < filled) {
215
+ const ratio = i / width;
216
+ if (ratio < 0.5) {
217
+ bar += chalk.green('█');
218
+ }
219
+ else if (ratio < 0.75) {
220
+ bar += chalk.yellow('█');
221
+ }
222
+ else {
223
+ bar += chalk.red('█');
224
+ }
225
+ }
226
+ else {
227
+ bar += chalk.gray('░');
228
+ }
229
+ }
230
+ bar += chalk.gray('╗');
231
+ const percentStr = showPercent ? ` ${percent.toFixed(0).padStart(3)}%` : '';
232
+ return `${bar}${percentStr}${message ? ` ${chalk.dim(message)}` : ''}`;
233
+ }
234
+ /**
235
+ * Create progress tracker
236
+ */
237
+ export function createProgress(total, taskMessage = 'Processing...') {
238
+ const spinner = ora(taskMessage).start();
239
+ return {
240
+ update: (current, message) => {
241
+ const percent = Math.round((current / total) * 100);
242
+ const progressBar = createProgressBar(percent);
243
+ spinner.text = `${progressBar} ${percent}%${message ? ` - ${message}` : ''}`;
244
+ },
245
+ finish: (message) => {
246
+ spinner.succeed(message || 'Complete');
247
+ },
248
+ };
249
+ }
250
+ /**
251
+ * Create ASCII progress bar
252
+ */
253
+ function createProgressBar(percent, width = 20) {
254
+ const filled = Math.round((percent / 100) * width);
255
+ let bar = '';
256
+ for (let i = 0; i < width; i++) {
257
+ const isGridline = i > 0 && i % (width / 5) === 0;
258
+ if (i < filled) {
259
+ bar += isGridline ? '│' : '█';
260
+ }
261
+ else {
262
+ bar += isGridline ? '┊' : ' ';
263
+ }
264
+ }
265
+ return `[${bar}]`;
266
+ }
267
+ /**
268
+ * Clear terminal
269
+ */
270
+ export function clearTerminal() {
271
+ if (process.stdout.isTTY) {
272
+ process.stdout.write('\x1B[2J\x1B[H');
273
+ }
274
+ }
275
+ /**
276
+ * Print table
277
+ */
278
+ export function printTable(headers, rows, columnWidths) {
279
+ const widths = columnWidths ||
280
+ headers.map((h, i) => {
281
+ const maxRow = Math.max(...rows.map((r) => (r[i] || '').length));
282
+ return Math.max(h.length, maxRow);
283
+ });
284
+ // Header
285
+ const headerRow = headers.map((h, i) => h.padEnd(widths[i])).join(' │ ');
286
+ console.log(chalk.bold(headerRow));
287
+ // Separator
288
+ const sep = widths.map((w) => '─'.repeat(w)).join('─┼─');
289
+ console.log(chalk.gray(sep));
290
+ // Rows
291
+ rows.forEach((row) => {
292
+ const line = row.map((cell, i) => cell.padEnd(widths[i])).join(' │ ');
293
+ console.log(line);
294
+ });
295
+ }
296
+ /**
297
+ * Show hide cursor
298
+ */
299
+ export function hideCursor() {
300
+ process.stdout.write('\x1B[?25l');
301
+ }
302
+ export function showCursor() {
303
+ process.stdout.write('\x1B[?25h');
304
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Interactive prompts for Broom CLI
3
+ */
4
+ import { select, confirm, checkbox, input } from '@inquirer/prompts';
5
+ import chalk from 'chalk';
6
+ import { formatSize } from '../utils/fs.js';
7
+ // Re-export inquirer functions for direct use
8
+ export { select, confirm, checkbox, input };
9
+ /**
10
+ * Select a category to clean
11
+ */
12
+ export async function selectCategory(results) {
13
+ const choices = results
14
+ .filter((r) => r.items.length > 0)
15
+ .map((r) => ({
16
+ name: `${r.category.name} (${formatSize(r.totalSize)} - ${r.items.length} items)`,
17
+ value: r,
18
+ }));
19
+ if (choices.length === 0) {
20
+ return null;
21
+ }
22
+ try {
23
+ return await select({
24
+ message: 'Select a category to clean:',
25
+ choices,
26
+ });
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /**
33
+ * Select multiple categories to clean
34
+ */
35
+ export async function selectCategories(results) {
36
+ const choices = results
37
+ .filter((r) => r.items.length > 0)
38
+ .map((r) => ({
39
+ name: `${r.category.name} (${formatSize(r.totalSize)} - ${r.items.length} items)`,
40
+ value: r,
41
+ checked: r.category.safetyLevel === 'safe',
42
+ }));
43
+ if (choices.length === 0) {
44
+ return [];
45
+ }
46
+ try {
47
+ return await checkbox({
48
+ message: 'Select categories to clean (space to toggle):',
49
+ choices,
50
+ });
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ /**
57
+ * Select files to clean
58
+ */
59
+ export async function selectFiles(items) {
60
+ if (items.length === 0) {
61
+ return [];
62
+ }
63
+ const choices = items.map((item) => ({
64
+ name: `${item.isDirectory ? '📁' : '📄'} ${item.name} (${formatSize(item.size)}) - ${chalk.dim(item.path)}`,
65
+ value: item,
66
+ checked: true,
67
+ }));
68
+ try {
69
+ return await checkbox({
70
+ message: 'Select files to remove:',
71
+ choices,
72
+ });
73
+ }
74
+ catch {
75
+ return [];
76
+ }
77
+ }
78
+ /**
79
+ * Select an application
80
+ */
81
+ export async function selectApp(apps) {
82
+ if (apps.length === 0) {
83
+ return null;
84
+ }
85
+ const choices = apps.map((app) => ({
86
+ name: `${app.name} (${formatSize(app.size)})${app.bundleId ? chalk.dim(` - ${app.bundleId}`) : ''}`,
87
+ value: app,
88
+ }));
89
+ try {
90
+ return await select({
91
+ message: 'Select an application:',
92
+ choices,
93
+ });
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ /**
100
+ * Confirm action
101
+ */
102
+ export async function confirmAction(message, defaultValue = false) {
103
+ try {
104
+ return await confirm({
105
+ message,
106
+ default: defaultValue,
107
+ });
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * Confirm removal
115
+ */
116
+ export async function confirmRemoval(itemCount, totalSize) {
117
+ try {
118
+ return await confirm({
119
+ message: `Remove ${itemCount} item(s) (${formatSize(totalSize)})?`,
120
+ default: false,
121
+ });
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Prompt for text input
129
+ */
130
+ export async function promptInput(message, defaultValue) {
131
+ try {
132
+ return await input({
133
+ message,
134
+ default: defaultValue,
135
+ });
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }
141
+ /**
142
+ * Text input prompt (alias for promptInput)
143
+ */
144
+ export async function inputPrompt(message, defaultValue) {
145
+ return promptInput(message, defaultValue);
146
+ }
147
+ /**
148
+ * Select from generic items
149
+ */
150
+ export async function selectItems(message, choices) {
151
+ if (choices.length === 0) {
152
+ return [];
153
+ }
154
+ try {
155
+ return await checkbox({
156
+ message,
157
+ choices: choices.map((c) => ({
158
+ name: c.description ? `${c.name} - ${chalk.dim(c.description)}` : c.name,
159
+ value: c.value,
160
+ })),
161
+ });
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ /**
168
+ * Select a path (directory browser)
169
+ */
170
+ export async function selectPath(message, choices) {
171
+ if (choices.length === 0) {
172
+ return null;
173
+ }
174
+ try {
175
+ return await select({
176
+ message,
177
+ choices,
178
+ });
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ }
184
+ /**
185
+ * Select main menu action
186
+ */
187
+ export async function selectMainAction() {
188
+ try {
189
+ return await select({
190
+ message: 'What would you like to do?',
191
+ choices: [
192
+ { name: '🧹 Clean - Deep system cleanup', value: 'clean' },
193
+ { name: '🗑️ Uninstall - Remove apps completely', value: 'uninstall' },
194
+ { name: '🔧 Optimize - Check and maintain system', value: 'optimize' },
195
+ { name: '📊 Analyze - Explore disk usage', value: 'analyze' },
196
+ { name: '📈 Status - Monitor system health', value: 'status' },
197
+ { name: '📦 Purge - Clean project artifacts', value: 'purge' },
198
+ { name: '💿 Installer - Remove installer files', value: 'installer' },
199
+ { name: '⚙️ Config - Manage settings', value: 'config' },
200
+ { name: '❌ Exit', value: 'exit' },
201
+ ],
202
+ });
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ /**
209
+ * Select config option
210
+ */
211
+ export async function selectConfigOption() {
212
+ try {
213
+ return await select({
214
+ message: 'Select a configuration option:',
215
+ choices: [
216
+ { name: 'Toggle scan locations', value: 'locations' },
217
+ { name: 'Manage whitelist', value: 'whitelist' },
218
+ { name: 'Toggle dry run mode', value: 'dryrun' },
219
+ { name: 'Reset to defaults', value: 'reset' },
220
+ { name: 'Exit', value: 'exit' },
221
+ ],
222
+ });
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
228
+ /**
229
+ * Select scan locations to toggle
230
+ */
231
+ export async function selectScanLocations(currentLocations) {
232
+ const choices = [
233
+ { name: 'User Cache', value: 'userCache', checked: currentLocations.userCache },
234
+ { name: 'System Cache', value: 'systemCache', checked: currentLocations.systemCache },
235
+ { name: 'System Logs', value: 'systemLogs', checked: currentLocations.systemLogs },
236
+ { name: 'User Logs', value: 'userLogs', checked: currentLocations.userLogs },
237
+ { name: 'Trash', value: 'trash', checked: currentLocations.trash },
238
+ { name: 'Downloads', value: 'downloads', checked: currentLocations.downloads },
239
+ { name: 'Browser Cache', value: 'browserCache', checked: currentLocations.browserCache },
240
+ { name: 'Development Cache', value: 'devCache', checked: currentLocations.devCache },
241
+ { name: 'Xcode Cache', value: 'xcodeCache', checked: currentLocations.xcodeCache },
242
+ ];
243
+ try {
244
+ return await checkbox({
245
+ message: 'Select locations to scan:',
246
+ choices,
247
+ });
248
+ }
249
+ catch {
250
+ return [];
251
+ }
252
+ }
253
+ /**
254
+ * Select cleanup action
255
+ */
256
+ export async function selectCleanupAction() {
257
+ try {
258
+ return await select({
259
+ message: 'What would you like to do?',
260
+ choices: [
261
+ { name: 'Remove selected files', value: 'remove' },
262
+ { name: 'Back to category selection', value: 'back' },
263
+ { name: 'Cancel', value: 'cancel' },
264
+ ],
265
+ });
266
+ }
267
+ catch {
268
+ return 'cancel';
269
+ }
270
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Configuration management for Broom CLI
3
+ */
4
+ import { readFile, writeFile, mkdir } from 'fs/promises';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { existsSync } from 'fs';
8
+ import { DEFAULT_CONFIG } from '../types/index.js';
9
+ const CONFIG_DIR = join(homedir(), '.config', 'broom');
10
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
11
+ const WHITELIST_FILE = join(CONFIG_DIR, 'whitelist');
12
+ let configCache = null;
13
+ /**
14
+ * Ensure config directory exists
15
+ */
16
+ async function ensureConfigDir() {
17
+ if (!existsSync(CONFIG_DIR)) {
18
+ await mkdir(CONFIG_DIR, { recursive: true });
19
+ }
20
+ }
21
+ /**
22
+ * Load configuration
23
+ */
24
+ export async function loadConfig() {
25
+ if (configCache) {
26
+ return configCache;
27
+ }
28
+ try {
29
+ await ensureConfigDir();
30
+ if (!existsSync(CONFIG_FILE)) {
31
+ await saveConfig(DEFAULT_CONFIG);
32
+ configCache = DEFAULT_CONFIG;
33
+ return DEFAULT_CONFIG;
34
+ }
35
+ const content = await readFile(CONFIG_FILE, 'utf-8');
36
+ const config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
37
+ // Load whitelist
38
+ if (existsSync(WHITELIST_FILE)) {
39
+ const whitelistContent = await readFile(WHITELIST_FILE, 'utf-8');
40
+ config.whitelist = whitelistContent
41
+ .split('\n')
42
+ .map((line) => line.trim())
43
+ .filter((line) => line && !line.startsWith('#'));
44
+ }
45
+ configCache = config;
46
+ return config;
47
+ }
48
+ catch (error) {
49
+ configCache = DEFAULT_CONFIG;
50
+ return DEFAULT_CONFIG;
51
+ }
52
+ }
53
+ /**
54
+ * Save configuration
55
+ */
56
+ export async function saveConfig(config) {
57
+ await ensureConfigDir();
58
+ const { whitelist, ...configWithoutWhitelist } = config;
59
+ await writeFile(CONFIG_FILE, JSON.stringify(configWithoutWhitelist, null, 2), 'utf-8');
60
+ // Save whitelist separately
61
+ if (whitelist && whitelist.length > 0) {
62
+ const whitelistContent = `# Broom Whitelist\n# One path per line\n\n${whitelist.join('\n')}`;
63
+ await writeFile(WHITELIST_FILE, whitelistContent, 'utf-8');
64
+ }
65
+ else {
66
+ // Delete whitelist file if empty
67
+ if (existsSync(WHITELIST_FILE)) {
68
+ const { unlink } = await import('fs/promises');
69
+ await unlink(WHITELIST_FILE);
70
+ }
71
+ }
72
+ configCache = config;
73
+ }
74
+ /**
75
+ * Reset configuration to defaults
76
+ */
77
+ export async function resetConfig() {
78
+ await saveConfig(DEFAULT_CONFIG);
79
+ configCache = DEFAULT_CONFIG;
80
+ }
81
+ /**
82
+ * Get config file path
83
+ */
84
+ export function getConfigPath() {
85
+ return CONFIG_FILE;
86
+ }
87
+ /**
88
+ * Get whitelist file path
89
+ */
90
+ export function getWhitelistPath() {
91
+ return WHITELIST_FILE;
92
+ }
93
+ /**
94
+ * Check if path is whitelisted
95
+ */
96
+ export function isWhitelisted(path, whitelist) {
97
+ const normalizedPath = path.replace(/\/+$/, '');
98
+ for (const pattern of whitelist) {
99
+ const normalizedPattern = pattern.replace(/\/+$/, '').replace('~', homedir());
100
+ if (normalizedPath === normalizedPattern) {
101
+ return true;
102
+ }
103
+ if (normalizedPath.startsWith(normalizedPattern + '/')) {
104
+ return true;
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Add path to whitelist
111
+ */
112
+ export async function addToWhitelist(path) {
113
+ const config = await loadConfig();
114
+ if (!config.whitelist.includes(path)) {
115
+ config.whitelist.push(path);
116
+ await saveConfig(config);
117
+ }
118
+ }
119
+ /**
120
+ * Remove path from whitelist
121
+ */
122
+ export async function removeFromWhitelist(path) {
123
+ const config = await loadConfig();
124
+ config.whitelist = config.whitelist.filter((p) => p !== path);
125
+ await saveConfig(config);
126
+ }
127
+ /**
128
+ * Clear config cache
129
+ */
130
+ export function clearConfigCache() {
131
+ configCache = null;
132
+ }
133
+ export { CONFIG_DIR, CONFIG_FILE, WHITELIST_FILE };