brain-cleaner 1.2.4 → 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 CHANGED
@@ -1,17 +1,8 @@
1
1
  # Brain Cleaner
2
2
 
3
- ![Brain Cleaner Banner](https://braincleaner.dev/og-image.png)
4
-
5
3
  **Language / Idioma:**
6
4
  🇪🇸 Español  |  [🇬🇧 English](https://github.com/konstantinWDK/brain-cleaner/blob/main/README.md)
7
5
 
8
- ![Brain Cleaner UI Mockup](https://braincleaner.dev/mockup.png)
9
-
10
- ---
11
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
12
- [![Licencia: MIT](https://img.shields.io/badge/Licencia-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
- [![Plataforma macOS](https://img.shields.io/badge/plataforma-macOS-lightgrey.svg)](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
- ![Brain Cleaner Banner](https://braincleaner.dev/og-image.png)
4
-
5
- [![NPM Version](https://img.shields.io/npm/v/brain-cleaner.svg)](https://www.npmjs.com/package/brain-cleaner)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](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
- ![Brain Cleaner UI Mockup](https://braincleaner.dev/mockup.png)
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.3",
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.3"))
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
- item_text = f"{mark_ptr} [{res['cat']:<12}] {res['path']}"
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
- # Trim path if too long
193
- available_width = self.term.width - 24
194
- if len(item_text) > available_width:
195
- item_text = item_text[:available_width-3] + "..."
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}{item_text}"
198
- line = line.ljust(self.term.width - 12) + self.term.bold(res['size_str'].rjust(10))
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.4",
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