killpy 0.24.2__tar.gz → 0.25.1__tar.gz
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.
- {killpy-0.24.2/killpy.egg-info → killpy-0.25.1}/PKG-INFO +56 -14
- killpy-0.24.2/PKG-INFO → killpy-0.25.1/README.md +46 -33
- {killpy-0.24.2 → killpy-0.25.1}/killpy/__main__.py +24 -6
- {killpy-0.24.2 → killpy-0.25.1}/killpy/cleaner.py +55 -7
- killpy-0.25.1/killpy/cleaners/__init__.py +34 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/cli.py +31 -24
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/_utils.py +24 -0
- killpy-0.25.1/killpy/commands/clean.py +24 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/delete.py +12 -2
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/doctor.py +40 -43
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/list.py +2 -1
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/artifacts.py +10 -10
- killpy-0.25.1/killpy/detectors/base.py +101 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/cache.py +49 -23
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/conda.py +37 -13
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/hatch.py +21 -12
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/pipenv.py +9 -4
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/pipx.py +24 -9
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/poetry.py +20 -5
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/pyenv.py +15 -5
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/tox.py +3 -7
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/uv.py +4 -7
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/venv.py +11 -11
- killpy-0.25.1/killpy/files/__init__.py +35 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/intelligence/git_analyzer.py +12 -9
- {killpy-0.24.2 → killpy-0.25.1}/killpy/models.py +15 -3
- {killpy-0.24.2 → killpy-0.25.1}/killpy/scanner.py +5 -4
- killpy-0.24.2/README.md → killpy-0.25.1/killpy.egg-info/PKG-INFO +75 -12
- {killpy-0.24.2 → killpy-0.25.1}/pyproject.toml +26 -2
- killpy-0.24.2/killpy/cleaners/__init__.py +0 -15
- killpy-0.24.2/killpy/commands/clean.py +0 -15
- killpy-0.24.2/killpy/detectors/base.py +0 -53
- killpy-0.24.2/killpy/files/__init__.py +0 -23
- {killpy-0.24.2 → killpy-0.25.1}/killpy/__init__.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/__init__.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/find.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/commands/stats.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/detectors/__init__.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/intelligence/__init__.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/intelligence/scoring.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/intelligence/suggestions.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy/intelligence/tracker.py +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy.egg-info/SOURCES.txt +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy.egg-info/dependency_links.txt +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy.egg-info/entry_points.txt +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy.egg-info/requires.txt +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/killpy.egg-info/top_level.txt +0 -0
- {killpy-0.24.2 → killpy-0.25.1}/setup.cfg +0 -0
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: killpy
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.25.1
|
|
4
|
+
Summary: Find and delete unused Python environments (.venv, conda, poetry, pipenv, pipx, pyenv, uv) and caches to free disk space — from a fast terminal UI or a single CLI command.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Tlaloc-Es/killpy
|
|
6
6
|
Project-URL: Documentation, https://tlaloc-es.github.io/killpy/
|
|
7
7
|
Project-URL: Repository, https://github.com/Tlaloc-Es/killpy
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/Tlaloc-Es/killpy/issues
|
|
9
|
+
Keywords: venv,virtualenv,conda,poetry,pipenv,pipx,pyenv,uv,cleanup,disk-space,cache,cli,tui,textual,developer-tools
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
11
|
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Environment :: Console :: Curses
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
10
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
11
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
15
23
|
Requires-Python: >=3.10
|
|
16
24
|
Description-Content-Type: text/markdown
|
|
17
25
|
Requires-Dist: click>=8.1.8
|
|
@@ -44,7 +52,7 @@ uvx killpy --path ~
|
|
|
44
52
|
[](<https://twitter.com/intent/tweet?text=%F0%9F%90%8D%20KillPy%20helps%20you%20reclaim%20disk%20space%20by%20detecting%20unused%20Python%20environments%20(.venv,%20poetry%20env,%20conda%20env)%20and%20pipx%20packages.%20Clean,%20organize%20and%20free%20up%20space%20effortlessly!%20%F0%9F%9A%80&url=https://github.com/Tlaloc-Es/KillPy>)
|
|
45
53
|
[](https://github.com/vinta/awesome-python)
|
|
46
54
|
|
|
47
|
-

|
|
48
56
|
|
|
49
57
|
</div>
|
|
50
58
|
|
|
@@ -62,6 +70,7 @@ ______________________________________________________________________
|
|
|
62
70
|
- [CLI reference](#cli-reference)
|
|
63
71
|
- [`killpy` — launch TUI or headless delete](#killpy--launch-tui-or-headless-delete)
|
|
64
72
|
- [`killpy list` — inspect environments](#killpy-list--inspect-environments)
|
|
73
|
+
- [`killpy find` — locate environments with a package](#killpy-find--locate-environments-with-a-package)
|
|
65
74
|
- [`killpy delete` — remove environments](#killpy-delete--remove-environments)
|
|
66
75
|
- [`killpy stats` — disk usage summary](#killpy-stats--disk-usage-summary)
|
|
67
76
|
- [`killpy clean` — remove cache directories](#killpy-clean--remove-cache-directories)
|
|
@@ -115,9 +124,9 @@ uvx killpy --path ~
|
|
|
115
124
|
| `pyenv` | pyenv-managed Python versions | `~/.pyenv/versions` |
|
|
116
125
|
| `pipenv` | Pipenv virtualenvs | `~/.local/share/virtualenvs` |
|
|
117
126
|
| `hatch` | Hatch environments | `~/.local/share/hatch/env` |
|
|
118
|
-
| `uv` | uv
|
|
127
|
+
| `uv` | uv tool environments (`uv tool install`) and uv-managed Pythons | `~/.local/share/uv` |
|
|
119
128
|
| `tox` | tox environments | `.tox/` inside repo |
|
|
120
|
-
| `cache` | `__pycache__`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, global pip/uv caches |
|
|
129
|
+
| `cache` | `__pycache__`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, plus global pip/uv caches when the scanned path contains them | project tree, `~/.cache` |
|
|
121
130
|
| `artifacts` | `dist/`, `build/`, `.egg-info`, `.dist-info` | project root |
|
|
122
131
|
|
|
123
132
|
______________________________________________________________________
|
|
@@ -180,7 +189,7 @@ killpy --path ~ --exclude "company-projects"
|
|
|
180
189
|
|
|
181
190
|
The TUI starts immediately and streams results as each detector finishes — no waiting for a full scan before you can start browsing. Select items, mark them, and confirm; **nothing is deleted without explicit action**.
|
|
182
191
|
|
|
183
|
-
Environments flagged with `⚠️` are actively in use
|
|
192
|
+
Environments flagged with `⚠️` are actively in use (the environment killpy runs from, or the pyenv global version) — the TUI shows them but refuses to delete them.
|
|
184
193
|
|
|
185
194
|
### Keyboard shortcuts
|
|
186
195
|
|
|
@@ -231,6 +240,8 @@ Options:
|
|
|
231
240
|
-D, --delete-all Scan and delete ALL found environments without
|
|
232
241
|
launching the TUI
|
|
233
242
|
-y, --yes Skip confirmation prompt (use with --delete-all)
|
|
243
|
+
--force With --delete-all: also delete environments
|
|
244
|
+
currently in use (⚠ system-critical)
|
|
234
245
|
--help Show this message and exit.
|
|
235
246
|
```
|
|
236
247
|
|
|
@@ -248,6 +259,8 @@ ______________________________________________________________________
|
|
|
248
259
|
|
|
249
260
|
### `killpy list` — inspect environments
|
|
250
261
|
|
|
262
|
+

|
|
263
|
+
|
|
251
264
|
```bash
|
|
252
265
|
killpy list # list all detected environments
|
|
253
266
|
killpy list --path ~/projects # scan a specific path
|
|
@@ -260,6 +273,8 @@ killpy list --quiet # suppress progress output (scripts/CI
|
|
|
260
273
|
|
|
261
274
|
While scanning, `killpy list` shows a live progress indicator on **stderr** so you can see which detector is running. Stdout receives only the final output (table, JSON, or NDJSON), so pipes and redirections are never polluted. Use `--quiet` / `-q` to silence the progress indicator entirely.
|
|
262
275
|
|
|
276
|
+
The **Path** column mirrors the form of `--path`: a relative `--path` produces relative paths, an absolute one produces absolute paths (long paths are truncated to keep rows compact). The full absolute path is always available via `--json` / `--json-stream` in the `absolute_path` field.
|
|
277
|
+
|
|
263
278
|
`--json` example output:
|
|
264
279
|
|
|
265
280
|
```json
|
|
@@ -284,10 +299,31 @@ While scanning, `killpy list` shows a live progress indicator on **stderr** so y
|
|
|
284
299
|
killpy list --json-stream --path ~ | jq 'select(.type == "conda") | .size_human'
|
|
285
300
|
```
|
|
286
301
|
|
|
302
|
+

|
|
303
|
+
|
|
304
|
+
______________________________________________________________________
|
|
305
|
+
|
|
306
|
+
### `killpy find` — locate environments with a package
|
|
307
|
+
|
|
308
|
+

|
|
309
|
+
|
|
310
|
+
Find every environment that has a given package installed. `PACKAGE` accepts
|
|
311
|
+
standard PEP 508 / uv-style version specifiers.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
killpy find requests # any version of requests
|
|
315
|
+
killpy find "flask>=1.0" # version specifier
|
|
316
|
+
killpy find "numpy>=1.24,<2.0" # combined constraints
|
|
317
|
+
killpy find requests --type venv # restrict to a type
|
|
318
|
+
killpy find requests --json # machine-readable output
|
|
319
|
+
```
|
|
320
|
+
|
|
287
321
|
______________________________________________________________________
|
|
288
322
|
|
|
289
323
|
### `killpy delete` — remove environments
|
|
290
324
|
|
|
325
|
+

|
|
326
|
+
|
|
291
327
|
```bash
|
|
292
328
|
killpy delete # interactive confirmation before delete
|
|
293
329
|
killpy delete --yes # skip confirmation
|
|
@@ -295,6 +331,7 @@ killpy delete --dry-run # preview — nothing is deleted
|
|
|
295
331
|
killpy delete --type venv # only a specific type
|
|
296
332
|
killpy delete --type venv --type cache # multiple types
|
|
297
333
|
killpy delete --older-than 180 --yes # delete stale envs, no prompt
|
|
334
|
+
killpy delete --force # include in-use (⚠) environments
|
|
298
335
|
killpy delete --path ~/projects
|
|
299
336
|
```
|
|
300
337
|
|
|
@@ -302,6 +339,8 @@ ______________________________________________________________________
|
|
|
302
339
|
|
|
303
340
|
### `killpy stats` — disk usage summary
|
|
304
341
|
|
|
342
|
+

|
|
343
|
+
|
|
305
344
|
```bash
|
|
306
345
|
killpy stats
|
|
307
346
|
killpy stats --path ~/projects
|
|
@@ -329,6 +368,8 @@ ______________________________________________________________________
|
|
|
329
368
|
|
|
330
369
|
### `killpy clean` — remove cache directories
|
|
331
370
|
|
|
371
|
+

|
|
372
|
+
|
|
332
373
|
```bash
|
|
333
374
|
killpy clean
|
|
334
375
|
killpy clean --path ~/projects
|
|
@@ -351,6 +392,8 @@ Options:
|
|
|
351
392
|
--help Show this message and exit.
|
|
352
393
|
```
|
|
353
394
|
|
|
395
|
+

|
|
396
|
+
|
|
354
397
|
`doctor` analyses every detected virtual environment in two phases:
|
|
355
398
|
|
|
356
399
|
**Phase 1 — Scoring (for sorting only)**
|
|
@@ -373,10 +416,9 @@ Category is assigned deterministically by applying the following rules in order:
|
|
|
373
416
|
| Priority | Rule | Category |
|
|
374
417
|
|----------|------|----------|
|
|
375
418
|
| 1 | Orphan (`is_orphan=True`) **and** `age ≥ 180 days` | `HIGH` |
|
|
376
|
-
| 2 |
|
|
377
|
-
| 3 |
|
|
378
|
-
| 4 |
|
|
379
|
-
| 5 | Fallback | `LOW` |
|
|
419
|
+
| 2 | Active git repository **or** `age < 120 days` | `LOW` |
|
|
420
|
+
| 3 | `age ≥ 120 days` | `MEDIUM` |
|
|
421
|
+
| 4 | Fallback | `LOW` |
|
|
380
422
|
|
|
381
423
|
Age and orphan status are the dominant signals. Size does not affect the category.
|
|
382
424
|
|
|
@@ -532,7 +574,7 @@ Or press `P` in the TUI to clean them for the scanned path.
|
|
|
532
574
|
|
|
533
575
|
**What does ⚠️ mean next to an environment?**
|
|
534
576
|
|
|
535
|
-
The environment is currently in use
|
|
577
|
+
The environment is currently in use — it is the environment killpy itself runs from, or your pyenv global version. killpy shows it so you are aware of it, but refuses to delete it: the TUI always skips it, and `killpy delete` / `--delete-all` skip it unless you pass `--force`.
|
|
536
578
|
|
|
537
579
|
**Does it fail if Conda, pipx or pyenv are not installed?**
|
|
538
580
|
|
|
@@ -562,7 +604,7 @@ ______________________________________________________________________
|
|
|
562
604
|
|
|
563
605
|
```yaml
|
|
564
606
|
- repo: https://github.com/Tlaloc-Es/KillPy
|
|
565
|
-
rev: 0.
|
|
607
|
+
rev: 0.25.1
|
|
566
608
|
hooks:
|
|
567
609
|
- id: killpy # remove __pycache__ on every commit
|
|
568
610
|
- id: killpy-clean-caches # also removes .mypy_cache, .pytest_cache, .ruff_cache
|
|
@@ -587,7 +629,7 @@ Typical minimal setup (safe for daily use):
|
|
|
587
629
|
|
|
588
630
|
```yaml
|
|
589
631
|
- repo: https://github.com/Tlaloc-Es/KillPy
|
|
590
|
-
rev: 0.
|
|
632
|
+
rev: 0.25.1
|
|
591
633
|
hooks:
|
|
592
634
|
- id: killpy
|
|
593
635
|
```
|
|
@@ -598,7 +640,7 @@ ______________________________________________________________________
|
|
|
598
640
|
|
|
599
641
|
## Safety
|
|
600
642
|
|
|
601
|
-
`killpy` performs **destructive, irreversible** actions. Always review the selection before confirming removal. The `--dry-run` flag lets you preview everything safely. Environments marked `⚠️` are actively in use and
|
|
643
|
+
`killpy` performs **destructive, irreversible** actions. Always review the selection before confirming removal. The `--dry-run` flag lets you preview everything safely. Environments marked `⚠️` are actively in use and are skipped by every delete path unless you explicitly pass `--force`.
|
|
602
644
|
|
|
603
645
|
**You are responsible for files deleted on your system.**
|
|
604
646
|
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: killpy
|
|
3
|
-
Version: 0.24.2
|
|
4
|
-
Summary: List all .venv directories and Conda environments 🐍 on your system and check how much space they are using. You can then choose which ones to delete in order to free up space 🧹.
|
|
5
|
-
Project-URL: Homepage, https://github.com/Tlaloc-Es/killpy
|
|
6
|
-
Project-URL: Documentation, https://tlaloc-es.github.io/killpy/
|
|
7
|
-
Project-URL: Repository, https://github.com/Tlaloc-Es/killpy
|
|
8
|
-
Project-URL: Bug Tracker, https://github.com/Tlaloc-Es/killpy/issues
|
|
9
|
-
Classifier: Environment :: Console
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: click>=8.1.8
|
|
18
|
-
Requires-Dist: packaging>=24.0
|
|
19
|
-
Requires-Dist: rich>=13.9.4
|
|
20
|
-
Requires-Dist: textual>=1.0.0
|
|
21
|
-
|
|
22
1
|
<div align="center">
|
|
23
2
|
|
|
24
3
|

|
|
@@ -44,7 +23,7 @@ uvx killpy --path ~
|
|
|
44
23
|
[](<https://twitter.com/intent/tweet?text=%F0%9F%90%8D%20KillPy%20helps%20you%20reclaim%20disk%20space%20by%20detecting%20unused%20Python%20environments%20(.venv,%20poetry%20env,%20conda%20env)%20and%20pipx%20packages.%20Clean,%20organize%20and%20free%20up%20space%20effortlessly!%20%F0%9F%9A%80&url=https://github.com/Tlaloc-Es/KillPy>)
|
|
45
24
|
[](https://github.com/vinta/awesome-python)
|
|
46
25
|
|
|
47
|
-

|
|
48
27
|
|
|
49
28
|
</div>
|
|
50
29
|
|
|
@@ -62,6 +41,7 @@ ______________________________________________________________________
|
|
|
62
41
|
- [CLI reference](#cli-reference)
|
|
63
42
|
- [`killpy` — launch TUI or headless delete](#killpy--launch-tui-or-headless-delete)
|
|
64
43
|
- [`killpy list` — inspect environments](#killpy-list--inspect-environments)
|
|
44
|
+
- [`killpy find` — locate environments with a package](#killpy-find--locate-environments-with-a-package)
|
|
65
45
|
- [`killpy delete` — remove environments](#killpy-delete--remove-environments)
|
|
66
46
|
- [`killpy stats` — disk usage summary](#killpy-stats--disk-usage-summary)
|
|
67
47
|
- [`killpy clean` — remove cache directories](#killpy-clean--remove-cache-directories)
|
|
@@ -115,9 +95,9 @@ uvx killpy --path ~
|
|
|
115
95
|
| `pyenv` | pyenv-managed Python versions | `~/.pyenv/versions` |
|
|
116
96
|
| `pipenv` | Pipenv virtualenvs | `~/.local/share/virtualenvs` |
|
|
117
97
|
| `hatch` | Hatch environments | `~/.local/share/hatch/env` |
|
|
118
|
-
| `uv` | uv
|
|
98
|
+
| `uv` | uv tool environments (`uv tool install`) and uv-managed Pythons | `~/.local/share/uv` |
|
|
119
99
|
| `tox` | tox environments | `.tox/` inside repo |
|
|
120
|
-
| `cache` | `__pycache__`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, global pip/uv caches |
|
|
100
|
+
| `cache` | `__pycache__`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, plus global pip/uv caches when the scanned path contains them | project tree, `~/.cache` |
|
|
121
101
|
| `artifacts` | `dist/`, `build/`, `.egg-info`, `.dist-info` | project root |
|
|
122
102
|
|
|
123
103
|
______________________________________________________________________
|
|
@@ -180,7 +160,7 @@ killpy --path ~ --exclude "company-projects"
|
|
|
180
160
|
|
|
181
161
|
The TUI starts immediately and streams results as each detector finishes — no waiting for a full scan before you can start browsing. Select items, mark them, and confirm; **nothing is deleted without explicit action**.
|
|
182
162
|
|
|
183
|
-
Environments flagged with `⚠️` are actively in use
|
|
163
|
+
Environments flagged with `⚠️` are actively in use (the environment killpy runs from, or the pyenv global version) — the TUI shows them but refuses to delete them.
|
|
184
164
|
|
|
185
165
|
### Keyboard shortcuts
|
|
186
166
|
|
|
@@ -231,6 +211,8 @@ Options:
|
|
|
231
211
|
-D, --delete-all Scan and delete ALL found environments without
|
|
232
212
|
launching the TUI
|
|
233
213
|
-y, --yes Skip confirmation prompt (use with --delete-all)
|
|
214
|
+
--force With --delete-all: also delete environments
|
|
215
|
+
currently in use (⚠ system-critical)
|
|
234
216
|
--help Show this message and exit.
|
|
235
217
|
```
|
|
236
218
|
|
|
@@ -248,6 +230,8 @@ ______________________________________________________________________
|
|
|
248
230
|
|
|
249
231
|
### `killpy list` — inspect environments
|
|
250
232
|
|
|
233
|
+

|
|
234
|
+
|
|
251
235
|
```bash
|
|
252
236
|
killpy list # list all detected environments
|
|
253
237
|
killpy list --path ~/projects # scan a specific path
|
|
@@ -260,6 +244,8 @@ killpy list --quiet # suppress progress output (scripts/CI
|
|
|
260
244
|
|
|
261
245
|
While scanning, `killpy list` shows a live progress indicator on **stderr** so you can see which detector is running. Stdout receives only the final output (table, JSON, or NDJSON), so pipes and redirections are never polluted. Use `--quiet` / `-q` to silence the progress indicator entirely.
|
|
262
246
|
|
|
247
|
+
The **Path** column mirrors the form of `--path`: a relative `--path` produces relative paths, an absolute one produces absolute paths (long paths are truncated to keep rows compact). The full absolute path is always available via `--json` / `--json-stream` in the `absolute_path` field.
|
|
248
|
+
|
|
263
249
|
`--json` example output:
|
|
264
250
|
|
|
265
251
|
```json
|
|
@@ -284,10 +270,31 @@ While scanning, `killpy list` shows a live progress indicator on **stderr** so y
|
|
|
284
270
|
killpy list --json-stream --path ~ | jq 'select(.type == "conda") | .size_human'
|
|
285
271
|
```
|
|
286
272
|
|
|
273
|
+

|
|
274
|
+
|
|
275
|
+
______________________________________________________________________
|
|
276
|
+
|
|
277
|
+
### `killpy find` — locate environments with a package
|
|
278
|
+
|
|
279
|
+

|
|
280
|
+
|
|
281
|
+
Find every environment that has a given package installed. `PACKAGE` accepts
|
|
282
|
+
standard PEP 508 / uv-style version specifiers.
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
killpy find requests # any version of requests
|
|
286
|
+
killpy find "flask>=1.0" # version specifier
|
|
287
|
+
killpy find "numpy>=1.24,<2.0" # combined constraints
|
|
288
|
+
killpy find requests --type venv # restrict to a type
|
|
289
|
+
killpy find requests --json # machine-readable output
|
|
290
|
+
```
|
|
291
|
+
|
|
287
292
|
______________________________________________________________________
|
|
288
293
|
|
|
289
294
|
### `killpy delete` — remove environments
|
|
290
295
|
|
|
296
|
+

|
|
297
|
+
|
|
291
298
|
```bash
|
|
292
299
|
killpy delete # interactive confirmation before delete
|
|
293
300
|
killpy delete --yes # skip confirmation
|
|
@@ -295,6 +302,7 @@ killpy delete --dry-run # preview — nothing is deleted
|
|
|
295
302
|
killpy delete --type venv # only a specific type
|
|
296
303
|
killpy delete --type venv --type cache # multiple types
|
|
297
304
|
killpy delete --older-than 180 --yes # delete stale envs, no prompt
|
|
305
|
+
killpy delete --force # include in-use (⚠) environments
|
|
298
306
|
killpy delete --path ~/projects
|
|
299
307
|
```
|
|
300
308
|
|
|
@@ -302,6 +310,8 @@ ______________________________________________________________________
|
|
|
302
310
|
|
|
303
311
|
### `killpy stats` — disk usage summary
|
|
304
312
|
|
|
313
|
+

|
|
314
|
+
|
|
305
315
|
```bash
|
|
306
316
|
killpy stats
|
|
307
317
|
killpy stats --path ~/projects
|
|
@@ -329,6 +339,8 @@ ______________________________________________________________________
|
|
|
329
339
|
|
|
330
340
|
### `killpy clean` — remove cache directories
|
|
331
341
|
|
|
342
|
+

|
|
343
|
+
|
|
332
344
|
```bash
|
|
333
345
|
killpy clean
|
|
334
346
|
killpy clean --path ~/projects
|
|
@@ -351,6 +363,8 @@ Options:
|
|
|
351
363
|
--help Show this message and exit.
|
|
352
364
|
```
|
|
353
365
|
|
|
366
|
+

|
|
367
|
+
|
|
354
368
|
`doctor` analyses every detected virtual environment in two phases:
|
|
355
369
|
|
|
356
370
|
**Phase 1 — Scoring (for sorting only)**
|
|
@@ -373,10 +387,9 @@ Category is assigned deterministically by applying the following rules in order:
|
|
|
373
387
|
| Priority | Rule | Category |
|
|
374
388
|
|----------|------|----------|
|
|
375
389
|
| 1 | Orphan (`is_orphan=True`) **and** `age ≥ 180 days` | `HIGH` |
|
|
376
|
-
| 2 |
|
|
377
|
-
| 3 |
|
|
378
|
-
| 4 |
|
|
379
|
-
| 5 | Fallback | `LOW` |
|
|
390
|
+
| 2 | Active git repository **or** `age < 120 days` | `LOW` |
|
|
391
|
+
| 3 | `age ≥ 120 days` | `MEDIUM` |
|
|
392
|
+
| 4 | Fallback | `LOW` |
|
|
380
393
|
|
|
381
394
|
Age and orphan status are the dominant signals. Size does not affect the category.
|
|
382
395
|
|
|
@@ -532,7 +545,7 @@ Or press `P` in the TUI to clean them for the scanned path.
|
|
|
532
545
|
|
|
533
546
|
**What does ⚠️ mean next to an environment?**
|
|
534
547
|
|
|
535
|
-
The environment is currently in use
|
|
548
|
+
The environment is currently in use — it is the environment killpy itself runs from, or your pyenv global version. killpy shows it so you are aware of it, but refuses to delete it: the TUI always skips it, and `killpy delete` / `--delete-all` skip it unless you pass `--force`.
|
|
536
549
|
|
|
537
550
|
**Does it fail if Conda, pipx or pyenv are not installed?**
|
|
538
551
|
|
|
@@ -562,7 +575,7 @@ ______________________________________________________________________
|
|
|
562
575
|
|
|
563
576
|
```yaml
|
|
564
577
|
- repo: https://github.com/Tlaloc-Es/KillPy
|
|
565
|
-
rev: 0.
|
|
578
|
+
rev: 0.25.1
|
|
566
579
|
hooks:
|
|
567
580
|
- id: killpy # remove __pycache__ on every commit
|
|
568
581
|
- id: killpy-clean-caches # also removes .mypy_cache, .pytest_cache, .ruff_cache
|
|
@@ -587,7 +600,7 @@ Typical minimal setup (safe for daily use):
|
|
|
587
600
|
|
|
588
601
|
```yaml
|
|
589
602
|
- repo: https://github.com/Tlaloc-Es/KillPy
|
|
590
|
-
rev: 0.
|
|
603
|
+
rev: 0.25.1
|
|
591
604
|
hooks:
|
|
592
605
|
- id: killpy
|
|
593
606
|
```
|
|
@@ -598,7 +611,7 @@ ______________________________________________________________________
|
|
|
598
611
|
|
|
599
612
|
## Safety
|
|
600
613
|
|
|
601
|
-
`killpy` performs **destructive, irreversible** actions. Always review the selection before confirming removal. The `--dry-run` flag lets you preview everything safely. Environments marked `⚠️` are actively in use and
|
|
614
|
+
`killpy` performs **destructive, irreversible** actions. Always review the selection before confirming removal. The `--dry-run` flag lets you preview everything safely. Environments marked `⚠️` are actively in use and are skipped by every delete path unless you explicitly pass `--force`.
|
|
602
615
|
|
|
603
616
|
**You are responsible for files deleted on your system.**
|
|
604
617
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""killpy CLI entry point: the ``cli`` click group and its subcommands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
1
5
|
import logging
|
|
2
6
|
import sys
|
|
3
7
|
from pathlib import Path
|
|
@@ -9,7 +13,8 @@ from rich.prompt import Confirm
|
|
|
9
13
|
|
|
10
14
|
from killpy.cleaner import Cleaner, CleanerError
|
|
11
15
|
from killpy.cli import TableApp
|
|
12
|
-
from killpy.commands.
|
|
16
|
+
from killpy.commands._utils import partition_in_use
|
|
17
|
+
from killpy.commands.clean import clean_cmd
|
|
13
18
|
from killpy.commands.delete import delete_cmd
|
|
14
19
|
from killpy.commands.doctor import doctor_cmd
|
|
15
20
|
from killpy.commands.find import find_cmd
|
|
@@ -20,11 +25,13 @@ from killpy.intelligence.tracker import UsageTracker
|
|
|
20
25
|
from killpy.scanner import Scanner
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
def _run_delete_all(
|
|
28
|
+
def _run_delete_all(
|
|
29
|
+
path: Path, excluded: set[str], yes: bool, force: bool = False
|
|
30
|
+
) -> None:
|
|
24
31
|
"""Scan and delete all discovered environments without launching the TUI."""
|
|
25
32
|
console = Console()
|
|
26
33
|
scanner = Scanner(excluded=excluded)
|
|
27
|
-
cleaner = Cleaner()
|
|
34
|
+
cleaner = Cleaner(force=force)
|
|
28
35
|
|
|
29
36
|
with Progress(
|
|
30
37
|
SpinnerColumn(),
|
|
@@ -34,6 +41,8 @@ def _run_delete_all(path: Path, excluded: set[str], yes: bool) -> None:
|
|
|
34
41
|
):
|
|
35
42
|
envs = scanner.scan(path)
|
|
36
43
|
|
|
44
|
+
envs = partition_in_use(envs, force, console)
|
|
45
|
+
|
|
37
46
|
if not envs:
|
|
38
47
|
console.print("[yellow]No environments found.[/yellow]")
|
|
39
48
|
return
|
|
@@ -116,21 +125,30 @@ def _run_delete_all(path: Path, excluded: set[str], yes: bool) -> None:
|
|
|
116
125
|
default=False,
|
|
117
126
|
help="Skip the confirmation prompt (use together with --delete-all).",
|
|
118
127
|
)
|
|
128
|
+
@click.option(
|
|
129
|
+
"--force",
|
|
130
|
+
is_flag=True,
|
|
131
|
+
default=False,
|
|
132
|
+
help=(
|
|
133
|
+
"With --delete-all: also delete environments currently in use "
|
|
134
|
+
"(flagged system-critical)."
|
|
135
|
+
),
|
|
136
|
+
)
|
|
119
137
|
@click.pass_context
|
|
120
|
-
def cli(ctx, path: Path, exclude: str, delete_all: bool, yes: bool):
|
|
138
|
+
def cli(ctx, path: Path, exclude: str, delete_all: bool, yes: bool, force: bool):
|
|
121
139
|
logging.basicConfig(level=logging.WARNING)
|
|
122
140
|
excluded = (
|
|
123
141
|
{p.strip() for p in exclude.split(",") if p.strip()} if exclude else set()
|
|
124
142
|
)
|
|
125
143
|
if not ctx.invoked_subcommand:
|
|
126
144
|
if delete_all:
|
|
127
|
-
_run_delete_all(path, excluded, yes)
|
|
145
|
+
_run_delete_all(path, excluded, yes, force)
|
|
128
146
|
else:
|
|
129
147
|
app = TableApp(root_dir=path, excluded=excluded)
|
|
130
148
|
app.run()
|
|
131
149
|
|
|
132
150
|
|
|
133
|
-
cli.add_command(clean)
|
|
151
|
+
cli.add_command(clean_cmd, name="clean")
|
|
134
152
|
cli.add_command(list_cmd, name="list")
|
|
135
153
|
cli.add_command(delete_cmd, name="delete")
|
|
136
154
|
cli.add_command(stats_cmd, name="stats")
|
|
@@ -27,6 +27,10 @@ from killpy.models import Environment
|
|
|
27
27
|
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
|
|
30
|
+
# Minimum number of path components below the filesystem root for a
|
|
31
|
+
# deletion target: refuses e.g. /usr or /home, which are never environments.
|
|
32
|
+
_MIN_DEPTH_BELOW_ROOT = 2
|
|
33
|
+
|
|
30
34
|
|
|
31
35
|
class CleanerError(Exception):
|
|
32
36
|
"""Raised when a deletion operation fails."""
|
|
@@ -40,10 +44,15 @@ class Cleaner:
|
|
|
40
44
|
dry_run:
|
|
41
45
|
When ``True`` no destructive operations are performed; the method
|
|
42
46
|
still returns the would-be size so callers can show totals.
|
|
47
|
+
force:
|
|
48
|
+
When ``True``, allow deleting environments flagged
|
|
49
|
+
:attr:`~killpy.models.Environment.is_system_critical` (currently in
|
|
50
|
+
use). By default those are refused with a :class:`CleanerError`.
|
|
43
51
|
"""
|
|
44
52
|
|
|
45
|
-
def __init__(self, dry_run: bool = False) -> None:
|
|
53
|
+
def __init__(self, dry_run: bool = False, force: bool = False) -> None:
|
|
46
54
|
self.dry_run = dry_run
|
|
55
|
+
self.force = force
|
|
47
56
|
|
|
48
57
|
# ------------------------------------------------------------------ #
|
|
49
58
|
# Public API #
|
|
@@ -61,13 +70,22 @@ class Cleaner:
|
|
|
61
70
|
-------
|
|
62
71
|
int
|
|
63
72
|
Number of bytes freed (or that *would have been* freed in dry-run
|
|
64
|
-
mode).
|
|
73
|
+
mode). ``0`` when the path was already gone — e.g. it was
|
|
74
|
+
removed together with a previously deleted parent environment —
|
|
75
|
+
so callers don't over-report freed space.
|
|
65
76
|
|
|
66
77
|
Raises
|
|
67
78
|
------
|
|
68
79
|
CleanerError
|
|
69
|
-
If the underlying removal command fails
|
|
80
|
+
If the underlying removal command fails, or when *env* is
|
|
81
|
+
system-critical and :attr:`force` is ``False``.
|
|
70
82
|
"""
|
|
83
|
+
if env.is_system_critical and not self.force:
|
|
84
|
+
raise CleanerError(
|
|
85
|
+
f"Refusing to delete {env.path}: environment is currently in "
|
|
86
|
+
"use (system-critical). Pass --force to delete it anyway."
|
|
87
|
+
)
|
|
88
|
+
|
|
71
89
|
if self.dry_run:
|
|
72
90
|
logger.info("[dry-run] Would delete %s (%s)", env.path, env.size_human)
|
|
73
91
|
return env.size_bytes
|
|
@@ -79,8 +97,8 @@ class Cleaner:
|
|
|
79
97
|
self._remove_pipx(env.name)
|
|
80
98
|
elif env.managed_by == "uv":
|
|
81
99
|
self._remove_uv_tool(env.name)
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
elif not self._remove_filesystem(env.path):
|
|
101
|
+
return 0
|
|
84
102
|
except CleanerError:
|
|
85
103
|
raise
|
|
86
104
|
except Exception as exc: # noqa: BLE001
|
|
@@ -128,11 +146,41 @@ class Cleaner:
|
|
|
128
146
|
# ------------------------------------------------------------------ #
|
|
129
147
|
|
|
130
148
|
@staticmethod
|
|
131
|
-
def
|
|
149
|
+
def _ensure_sane_deletion_target(path: Path) -> None:
|
|
150
|
+
"""Refuse obviously wrong deletion targets (defense in depth).
|
|
151
|
+
|
|
152
|
+
The scanner should never produce these, but a bug upstream — or a
|
|
153
|
+
directory swapped underneath us between scan and delete — must not
|
|
154
|
+
translate into wiping the filesystem root, the user's home, or a
|
|
155
|
+
symlink's target tree. These checks are unconditional; ``force``
|
|
156
|
+
does not bypass them.
|
|
157
|
+
"""
|
|
158
|
+
if path.is_symlink():
|
|
159
|
+
raise CleanerError(
|
|
160
|
+
f"Refusing to delete {path}: it is a symlink (the "
|
|
161
|
+
"environment may have been replaced since it was scanned)"
|
|
162
|
+
)
|
|
163
|
+
try:
|
|
164
|
+
resolved = path.resolve()
|
|
165
|
+
except OSError as exc:
|
|
166
|
+
raise CleanerError(f"Cannot resolve {path}: {exc}") from exc
|
|
167
|
+
anchor = Path(resolved.anchor)
|
|
168
|
+
if resolved == anchor:
|
|
169
|
+
raise CleanerError(f"Refusing to delete the filesystem root: {resolved}")
|
|
170
|
+
if resolved == Path.home():
|
|
171
|
+
raise CleanerError(f"Refusing to delete the home directory: {resolved}")
|
|
172
|
+
if len(resolved.parts) - len(anchor.parts) < _MIN_DEPTH_BELOW_ROOT:
|
|
173
|
+
raise CleanerError(f"Refusing to delete top-level directory: {resolved}")
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _remove_filesystem(path: Path) -> bool:
|
|
177
|
+
"""Remove *path* recursively; return False when it no longer exists."""
|
|
132
178
|
if not path.exists():
|
|
133
179
|
logger.warning("Path no longer exists: %s", path)
|
|
134
|
-
return
|
|
180
|
+
return False
|
|
181
|
+
Cleaner._ensure_sane_deletion_target(path)
|
|
135
182
|
shutil.rmtree(path)
|
|
183
|
+
return True
|
|
136
184
|
|
|
137
185
|
@staticmethod
|
|
138
186
|
def _remove_conda(env_name: str) -> None:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Stateless ``__pycache__`` removal helper used by ``killpy clean``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from killpy.files import get_total_size
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def remove_pycache(path: Path) -> int:
|
|
13
|
+
"""Remove every ``__pycache__`` directory under *path*.
|
|
14
|
+
|
|
15
|
+
The walk never follows symlinks, so a link placed inside the tree
|
|
16
|
+
cannot steer the deletion outside the scanned root. Failed removals
|
|
17
|
+
are skipped and not counted as freed space.
|
|
18
|
+
"""
|
|
19
|
+
total_freed_space = 0
|
|
20
|
+
for current_root, directories, _files in os.walk(path, topdown=True):
|
|
21
|
+
if "__pycache__" not in directories:
|
|
22
|
+
continue
|
|
23
|
+
# Prune it from the walk: it is deleted below, not descended into.
|
|
24
|
+
directories.remove("__pycache__")
|
|
25
|
+
pycache_dir = Path(current_root) / "__pycache__"
|
|
26
|
+
if pycache_dir.is_symlink():
|
|
27
|
+
continue
|
|
28
|
+
try:
|
|
29
|
+
size = get_total_size(pycache_dir)
|
|
30
|
+
shutil.rmtree(pycache_dir)
|
|
31
|
+
except OSError:
|
|
32
|
+
continue
|
|
33
|
+
total_freed_space += size
|
|
34
|
+
return total_freed_space
|