fixos 2.2.39__tar.gz → 2.2.41__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.
Files changed (178) hide show
  1. {fixos-2.2.39 → fixos-2.2.41}/CHANGELOG.md +68 -0
  2. {fixos-2.2.39 → fixos-2.2.41}/PKG-INFO +24 -11
  3. {fixos-2.2.39 → fixos-2.2.41}/README.md +23 -10
  4. {fixos-2.2.39 → fixos-2.2.41}/fixos/__init__.py +1 -1
  5. fixos-2.2.41/fixos/__main__.py +7 -0
  6. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/cleanup_cmd.py +209 -35
  7. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/quick_snapshot.py +5 -1
  8. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/service_cleanup.py +117 -38
  9. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/service_scanner.py +19 -7
  10. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/PKG-INFO +24 -11
  11. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/SOURCES.txt +2 -0
  12. {fixos-2.2.39 → fixos-2.2.41}/pyproject.toml +1 -1
  13. {fixos-2.2.39 → fixos-2.2.41}/setup.py +1 -1
  14. fixos-2.2.41/tests/unit/test_cleanup_cmd.py +194 -0
  15. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_service_cleanup.py +157 -6
  16. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_service_scanner.py +25 -0
  17. {fixos-2.2.39 → fixos-2.2.41}/.env.example +0 -0
  18. {fixos-2.2.39 → fixos-2.2.41}/LICENSE +0 -0
  19. {fixos-2.2.39 → fixos-2.2.41}/MANIFEST.in +0 -0
  20. {fixos-2.2.39 → fixos-2.2.41}/docker/README.md +0 -0
  21. {fixos-2.2.39 → fixos-2.2.41}/docker/TEST_RESULTS.md +0 -0
  22. {fixos-2.2.39 → fixos-2.2.41}/docker/TEST_RESULTS_V2.md +0 -0
  23. {fixos-2.2.39 → fixos-2.2.41}/docker/alpine/Dockerfile +0 -0
  24. {fixos-2.2.39 → fixos-2.2.41}/docker/arch/Dockerfile +0 -0
  25. {fixos-2.2.39 → fixos-2.2.41}/docker/base/Dockerfile +0 -0
  26. {fixos-2.2.39 → fixos-2.2.41}/docker/broken-audio/Dockerfile +0 -0
  27. {fixos-2.2.39 → fixos-2.2.41}/docker/broken-full/Dockerfile +0 -0
  28. {fixos-2.2.39 → fixos-2.2.41}/docker/broken-network/Dockerfile +0 -0
  29. {fixos-2.2.39 → fixos-2.2.41}/docker/broken-thumbnails/Dockerfile +0 -0
  30. {fixos-2.2.39 → fixos-2.2.41}/docker/debian/Dockerfile +0 -0
  31. {fixos-2.2.39 → fixos-2.2.41}/docker/docker-compose.multi-system.yml +0 -0
  32. {fixos-2.2.39 → fixos-2.2.41}/docker/docker-compose.yml +0 -0
  33. {fixos-2.2.39 → fixos-2.2.41}/docker/fedora/Dockerfile +0 -0
  34. {fixos-2.2.39 → fixos-2.2.41}/docker/test-multi-system.sh +0 -0
  35. {fixos-2.2.39 → fixos-2.2.41}/docker/test-scenarios.sh +0 -0
  36. {fixos-2.2.39 → fixos-2.2.41}/docker/ubuntu/Dockerfile +0 -0
  37. {fixos-2.2.39 → fixos-2.2.41}/docker/validate-scenario.py +0 -0
  38. {fixos-2.2.39 → fixos-2.2.41}/docs/examples/advanced_usage.py +0 -0
  39. {fixos-2.2.39 → fixos-2.2.41}/docs/examples/quickstart.py +0 -0
  40. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/__init__.py +0 -0
  41. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/autonomous.py +0 -0
  42. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/autonomous_session.py +0 -0
  43. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/hitl.py +0 -0
  44. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/hitl_session.py +0 -0
  45. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/session_core.py +0 -0
  46. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/session_handlers.py +0 -0
  47. {fixos-2.2.39 → fixos-2.2.41}/fixos/agent/session_io.py +0 -0
  48. {fixos-2.2.39 → fixos-2.2.41}/fixos/anonymizer.py +0 -0
  49. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/__init__.py +0 -0
  50. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/_cleanup_flatpak.py +0 -0
  51. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/_cleanup_home.py +0 -0
  52. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/_cleanup_snap.py +0 -0
  53. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/_cleanup_system.py +0 -0
  54. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/_cleanup_utils.py +0 -0
  55. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/ask_cmd.py +0 -0
  56. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/config_cmd.py +0 -0
  57. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/features_cmd.py +0 -0
  58. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/fix_cmd.py +0 -0
  59. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/history_cmd.py +0 -0
  60. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/main.py +0 -0
  61. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/orchestrate_cmd.py +0 -0
  62. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/output_formatter.py +0 -0
  63. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/profile_cmd.py +0 -0
  64. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/projects_cmd.py +0 -0
  65. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/provider_cmd.py +0 -0
  66. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/quick_cmd.py +0 -0
  67. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/quickfix_cmd.py +0 -0
  68. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/report_cmd.py +0 -0
  69. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/rollback_cmd.py +0 -0
  70. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/scan_cmd.py +0 -0
  71. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/shared.py +0 -0
  72. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/token_cmd.py +0 -0
  73. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli/watch_cmd.py +0 -0
  74. {fixos-2.2.39 → fixos-2.2.41}/fixos/cli.py +0 -0
  75. {fixos-2.2.39 → fixos-2.2.41}/fixos/config.py +0 -0
  76. {fixos-2.2.39 → fixos-2.2.41}/fixos/config_interactive.py +0 -0
  77. {fixos-2.2.39 → fixos-2.2.41}/fixos/constants.py +0 -0
  78. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/__init__.py +0 -0
  79. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_flatpak_analysis_mixin.py +0 -0
  80. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_flatpak_execution_mixin.py +0 -0
  81. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_flatpak_recommendations_mixin.py +0 -0
  82. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_storage_container_mixin.py +0 -0
  83. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_storage_system_mixin.py +0 -0
  84. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/_storage_user_mixin.py +0 -0
  85. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/cache_discovery.py +0 -0
  86. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/__init__.py +0 -0
  87. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/_shared.py +0 -0
  88. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/audio.py +0 -0
  89. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/file_analysis.py +0 -0
  90. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/hardware.py +0 -0
  91. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/packages.py +0 -0
  92. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/resources.py +0 -0
  93. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/security.py +0 -0
  94. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/storage_optimization.py +0 -0
  95. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/system_core.py +0 -0
  96. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/checks/thumbnails.py +0 -0
  97. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  98. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/disk_analyzer.py +0 -0
  99. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  100. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/project_scanner.py +0 -0
  101. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/service_details.py +0 -0
  102. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/storage_analyzer.py +0 -0
  103. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/system_checks.py +0 -0
  104. {fixos-2.2.39 → fixos-2.2.41}/fixos/diagnostics/utils.py +0 -0
  105. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/__init__.py +0 -0
  106. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/auditor.py +0 -0
  107. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/catalog.py +0 -0
  108. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/installer.py +0 -0
  109. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/profiles.py +0 -0
  110. {fixos-2.2.39 → fixos-2.2.41}/fixos/features/renderer.py +0 -0
  111. {fixos-2.2.39 → fixos-2.2.41}/fixos/fixes/__init__.py +0 -0
  112. {fixos-2.2.39 → fixos-2.2.41}/fixos/interactive/__init__.py +0 -0
  113. {fixos-2.2.39 → fixos-2.2.41}/fixos/interactive/cleanup_planner.py +0 -0
  114. {fixos-2.2.39 → fixos-2.2.41}/fixos/llm_shell.py +0 -0
  115. {fixos-2.2.39 → fixos-2.2.41}/fixos/orchestrator/__init__.py +0 -0
  116. {fixos-2.2.39 → fixos-2.2.41}/fixos/orchestrator/executor.py +0 -0
  117. {fixos-2.2.39 → fixos-2.2.41}/fixos/orchestrator/graph.py +0 -0
  118. {fixos-2.2.39 → fixos-2.2.41}/fixos/orchestrator/orchestrator.py +0 -0
  119. {fixos-2.2.39 → fixos-2.2.41}/fixos/orchestrator/rollback.py +0 -0
  120. {fixos-2.2.39 → fixos-2.2.41}/fixos/platform_utils.py +0 -0
  121. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/__init__.py +0 -0
  122. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/base.py +0 -0
  123. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/__init__.py +0 -0
  124. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/audio.py +0 -0
  125. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/disk.py +0 -0
  126. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/hardware.py +0 -0
  127. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/resources.py +0 -0
  128. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/security.py +0 -0
  129. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/builtin/thumbnails.py +0 -0
  130. {fixos-2.2.39 → fixos-2.2.41}/fixos/plugins/registry.py +0 -0
  131. {fixos-2.2.39 → fixos-2.2.41}/fixos/profiles/__init__.py +0 -0
  132. {fixos-2.2.39 → fixos-2.2.41}/fixos/providers/__init__.py +0 -0
  133. {fixos-2.2.39 → fixos-2.2.41}/fixos/providers/llm.py +0 -0
  134. {fixos-2.2.39 → fixos-2.2.41}/fixos/providers/llm_analyzer.py +0 -0
  135. {fixos-2.2.39 → fixos-2.2.41}/fixos/providers/schemas.py +0 -0
  136. {fixos-2.2.39 → fixos-2.2.41}/fixos/system_checks.py +0 -0
  137. {fixos-2.2.39 → fixos-2.2.41}/fixos/utils/__init__.py +0 -0
  138. {fixos-2.2.39 → fixos-2.2.41}/fixos/utils/anonymizer.py +0 -0
  139. {fixos-2.2.39 → fixos-2.2.41}/fixos/utils/terminal.py +0 -0
  140. {fixos-2.2.39 → fixos-2.2.41}/fixos/utils/timeout.py +0 -0
  141. {fixos-2.2.39 → fixos-2.2.41}/fixos/utils/web_search.py +0 -0
  142. {fixos-2.2.39 → fixos-2.2.41}/fixos/watch.py +0 -0
  143. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/dependency_links.txt +0 -0
  144. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/entry_points.txt +0 -0
  145. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/requires.txt +0 -0
  146. {fixos-2.2.39 → fixos-2.2.41}/fixos.egg-info/top_level.txt +0 -0
  147. {fixos-2.2.39 → fixos-2.2.41}/pytest.ini +0 -0
  148. {fixos-2.2.39 → fixos-2.2.41}/requirements-dev.txt +0 -0
  149. {fixos-2.2.39 → fixos-2.2.41}/requirements.txt +0 -0
  150. {fixos-2.2.39 → fixos-2.2.41}/scripts/pyqual-calibrate.py +0 -0
  151. {fixos-2.2.39 → fixos-2.2.41}/setup.cfg +0 -0
  152. {fixos-2.2.39 → fixos-2.2.41}/tests/__init__.py +0 -0
  153. {fixos-2.2.39 → fixos-2.2.41}/tests/conftest.py +0 -0
  154. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/__init__.py +0 -0
  155. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_anonymization_layers.py +0 -0
  156. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_audio_broken.py +0 -0
  157. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_cli.py +0 -0
  158. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_diagnostics_integration.py +0 -0
  159. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_executor.py +0 -0
  160. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_multi_system.py +0 -0
  161. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_network_broken.py +0 -0
  162. {fixos-2.2.39 → fixos-2.2.41}/tests/e2e/test_thumbnails_broken.py +0 -0
  163. {fixos-2.2.39 → fixos-2.2.41}/tests/test_fixos.py +0 -0
  164. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/__init__.py +0 -0
  165. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_anonymizer.py +0 -0
  166. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_cache_discovery.py +0 -0
  167. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_core.py +0 -0
  168. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_dev_project_analyzer.py +0 -0
  169. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_executor.py +0 -0
  170. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_llm_provider.py +0 -0
  171. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_orchestrator.py +0 -0
  172. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_project_scanner.py +0 -0
  173. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_projects_cmd.py +0 -0
  174. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_quick_cmd.py +0 -0
  175. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_quick_snapshot.py +0 -0
  176. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_resources_quick.py +0 -0
  177. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_security_quick.py +0 -0
  178. {fixos-2.2.39 → fixos-2.2.41}/tests/unit/test_system_checks_parallel.py +0 -0
@@ -8,9 +8,41 @@
8
8
  dnia oraz alarmami o szybkim wzroście dysku, RAM, swapu lub obciążenia CPU.
9
9
  - Rozpoznawanie systemu, profilu użytkownika i stosu technologicznego oraz
10
10
  opcjonalne przejście do wolniejszej analizy przez `fixos quick --deep`.
11
+ - Uporządkowana dokumentacja użytkownika: szybki start, model bezpieczeństwa
12
+ cleanup, aktualna architektura i rozdzielenie aktywnego TODO od archiwalnych
13
+ artykułów.
14
+
15
+ ### Changed
16
+
17
+ - Zakończone elementy historycznych roadmap zostały przeniesione z TODO do
18
+ changelogu: modułowy pakiet CLI, klasowe sesje HITL, wspólny
19
+ `SessionTimeout`, plugin registry, structured output LLM, rollback, profile
20
+ diagnostyczne i watch mode.
21
+ - `TODO.md` zawiera teraz wyłącznie ręcznie zweryfikowane, aktualne zadania;
22
+ usunięto nieaktualny raport statyczny z 311 wpisami opartymi na dawnych
23
+ numerach linii i fałszywych alarmach.
11
24
 
12
25
  ### Fixed
13
26
 
27
+ - Zablokowano zbiorcze kasowanie chronionych danych LM Studio, Hugging Face,
28
+ Podman/containerd, Jupyter, Minikube, AppImage, VirtualBox, VMware oraz
29
+ rozszerzeń VS Code/Cursor. Takie wpisy pokazują teraz wyłącznie bezpieczny
30
+ podgląd i wymagają ręcznego wskazania konkretnego elementu poza fixOS.
31
+ - Doprecyzowano estymatę Dockera: wartość `RECLAIMABLE` jest informacyjna,
32
+ natomiast ograniczony filtr cache buildów starszych niż 7 dni może zwolnić
33
+ znacznie mniej miejsca.
34
+ - Tryb `fixos cleanup` → „Wybierz pojedyncze” przechodzi teraz kolejno przez
35
+ wszystkie usługi: bezpieczne, wymagające przeglądu i chronione. Pozycje bez
36
+ bezpiecznej operacji zbiorczej pokazują komendę podglądu, dane chronione
37
+ wymagają dodatkowego potwierdzenia, a wykonanie jest przypięte do dokładnie
38
+ wybranej ścieżki i poziomu ryzyka.
39
+ - Skany cache npm, pnpm, Yarn, Maven i uv obejmują teraz wyłącznie
40
+ odtwarzalne katalogi. `~/.local/share/uv` (narzędzia, interpretery i
41
+ credentials) oraz `.tools` pnpm nie są już błędnie liczone jako bezpieczne
42
+ cache; czyszczenie npm faktycznie obejmuje raportowane `_npx`.
43
+ - Naprawiono scenariusze TestQL i dodano punkt wejścia `python -m fixos`.
44
+ `cleanup -c <chroniona-usługa> --dry-run` pokazuje bezpieczną komendę
45
+ diagnostyczną zamiast kończyć się mylącym błędem.
14
46
  - Stabilny raport Docker z osobnym rozmiarem aktywnym i odzyskiwalnym oraz
15
47
  poprawnym pomiarem przed/po czyszczeniu.
16
48
  - Usunięto niebezpieczne zbiorcze komendy kasowania wolumenów Docker i
@@ -19,6 +51,42 @@
19
51
  rekurencyjnych skanów `/` oraz `/home` z szybkich kontroli.
20
52
  - Czyszczenie cache JetBrains trafia w aktualną ścieżkę i wymaga zamknięcia IDE.
21
53
 
54
+ ## [2.2.41] - 2026-07-23
55
+
56
+ ### Docs
57
+ - Update CHANGELOG.md
58
+ - Update README.md
59
+
60
+ ### Test
61
+ - Update tests/unit/test_cleanup_cmd.py
62
+ - Update tests/unit/test_service_cleanup.py
63
+ - Update tests/unit/test_service_scanner.py
64
+
65
+ ### Other
66
+ - Update fixos/cli/cleanup_cmd.py
67
+ - Update fixos/diagnostics/service_cleanup.py
68
+
69
+ ## [2.2.40] - 2026-07-23
70
+
71
+ ### Docs
72
+ - Update CHANGELOG.md
73
+ - Update README.md
74
+
75
+ ### Test
76
+ - Update testql-scenarios/generated-cli-tests.testql.toon.yaml
77
+ - Update testql-scenarios/generated-from-pytests.testql.toon.yaml
78
+ - Update tests/unit/test_cleanup_cmd.py
79
+ - Update tests/unit/test_service_cleanup.py
80
+ - Update tests/unit/test_service_scanner.py
81
+
82
+ ### Other
83
+ - Update fixos/__main__.py
84
+ - Update fixos/cli/cleanup_cmd.py
85
+ - Update fixos/diagnostics/quick_snapshot.py
86
+ - Update fixos/diagnostics/service_cleanup.py
87
+ - Update fixos/diagnostics/service_scanner.py
88
+ - Update uv.lock
89
+
22
90
  ## [2.2.39] - 2026-07-23
23
91
 
24
92
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.39
3
+ Version: 2.2.41
4
4
  Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
5
5
  Home-page: https://github.com/wronai/fixos
6
6
  Author: fixos contributors
@@ -63,11 +63,11 @@ AI-powered OS Diagnostics
63
63
 
64
64
  ## AI Cost Tracking
65
65
 
66
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.39-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$4.76-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-38.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.41-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$4.87-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-39.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $4.7645 (146 commits)
70
- - 👤 **Human dev:** ~$3795 (38.0h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $4.8653 (149 commits)
70
+ - 👤 **Human dev:** ~$3913 (39.1h @ $100/h, 30min dedup)
71
71
 
72
72
  Generated on 2026-07-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
@@ -205,6 +205,8 @@ Pierwsze uruchomienie zapisuje mały punkt odniesienia w
205
205
  swapu i znanych cache w wybranym oknie oraz od początku bieżącego dnia. Kwota
206
206
  „bezpieczne cache” obejmuje wyłącznie jawnie odtwarzalne dane. Docker, modele
207
207
  AI, rozszerzenia i cache IDE są raportowane osobno jako wymagające decyzji.
208
+ Nie są do niej zaliczane m.in. narzędzia i interpretery z
209
+ `~/.local/share/uv`, `.tools` pnpm, modele AI ani rozszerzenia edytorów.
208
210
 
209
211
  ### Przykładowy widok w terminalu (Czyszczenie dysku)
210
212
 
@@ -258,15 +260,24 @@ Tryb DRY-RUN - żadne akcje nie zostaną wykonane
258
260
  Każda znaleziona usługa jest klasyfikowana do jednej z trzech grup:
259
261
 
260
262
  - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...).
261
- Jedyna grupa oferowana do automatycznego usunięcia, z menu wyboru: wszystkie / pojedynczo / pomiń.
263
+ Tylko tę grupę można usunąć automatycznie opcją „wszystkie”.
262
264
  - **do rozważenia** – reinstalowalne aplikacje, dane długo nieużywane, nierozpoznane
263
- foldery (JetBrains, Snap, Flatpak) – pokazywane z podpowiedziami, nigdy nie usuwane automatycznie.
265
+ foldery (JetBrains, Snap, Flatpak) – pokazywane z komendą i wymagają
266
+ osobnego, świadomego potwierdzenia.
264
267
  - **chronione lub mieszane** – realne dane aplikacji albo magazyny łączące dane aktywne
265
268
  z cache (Ollama, LM Studio, HuggingFace hub, Docker/Podman/containerd, cała biblioteka
266
269
  Steam, `.cursor/extensions`/`.vscode/extensions`
267
270
  – zainstalowane rozszerzenia edytora, nie ich cache). Osobna sekcja z ostrzeżeniem;
268
- zbiorcze kasowanie modeli i wolumenów jest wyłączone. Docker pokazuje osobno
269
- `SIZE` i `RECLAIMABLE`; domyślne czyszczenie ogranicza się do starego cache buildów.
271
+ zbiorcze kasowanie modeli, klastrów, maszyn wirtualnych, rozszerzeń i
272
+ wolumenów jest wyłączone. Docker pokazuje osobno `SIZE` i `RECLAIMABLE`;
273
+ domyślne czyszczenie ogranicza się do starego cache buildów. Raportowane
274
+ `RECLAIMABLE` jest górną informacją z Dockera — filtr starszy niż 7 dni może
275
+ faktycznie zwolnić znacznie mniej.
276
+
277
+ Opcja „Wybierz pojedyncze” przechodzi kolejno przez wszystkie znalezione
278
+ usługi w tych trzech grupach. Dla pozycji bez bezpiecznej operacji zbiorczej
279
+ wyświetla wyłącznie komendę podglądu. Pozycje chronione wymagają dodatkowego
280
+ potwierdzenia.
270
281
 
271
282
  ### `fixos projects` – skaner artefaktów w projektach deweloperskich
272
283
 
@@ -565,7 +576,8 @@ fixos/
565
576
 
566
577
  ## 📋 Roadmap
567
578
 
568
- Zobacz pełną listę zadań i roadmap w pliku [TODO.md](./TODO.md)
579
+ Aktualne, ręcznie zweryfikowane zadania znajdują się w [TODO.md](./TODO.md).
580
+ Historyczne artykuły i plany są przechowywane w katalogu [TODO/](./TODO/).
569
581
 
570
582
  ---
571
583
 
@@ -620,7 +632,8 @@ fixos orchestrate --dry-run
620
632
 
621
633
  ### 🔗 Linki
622
634
  - **GitHub**: https://github.com/wronai/fixos
623
- - **Pełna dokumentacja**: [TODO.md](./TODO.md)
635
+ - **Dokumentacja**: [docs/README.md](./docs/README.md)
636
+ - **Aktualne zadania**: [TODO.md](./TODO.md)
624
637
  ---
625
638
 
626
639
  ## Licencja
@@ -19,11 +19,11 @@ AI-powered OS Diagnostics
19
19
 
20
20
  ## AI Cost Tracking
21
21
 
22
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.39-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$4.76-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-38.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
22
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.41-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$4.87-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-39.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $4.7645 (146 commits)
26
- - 👤 **Human dev:** ~$3795 (38.0h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $4.8653 (149 commits)
26
+ - 👤 **Human dev:** ~$3913 (39.1h @ $100/h, 30min dedup)
27
27
 
28
28
  Generated on 2026-07-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
29
29
 
@@ -161,6 +161,8 @@ Pierwsze uruchomienie zapisuje mały punkt odniesienia w
161
161
  swapu i znanych cache w wybranym oknie oraz od początku bieżącego dnia. Kwota
162
162
  „bezpieczne cache” obejmuje wyłącznie jawnie odtwarzalne dane. Docker, modele
163
163
  AI, rozszerzenia i cache IDE są raportowane osobno jako wymagające decyzji.
164
+ Nie są do niej zaliczane m.in. narzędzia i interpretery z
165
+ `~/.local/share/uv`, `.tools` pnpm, modele AI ani rozszerzenia edytorów.
164
166
 
165
167
  ### Przykładowy widok w terminalu (Czyszczenie dysku)
166
168
 
@@ -214,15 +216,24 @@ Tryb DRY-RUN - żadne akcje nie zostaną wykonane
214
216
  Każda znaleziona usługa jest klasyfikowana do jednej z trzech grup:
215
217
 
216
218
  - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...).
217
- Jedyna grupa oferowana do automatycznego usunięcia, z menu wyboru: wszystkie / pojedynczo / pomiń.
219
+ Tylko tę grupę można usunąć automatycznie opcją „wszystkie”.
218
220
  - **do rozważenia** – reinstalowalne aplikacje, dane długo nieużywane, nierozpoznane
219
- foldery (JetBrains, Snap, Flatpak) – pokazywane z podpowiedziami, nigdy nie usuwane automatycznie.
221
+ foldery (JetBrains, Snap, Flatpak) – pokazywane z komendą i wymagają
222
+ osobnego, świadomego potwierdzenia.
220
223
  - **chronione lub mieszane** – realne dane aplikacji albo magazyny łączące dane aktywne
221
224
  z cache (Ollama, LM Studio, HuggingFace hub, Docker/Podman/containerd, cała biblioteka
222
225
  Steam, `.cursor/extensions`/`.vscode/extensions`
223
226
  – zainstalowane rozszerzenia edytora, nie ich cache). Osobna sekcja z ostrzeżeniem;
224
- zbiorcze kasowanie modeli i wolumenów jest wyłączone. Docker pokazuje osobno
225
- `SIZE` i `RECLAIMABLE`; domyślne czyszczenie ogranicza się do starego cache buildów.
227
+ zbiorcze kasowanie modeli, klastrów, maszyn wirtualnych, rozszerzeń i
228
+ wolumenów jest wyłączone. Docker pokazuje osobno `SIZE` i `RECLAIMABLE`;
229
+ domyślne czyszczenie ogranicza się do starego cache buildów. Raportowane
230
+ `RECLAIMABLE` jest górną informacją z Dockera — filtr starszy niż 7 dni może
231
+ faktycznie zwolnić znacznie mniej.
232
+
233
+ Opcja „Wybierz pojedyncze” przechodzi kolejno przez wszystkie znalezione
234
+ usługi w tych trzech grupach. Dla pozycji bez bezpiecznej operacji zbiorczej
235
+ wyświetla wyłącznie komendę podglądu. Pozycje chronione wymagają dodatkowego
236
+ potwierdzenia.
226
237
 
227
238
  ### `fixos projects` – skaner artefaktów w projektach deweloperskich
228
239
 
@@ -521,7 +532,8 @@ fixos/
521
532
 
522
533
  ## 📋 Roadmap
523
534
 
524
- Zobacz pełną listę zadań i roadmap w pliku [TODO.md](./TODO.md)
535
+ Aktualne, ręcznie zweryfikowane zadania znajdują się w [TODO.md](./TODO.md).
536
+ Historyczne artykuły i plany są przechowywane w katalogu [TODO/](./TODO/).
525
537
 
526
538
  ---
527
539
 
@@ -576,7 +588,8 @@ fixos orchestrate --dry-run
576
588
 
577
589
  ### 🔗 Linki
578
590
  - **GitHub**: https://github.com/wronai/fixos
579
- - **Pełna dokumentacja**: [TODO.md](./TODO.md)
591
+ - **Dokumentacja**: [docs/README.md](./docs/README.md)
592
+ - **Aktualne zadania**: [TODO.md](./TODO.md)
580
593
  ---
581
594
 
582
595
  ## Licencja
@@ -1,3 +1,3 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
2
 
3
- __version__ = "2.2.39"
3
+ __version__ = "2.2.41"
@@ -0,0 +1,7 @@
1
+ """Allow running fixOS as ``python -m fixos``."""
2
+
3
+ from fixos.cli import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -55,7 +55,8 @@ def _display_cleanup_summary(plan: dict, threshold: int) -> None:
55
55
  if reclaimable:
56
56
  click.echo(
57
57
  click.style(
58
- f" Odzyskiwalne wg menedżerów usług: {reclaimable:.2f} GB",
58
+ f" Odzyskiwalne wg menedżerów usług (informacyjnie, nie plan "
59
+ f"automatyczny): {reclaimable:.2f} GB",
59
60
  fg="cyan",
60
61
  )
61
62
  )
@@ -79,9 +80,7 @@ def _display_service_item(svc: dict) -> None:
79
80
  risk = svc.get("risk_level") or ("safe" if svc["safe_to_cleanup"] else "review")
80
81
  safe_text, safe_color = _RISK_LABELS.get(risk, _RISK_LABELS["review"])
81
82
 
82
- click.echo(
83
- f" {click.style(svc['name'], fg='yellow', bold=True)} - {size_str}"
84
- )
83
+ click.echo(f" {click.style(svc['name'], fg='yellow', bold=True)} - {size_str}")
85
84
  click.echo(f" {svc['description']}")
86
85
  paths = svc.get("details", {}).get("paths") or [svc["path"]]
87
86
  if len(paths) > 1:
@@ -90,7 +89,9 @@ def _display_service_item(svc: dict) -> None:
90
89
  click.echo(f" • {path}")
91
90
  else:
92
91
  click.echo(f" Ścieżka: {svc['path']}")
93
- click.echo(f" {click.style(safe_text, fg=safe_color, bold=(risk == 'dangerous'))}")
92
+ click.echo(
93
+ f" {click.style(safe_text, fg=safe_color, bold=(risk == 'dangerous'))}"
94
+ )
94
95
 
95
96
  # Show details for specific services
96
97
  if svc.get("details"):
@@ -134,7 +135,11 @@ def _execute_safe_cleanup(services: list, scanner) -> float:
134
135
  for svc in services:
135
136
  svc_type = svc["service_type"]
136
137
  click.echo(f"Czyszczenie {svc_type}...")
137
- result = scanner.cleanup_service(svc_type, dry_run=False)
138
+ result = scanner.cleanup_service(
139
+ svc_type,
140
+ dry_run=False,
141
+ planned_service=svc,
142
+ )
138
143
  if result["success"]:
139
144
  freed = result.get("space_freed_gb", 0)
140
145
  total_freed += freed
@@ -294,62 +299,140 @@ def _display_dangerous_services(services: list) -> None:
294
299
  )
295
300
 
296
301
 
297
- def _service_has_dangerous_data(service_name: str, scanner) -> bool:
298
- """True if any currently-scanned path for this service is 'dangerous'."""
299
- from fixos.diagnostics.service_scanner import RiskLevel, ServiceType
302
+ def _single_service_target(service_name: str, scanner) -> dict | None:
303
+ """Resolve ``cleanup -c NAME`` to the safest executable scanned entry.
304
+
305
+ Some service types expose both rebuildable cache and protected data. The
306
+ direct command must pin execution to the cache entry instead of warning
307
+ about extensions/models and then letting a rescan pick whichever entry is
308
+ largest.
309
+ """
310
+ from fixos.diagnostics.service_scanner import ServiceType
300
311
 
301
312
  try:
302
313
  service_enum = ServiceType(service_name)
303
314
  except ValueError:
304
- return False
305
- scanned = scanner.scan_service(service_enum)
306
- return any(s.risk_level == RiskLevel.DANGEROUS.value for s in scanned)
315
+ return None
316
+
317
+ entries = scanner.scan_service(service_enum)
318
+ if not entries:
319
+ return None
320
+
321
+ risk_order = {"safe": 0, "review": 1, "dangerous": 2}
322
+ executable = [
323
+ item
324
+ for item in entries
325
+ if item.can_cleanup and item.cleanup_command.strip()
326
+ ]
327
+ candidates = executable or entries
328
+ selected = min(
329
+ candidates,
330
+ key=lambda item: (risk_order.get(item.risk_level, 1), -item.size_mb),
331
+ )
332
+ return {
333
+ "service_type": selected.service_type.value,
334
+ "name": selected.name,
335
+ "path": selected.path,
336
+ "size_mb": selected.size_mb,
337
+ "size_gb": selected.size_gb,
338
+ "description": selected.description,
339
+ "can_cleanup": selected.can_cleanup,
340
+ "cleanup_command": selected.cleanup_command,
341
+ "preview_command": selected.preview_command,
342
+ "safe_to_cleanup": selected.safe_to_cleanup,
343
+ "risk_level": selected.risk_level,
344
+ "impact": selected.impact,
345
+ "items_count": selected.items_count,
346
+ "details": selected.details,
347
+ }
307
348
 
308
349
 
309
350
  def _cleanup_single_service(
310
351
  service_name: str, scanner, json_output: bool, dry_run: bool
311
352
  ) -> None:
312
353
  """Handle cleanup of a single specific service."""
354
+ target = _single_service_target(service_name, scanner)
355
+
313
356
  if json_output:
314
- result = scanner.cleanup_service(service_name, dry_run=dry_run)
357
+ result = scanner.cleanup_service(
358
+ service_name,
359
+ dry_run=dry_run,
360
+ planned_service=target,
361
+ )
315
362
  import json
316
363
 
317
364
  click.echo(json.dumps(result, indent=2, default=str))
318
365
  return
319
366
 
320
- if not dry_run and _service_has_dangerous_data(service_name, scanner):
367
+ if target is None:
368
+ click.echo(
369
+ click.style(
370
+ f"Nie znaleziono danych {service_name} powyżej progu.",
371
+ fg="yellow",
372
+ )
373
+ )
374
+ return
375
+
376
+ if not target["can_cleanup"] or not target["cleanup_command"].strip():
321
377
  click.echo(
322
378
  click.style(
323
- f"⚠️ {service_name} zawiera realne dane (nie tylko cache) — np. "
324
- "zainstalowane modele/rozszerzenia/kontenery. Tej operacji nie "
325
- "da się cofnąć.",
379
+ f"{target['name']}: zbiorcze czyszczenie jest wyłączone, "
380
+ "ponieważ wpis zawiera chronione dane.",
381
+ fg="yellow",
382
+ bold=True,
383
+ )
384
+ )
385
+ if target.get("preview_command"):
386
+ click.echo(f" Bezpieczny podgląd: {target['preview_command']}")
387
+ click.echo(" Usuń ręcznie tylko konkretny, wcześniej sprawdzony element.")
388
+ return
389
+
390
+ if not dry_run and target.get("risk_level") == "dangerous":
391
+ click.echo(
392
+ click.style(
393
+ f"⚠️ {target['name']} zawiera dane mieszane. fixOS wykona "
394
+ "wyłącznie ograniczoną operację pokazaną poniżej:",
326
395
  fg="red",
327
396
  bold=True,
328
397
  )
329
398
  )
330
- click.echo(f" Podgląd bez usuwania: fixos cleanup -c {service_name} --dry-run")
331
- if not click.confirm(f"Na pewno usunąć dane usługi {service_name}?"):
399
+ click.echo(f" {target['cleanup_command']}")
400
+ if not click.confirm(f"Wykonać ograniczone czyszczenie {target['name']}?"):
332
401
  click.echo("Anulowano.")
333
402
  return
334
403
 
335
- click.echo(click.style(f"Czyszczenie usługi: {service_name}", fg="yellow"))
404
+ click.echo(click.style(f"Czyszczenie usługi: {target['name']}", fg="yellow"))
336
405
  if dry_run:
337
406
  click.echo(click.style("[TRYB DRY-RUN] - brak faktycznych zmian", fg="cyan"))
338
407
 
339
- result = scanner.cleanup_service(service_name, dry_run=dry_run)
408
+ result = scanner.cleanup_service(
409
+ service_name,
410
+ dry_run=dry_run,
411
+ planned_service=target,
412
+ )
340
413
 
341
414
  if result["success"]:
342
415
  if dry_run:
343
- click.echo(click.style(f"Symulacja dla {service_name} zakończona", fg="green"))
416
+ click.echo(
417
+ click.style(f"Symulacja dla {service_name} zakończona", fg="green")
418
+ )
344
419
  if result.get("output"):
345
420
  click.echo(f" {result['output']}")
346
421
  if result["space_freed_gb"] > 0:
347
- click.echo(
422
+ estimate = (
348
423
  f" Szacowane maksimum do odzyskania: "
349
424
  f"{result['space_freed_gb']:.2f} GB"
350
425
  )
426
+ if service_name == "docker":
427
+ estimate += (
428
+ " (cały odzyskiwalny cache buildów wg Docker; "
429
+ "filtr >7 dni zwykle usunie mniej)"
430
+ )
431
+ click.echo(estimate)
351
432
  else:
352
- click.echo(click.style(f"Zakończono czyszczenie {service_name}", fg="green"))
433
+ click.echo(
434
+ click.style(f"Zakończono czyszczenie {service_name}", fg="green")
435
+ )
353
436
  if result["space_freed_gb"] > 0:
354
437
  click.echo(f" Zwolniono: {result['space_freed_gb']:.2f} GB")
355
438
  else:
@@ -362,11 +445,15 @@ def _cleanup_single_service(
362
445
 
363
446
 
364
447
  def _size_str(svc: dict) -> str:
365
- return f"{svc['size_gb']:.2f} GB" if svc["size_gb"] >= 1 else f"{svc['size_mb']:.0f} MB"
448
+ return (
449
+ f"{svc['size_gb']:.2f} GB"
450
+ if svc["size_gb"] >= 1
451
+ else f"{svc['size_mb']:.0f} MB"
452
+ )
366
453
 
367
454
 
368
- def _select_safe_services(safe_services: list) -> list:
369
- """Ask what to clean from the safe tier: all of it, hand-picked items, or none."""
455
+ def _select_safe_services(safe_services: list) -> tuple[str, list]:
456
+ """Choose bulk-safe cleanup, full individual selection, or no cleanup."""
370
457
  click.echo(click.style("Bezpieczne do wyczyszczenia:", fg="green"))
371
458
  for svc in safe_services:
372
459
  click.echo(f" • {svc['name']}: {_size_str(svc)}")
@@ -375,28 +462,115 @@ def _select_safe_services(safe_services: list) -> list:
375
462
  click.echo()
376
463
  click.echo("Co wyczyścić?")
377
464
  click.echo(f" [1] Wszystkie bezpieczne (zwolni {safe_total:.2f} GB)")
378
- click.echo(" [2] Wybierz pojedyncze usługi")
465
+ click.echo(" [2] Wybierz pojedyncze spośród wszystkich usług")
379
466
  click.echo(" [0] Nic — pomiń")
380
467
  choice = click.prompt(
381
468
  "Wybór", type=click.Choice(["0", "1", "2"]), default="1", show_choices=False
382
469
  )
383
470
 
384
471
  if choice == "0":
385
- return []
472
+ return "none", []
386
473
  if choice == "1":
387
- return safe_services
474
+ return "safe", safe_services
475
+ return "individual", []
476
+
477
+
478
+ def _selection_description(svc: dict) -> str:
479
+ risk = svc.get("risk_level", "review")
480
+ labels = {
481
+ "safe": "bezpieczne cache",
482
+ "review": "do rozważenia",
483
+ "dangerous": "CHRONIONE/mieszane",
484
+ }
485
+ description = labels.get(risk, "do rozważenia")
486
+ if risk == "dangerous" and svc["service_type"] == "docker":
487
+ reclaimable = svc.get("details", {}).get("usage", {}).get("Build Cache", {})
488
+ reclaimable_gb = float(reclaimable.get("reclaimable_gb", 0))
489
+ if reclaimable_gb:
490
+ description += (
491
+ f"; Docker raportuje {reclaimable_gb:.2f} GB odzyskiwalnego "
492
+ "cache buildów, filtr >7 dni zwykle usunie mniej"
493
+ )
494
+ return description
388
495
 
389
- click.echo(click.style("Wybierz usługi do wyczyszczenia:", fg="cyan"))
390
- return [svc for svc in safe_services if click.confirm(f" {svc['name']} ({_size_str(svc)})")]
496
+
497
+ def _select_individual_services(services: list) -> list:
498
+ """Offer every scanned, executable entry in its displayed order."""
499
+ click.echo(
500
+ click.style(
501
+ "Wybierz kolejno spośród wszystkich możliwych usług:",
502
+ fg="cyan",
503
+ )
504
+ )
505
+ selected = []
506
+ for svc in services:
507
+ label = (
508
+ f" {svc['name']} ({_size_str(svc)}) " f"[{_selection_description(svc)}]"
509
+ )
510
+ if not svc.get("can_cleanup") or not svc.get("cleanup_command", "").strip():
511
+ click.echo(f"{label} — brak bezpiecznej operacji zbiorczej")
512
+ preview = svc.get("preview_command")
513
+ if preview:
514
+ click.echo(f" Podgląd: {preview}")
515
+ continue
516
+ if click.confirm(label, default=False):
517
+ selected.append(svc)
518
+ return selected
519
+
520
+
521
+ def _execute_individual_cleanup(services: list, scanner) -> float:
522
+ """Execute hand-picked entries with an extra gate for protected data."""
523
+ total_freed = 0.0
524
+ for svc in services:
525
+ risk = svc.get("risk_level", "review")
526
+ if risk == "dangerous":
527
+ click.echo(
528
+ click.style(
529
+ f"⚠️ {svc['name']} zawiera dane chronione lub mieszane. "
530
+ "Operacji nie da się automatycznie cofnąć.",
531
+ fg="red",
532
+ bold=True,
533
+ )
534
+ )
535
+ if not click.confirm(
536
+ f"Potwierdź wykonanie dokładnie dla: {svc['name']}",
537
+ default=False,
538
+ ):
539
+ click.echo(f" Pominięto {svc['name']}.")
540
+ continue
541
+
542
+ click.echo(f"Czyszczenie {svc['name']}...")
543
+ result = scanner.cleanup_service(
544
+ svc["service_type"],
545
+ dry_run=False,
546
+ planned_service=svc,
547
+ )
548
+ if result["success"]:
549
+ freed = float(result.get("space_freed_gb", 0))
550
+ total_freed += freed
551
+ click.echo(click.style(f" Zwolniono {freed:.2f} GB", fg="green"))
552
+ else:
553
+ click.echo(click.style(f" Błąd: {_error_message(result)}", fg="red"))
554
+ return total_freed
391
555
 
392
556
 
393
557
  def _run_interactive_cleanup(plan: dict, list_only: bool, scanner) -> None:
394
558
  """Offer interactive safe cleanup and display unsafe services."""
395
- if not list_only and plan["safe_to_cleanup"]:
396
- selected = _select_safe_services(plan["safe_to_cleanup"])
559
+ if not list_only and plan["services"]:
560
+ if plan["safe_to_cleanup"]:
561
+ mode, selected = _select_safe_services(plan["safe_to_cleanup"])
562
+ else:
563
+ mode, selected = "individual", []
564
+ if mode == "individual":
565
+ selected = _select_individual_services(plan["services"])
566
+ if selected:
567
+ _execute_individual_cleanup(selected, scanner)
568
+ else:
569
+ click.echo(click.style("Pominięto czyszczenie.", fg="yellow"))
570
+ return
397
571
  if selected:
398
572
  _execute_safe_cleanup(selected, scanner)
399
- else:
573
+ elif mode == "none":
400
574
  click.echo(click.style("Pominięto czyszczenie.", fg="yellow"))
401
575
  if plan["requires_review"] and not list_only:
402
576
  _display_unsafe_services(plan["requires_review"])
@@ -61,10 +61,14 @@ CACHE_RULES: tuple[CacheRule, ...] = (
61
61
  (
62
62
  "~/.npm/_cacache",
63
63
  "~/.npm/_npx",
64
+ "~/.npm/_prebuilds",
64
65
  "~/AppData/Local/npm-cache/_cacache",
65
66
  "~/AppData/Local/npm-cache/_npx",
66
67
  ),
67
- "npm cache clean --force",
68
+ (
69
+ "npm cache clean --force 2>/dev/null || true; "
70
+ "rm -rf ~/.npm/_npx ~/.npm/_prebuilds"
71
+ ),
68
72
  stack="node",
69
73
  ),
70
74
  CacheRule(