brain-cleaner 1.2.5 → 1.2.6
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.es.md +0 -9
- package/README.md +1 -7
- package/app.py +3 -3
- package/console/brain_cleaner_cli.py +34 -13
- package/package.json +1 -1
- package/scanner.py +26 -3
package/README.es.md
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
# Brain Cleaner
|
|
2
2
|
|
|
3
|
-

|
|
4
|
-
|
|
5
3
|
**Language / Idioma:**
|
|
6
4
|
🇪🇸 Español | [🇬🇧 English](https://github.com/konstantinWDK/brain-cleaner/blob/main/README.md)
|
|
7
5
|
|
|
8
|
-

|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
[](https://www.python.org/downloads/)
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
[](https://www.apple.com/macos/)
|
|
14
|
-
|
|
15
6
|
---
|
|
16
7
|
|
|
17
8
|
### 🚀 Instalación Recomendada (Global)
|
package/README.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# Brain Cleaner
|
|
2
2
|
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/brain-cleaner)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://www.python.org/downloads/)
|
|
8
|
-
|
|
9
3
|
**Language / Idioma:**
|
|
10
4
|
🇬🇧 English | [🇪🇸 Español](https://github.com/konstantinWDK/brain-cleaner/blob/main/README.es.md)
|
|
11
5
|
|
|
12
|
-
|
|
6
|
+
---
|
|
13
7
|
|
|
14
8
|
---
|
|
15
9
|
|
package/app.py
CHANGED
|
@@ -90,7 +90,7 @@ class BrainCleanerApp(ctk.CTk):
|
|
|
90
90
|
ctk.CTkLabel(title_f, text="Brain Cleaner",
|
|
91
91
|
font=ctk.CTkFont(size=19, weight="bold")
|
|
92
92
|
).pack(pady=(0, 2))
|
|
93
|
-
ctk.CTkLabel(title_f, text="v1.2.
|
|
93
|
+
ctk.CTkLabel(title_f, text="v1.2.6",
|
|
94
94
|
font=ctk.CTkFont(size=11, slant="italic"), text_color="#a1a1a1"
|
|
95
95
|
).pack()
|
|
96
96
|
|
|
@@ -538,8 +538,8 @@ class BrainCleanerApp(ctk.CTk):
|
|
|
538
538
|
self.update_bubble_selection("All")
|
|
539
539
|
|
|
540
540
|
def _render_rows(self, items, cat):
|
|
541
|
-
"""Render a list of (path, size_str, size_bytes) result items."""
|
|
542
|
-
for path, size_str, _ in items:
|
|
541
|
+
"""Render a list of (path, size_str, size_bytes, mtime) result items."""
|
|
542
|
+
for path, size_str, _, mtime in items:
|
|
543
543
|
var = ctk.BooleanVar(value=False)
|
|
544
544
|
|
|
545
545
|
wrapper = ctk.CTkFrame(self.results_frame, fg_color="transparent")
|
|
@@ -50,7 +50,7 @@ class BrainCleanerCLI:
|
|
|
50
50
|
def draw_splash(self):
|
|
51
51
|
content = []
|
|
52
52
|
content.append(self.term.cyan(ASCII_ART))
|
|
53
|
-
content.append(self.term.bold("\n Welcome to Brain Cleaner CLI v1.2.
|
|
53
|
+
content.append(self.term.bold("\n Welcome to Brain Cleaner CLI v1.2.6"))
|
|
54
54
|
content.append(" " + "-" * 40)
|
|
55
55
|
content.append("\n Select Mode to begin:")
|
|
56
56
|
content.append(self.term.blue(" [1] AI Tools Cleanup"))
|
|
@@ -100,13 +100,14 @@ class BrainCleanerCLI:
|
|
|
100
100
|
|
|
101
101
|
def _scan_worker(self, path):
|
|
102
102
|
try:
|
|
103
|
-
for cat, path, size_str, size_bytes in self.scanner.scan_stream(path, self.interrupt_event):
|
|
104
|
-
|
|
103
|
+
for cat, path, size_str, size_bytes, mtime in self.scanner.scan_stream(path, self.interrupt_event):
|
|
105
104
|
self.results.append({
|
|
106
105
|
'cat': cat,
|
|
107
106
|
'path': path,
|
|
108
107
|
'size_str': size_str,
|
|
109
108
|
'size_bytes': size_bytes,
|
|
109
|
+
'mtime': mtime,
|
|
110
|
+
'rel_time': self.scanner.format_relative_time(mtime),
|
|
110
111
|
'deleted': False,
|
|
111
112
|
'selected': False
|
|
112
113
|
})
|
|
@@ -155,11 +156,19 @@ class BrainCleanerCLI:
|
|
|
155
156
|
content.append(status_line)
|
|
156
157
|
content.append("-" * self.term.width)
|
|
157
158
|
|
|
158
|
-
# List Area
|
|
159
|
-
list_height = self.term.height - 7
|
|
159
|
+
# List Area Settings
|
|
160
160
|
visible_results = [r for r in self.results if not r['deleted']]
|
|
161
161
|
marked_count = len([r for r in visible_results if r['selected']])
|
|
162
162
|
|
|
163
|
+
# Summary Line
|
|
164
|
+
total_to_clean = sum(r['size_bytes'] for r in visible_results)
|
|
165
|
+
summary_text = f" {self.term.bold('TOTAL TO CLEAN:')} {self.term.yellow(self.scanner.format_size(total_to_clean))} | {self.term.bold('FOUND:')} {len(visible_results)} items"
|
|
166
|
+
content.append(summary_text)
|
|
167
|
+
content.append("-" * self.term.width)
|
|
168
|
+
|
|
169
|
+
# Draw List
|
|
170
|
+
list_height = self.term.height - 9
|
|
171
|
+
|
|
163
172
|
if not visible_results:
|
|
164
173
|
if not self.is_scanning:
|
|
165
174
|
content.append("\n" * (list_height // 2) + self.term.center("No items to display."))
|
|
@@ -187,15 +196,25 @@ class BrainCleanerCLI:
|
|
|
187
196
|
# Formatting
|
|
188
197
|
cursor_ptr = " > " if is_selected else " "
|
|
189
198
|
mark_ptr = "[*]" if is_marked else "[ ]"
|
|
190
|
-
|
|
199
|
+
rel_time = res.get('rel_time', '-')
|
|
200
|
+
|
|
201
|
+
# Layout Math
|
|
202
|
+
# [mark] [CATEGORY ] /path/to/folder 2d ago 10.5 MB
|
|
203
|
+
cat_str = f"[{res['cat']}]"
|
|
204
|
+
|
|
205
|
+
# Column widths
|
|
206
|
+
cat_w = 15
|
|
207
|
+
time_w = 8
|
|
208
|
+
size_w = 10
|
|
209
|
+
meta_w = time_w + size_w + 4 # Padding between meta columns
|
|
191
210
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if len(
|
|
195
|
-
|
|
211
|
+
path_available = self.term.width - (len(cursor_ptr) + len(mark_ptr) + cat_w + meta_w + 4)
|
|
212
|
+
path_text = res['path']
|
|
213
|
+
if len(path_text) > path_available:
|
|
214
|
+
path_text = "..." + path_text[-(path_available-3):]
|
|
196
215
|
|
|
197
|
-
line = f"{cursor_ptr}{
|
|
198
|
-
line
|
|
216
|
+
line = f"{cursor_ptr}{mark_ptr} {cat_str:<{cat_w}} {path_text:<{path_available}} "
|
|
217
|
+
line += f"{rel_time.rjust(time_w)} {self.term.bold(res['size_str'].rjust(size_w))}"
|
|
199
218
|
|
|
200
219
|
if is_selected:
|
|
201
220
|
content.append(self.term.reverse(line))
|
|
@@ -293,7 +312,7 @@ class BrainCleanerCLI:
|
|
|
293
312
|
if self.delete_all and not self.dry_run:
|
|
294
313
|
print(self.term.home + self.term.clear + f"[*] Starting automatic deletion in: {self.selected_path}")
|
|
295
314
|
count = 0
|
|
296
|
-
for cat, p, s_str, s_bytes in self.scanner.scan_stream(self.selected_path):
|
|
315
|
+
for cat, p, s_str, s_bytes, mtime in self.scanner.scan_stream(self.selected_path):
|
|
297
316
|
success, msg = self.scanner.delete_folder(p)
|
|
298
317
|
if success:
|
|
299
318
|
print(f" [OK] Deleted: {p} ({s_str})")
|
|
@@ -315,6 +334,8 @@ class BrainCleanerCLI:
|
|
|
315
334
|
return
|
|
316
335
|
|
|
317
336
|
elif state == "SCANNING":
|
|
337
|
+
visible_results = [r for r in self.results if not r['deleted']]
|
|
338
|
+
marked_count = len([r for r in visible_results if r['selected']])
|
|
318
339
|
self.draw()
|
|
319
340
|
val = self.term.inkey(timeout=0.1)
|
|
320
341
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brain-cleaner",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Professional CLI utility to reclaim disk space by removing residues from AI tools (Gemini, Claude, Cursor) and heavy node_modules folders. Optimized for system performance and digital decluttering.",
|
|
5
5
|
"main": "bin/brain-cleaner.js",
|
|
6
6
|
"bin": {
|
package/scanner.py
CHANGED
|
@@ -70,6 +70,24 @@ class BrainScanner:
|
|
|
70
70
|
s = round(size_bytes / p, 2)
|
|
71
71
|
return "%s %s" % (s, size_name[i])
|
|
72
72
|
|
|
73
|
+
def format_relative_time(self, mtime):
|
|
74
|
+
"""
|
|
75
|
+
Formats a timestamp into a relative string like '2h', '3d', '1y'.
|
|
76
|
+
"""
|
|
77
|
+
diff = time.time() - mtime
|
|
78
|
+
if diff < 60:
|
|
79
|
+
return "just now"
|
|
80
|
+
elif diff < 3600:
|
|
81
|
+
return f"{int(diff // 60)}m"
|
|
82
|
+
elif diff < 86400:
|
|
83
|
+
return f"{int(diff // 3600)}h"
|
|
84
|
+
elif diff < 2592000: # 30 days roughly
|
|
85
|
+
return f"{int(diff // 86400)}d"
|
|
86
|
+
elif diff < 31536000: # 365 days
|
|
87
|
+
return f"{int(diff // 2592000)}mo"
|
|
88
|
+
else:
|
|
89
|
+
return f"{int(diff // 31536000)}y"
|
|
90
|
+
|
|
73
91
|
def find_residues(self, start_path, interrupt_event=None):
|
|
74
92
|
"""
|
|
75
93
|
Scans and returns categorized results with sizes.
|
|
@@ -78,8 +96,8 @@ class BrainScanner:
|
|
|
78
96
|
found = {cat: [] for cat in self.categories.keys()}
|
|
79
97
|
found["All"] = []
|
|
80
98
|
|
|
81
|
-
for cat, path, size_str, size_bytes in self.scan_stream(start_path, interrupt_event):
|
|
82
|
-
item = (path, size_str, size_bytes)
|
|
99
|
+
for cat, path, size_str, size_bytes, mtime in self.scan_stream(start_path, interrupt_event):
|
|
100
|
+
item = (path, size_str, size_bytes, mtime)
|
|
83
101
|
if cat not in found:
|
|
84
102
|
found[cat] = []
|
|
85
103
|
found[cat].append(item)
|
|
@@ -126,10 +144,15 @@ class BrainScanner:
|
|
|
126
144
|
except Exception:
|
|
127
145
|
pass
|
|
128
146
|
|
|
147
|
+
try:
|
|
148
|
+
mtime = os.path.getmtime(full_path)
|
|
149
|
+
except Exception:
|
|
150
|
+
mtime = time.time()
|
|
151
|
+
|
|
129
152
|
size_bytes = self.get_dir_size(full_path)
|
|
130
153
|
size_str = self.format_size(size_bytes)
|
|
131
154
|
|
|
132
|
-
yield cat, full_path, size_str, size_bytes
|
|
155
|
+
yield cat, full_path, size_str, size_bytes, mtime
|
|
133
156
|
matched_in_this_root.append(d)
|
|
134
157
|
break
|
|
135
158
|
|