fixos 2.2.45__tar.gz → 2.2.46__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.45 → fixos-2.2.46}/CHANGELOG.md +35 -0
  2. {fixos-2.2.45 → fixos-2.2.46}/PKG-INFO +46 -15
  3. {fixos-2.2.45 → fixos-2.2.46}/README.md +45 -14
  4. {fixos-2.2.45 → fixos-2.2.46}/fixos/__init__.py +1 -1
  5. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/cleanup_cmd.py +26 -2
  6. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/service_cleanup.py +157 -40
  7. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/PKG-INFO +46 -15
  8. {fixos-2.2.45 → fixos-2.2.46}/pyproject.toml +1 -1
  9. {fixos-2.2.45 → fixos-2.2.46}/setup.py +1 -1
  10. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_service_cleanup.py +37 -4
  11. {fixos-2.2.45 → fixos-2.2.46}/.env.example +0 -0
  12. {fixos-2.2.45 → fixos-2.2.46}/LICENSE +0 -0
  13. {fixos-2.2.45 → fixos-2.2.46}/MANIFEST.in +0 -0
  14. {fixos-2.2.45 → fixos-2.2.46}/docker/README.md +0 -0
  15. {fixos-2.2.45 → fixos-2.2.46}/docker/TEST_RESULTS.md +0 -0
  16. {fixos-2.2.45 → fixos-2.2.46}/docker/TEST_RESULTS_V2.md +0 -0
  17. {fixos-2.2.45 → fixos-2.2.46}/docker/alpine/Dockerfile +0 -0
  18. {fixos-2.2.45 → fixos-2.2.46}/docker/arch/Dockerfile +0 -0
  19. {fixos-2.2.45 → fixos-2.2.46}/docker/base/Dockerfile +0 -0
  20. {fixos-2.2.45 → fixos-2.2.46}/docker/broken-audio/Dockerfile +0 -0
  21. {fixos-2.2.45 → fixos-2.2.46}/docker/broken-full/Dockerfile +0 -0
  22. {fixos-2.2.45 → fixos-2.2.46}/docker/broken-network/Dockerfile +0 -0
  23. {fixos-2.2.45 → fixos-2.2.46}/docker/broken-thumbnails/Dockerfile +0 -0
  24. {fixos-2.2.45 → fixos-2.2.46}/docker/debian/Dockerfile +0 -0
  25. {fixos-2.2.45 → fixos-2.2.46}/docker/docker-compose.multi-system.yml +0 -0
  26. {fixos-2.2.45 → fixos-2.2.46}/docker/docker-compose.yml +0 -0
  27. {fixos-2.2.45 → fixos-2.2.46}/docker/fedora/Dockerfile +0 -0
  28. {fixos-2.2.45 → fixos-2.2.46}/docker/test-multi-system.sh +0 -0
  29. {fixos-2.2.45 → fixos-2.2.46}/docker/test-scenarios.sh +0 -0
  30. {fixos-2.2.45 → fixos-2.2.46}/docker/ubuntu/Dockerfile +0 -0
  31. {fixos-2.2.45 → fixos-2.2.46}/docker/validate-scenario.py +0 -0
  32. {fixos-2.2.45 → fixos-2.2.46}/docs/examples/advanced_usage.py +0 -0
  33. {fixos-2.2.45 → fixos-2.2.46}/docs/examples/quickstart.py +0 -0
  34. {fixos-2.2.45 → fixos-2.2.46}/fixos/__main__.py +0 -0
  35. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/__init__.py +0 -0
  36. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/autonomous.py +0 -0
  37. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/autonomous_session.py +0 -0
  38. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/hitl.py +0 -0
  39. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/hitl_session.py +0 -0
  40. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/session_core.py +0 -0
  41. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/session_handlers.py +0 -0
  42. {fixos-2.2.45 → fixos-2.2.46}/fixos/agent/session_io.py +0 -0
  43. {fixos-2.2.45 → fixos-2.2.46}/fixos/anonymizer.py +0 -0
  44. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/__init__.py +0 -0
  45. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/_cleanup_flatpak.py +0 -0
  46. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/_cleanup_home.py +0 -0
  47. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/_cleanup_snap.py +0 -0
  48. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/_cleanup_system.py +0 -0
  49. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/_cleanup_utils.py +0 -0
  50. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/ask_cmd.py +0 -0
  51. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/config_cmd.py +0 -0
  52. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/features_cmd.py +0 -0
  53. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/fix_cmd.py +0 -0
  54. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/history_cmd.py +0 -0
  55. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/main.py +0 -0
  56. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/orchestrate_cmd.py +0 -0
  57. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/output_formatter.py +0 -0
  58. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/profile_cmd.py +0 -0
  59. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/projects_cmd.py +0 -0
  60. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/provider_cmd.py +0 -0
  61. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/quick_cmd.py +0 -0
  62. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/quickfix_cmd.py +0 -0
  63. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/report_cmd.py +0 -0
  64. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/rollback_cmd.py +0 -0
  65. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/scan_cmd.py +0 -0
  66. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/shared.py +0 -0
  67. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/token_cmd.py +0 -0
  68. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli/watch_cmd.py +0 -0
  69. {fixos-2.2.45 → fixos-2.2.46}/fixos/cli.py +0 -0
  70. {fixos-2.2.45 → fixos-2.2.46}/fixos/config.py +0 -0
  71. {fixos-2.2.45 → fixos-2.2.46}/fixos/config_interactive.py +0 -0
  72. {fixos-2.2.45 → fixos-2.2.46}/fixos/constants.py +0 -0
  73. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/__init__.py +0 -0
  74. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_flatpak_analysis_mixin.py +0 -0
  75. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_flatpak_execution_mixin.py +0 -0
  76. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_flatpak_recommendations_mixin.py +0 -0
  77. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_storage_container_mixin.py +0 -0
  78. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_storage_system_mixin.py +0 -0
  79. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/_storage_user_mixin.py +0 -0
  80. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/cache_discovery.py +0 -0
  81. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/__init__.py +0 -0
  82. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/_shared.py +0 -0
  83. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/audio.py +0 -0
  84. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/file_analysis.py +0 -0
  85. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/hardware.py +0 -0
  86. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/packages.py +0 -0
  87. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/resources.py +0 -0
  88. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/security.py +0 -0
  89. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/storage_optimization.py +0 -0
  90. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/system_core.py +0 -0
  91. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/checks/thumbnails.py +0 -0
  92. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  93. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/disk_analyzer.py +0 -0
  94. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  95. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/project_scanner.py +0 -0
  96. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/quick_snapshot.py +0 -0
  97. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/service_details.py +0 -0
  98. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/service_scanner.py +0 -0
  99. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/storage_analyzer.py +0 -0
  100. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/system_checks.py +0 -0
  101. {fixos-2.2.45 → fixos-2.2.46}/fixos/diagnostics/utils.py +0 -0
  102. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/__init__.py +0 -0
  103. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/auditor.py +0 -0
  104. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/catalog.py +0 -0
  105. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/installer.py +0 -0
  106. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/profiles.py +0 -0
  107. {fixos-2.2.45 → fixos-2.2.46}/fixos/features/renderer.py +0 -0
  108. {fixos-2.2.45 → fixos-2.2.46}/fixos/fixes/__init__.py +0 -0
  109. {fixos-2.2.45 → fixos-2.2.46}/fixos/interactive/__init__.py +0 -0
  110. {fixos-2.2.45 → fixos-2.2.46}/fixos/interactive/cleanup_planner.py +0 -0
  111. {fixos-2.2.45 → fixos-2.2.46}/fixos/llm_shell.py +0 -0
  112. {fixos-2.2.45 → fixos-2.2.46}/fixos/orchestrator/__init__.py +0 -0
  113. {fixos-2.2.45 → fixos-2.2.46}/fixos/orchestrator/executor.py +0 -0
  114. {fixos-2.2.45 → fixos-2.2.46}/fixos/orchestrator/graph.py +0 -0
  115. {fixos-2.2.45 → fixos-2.2.46}/fixos/orchestrator/orchestrator.py +0 -0
  116. {fixos-2.2.45 → fixos-2.2.46}/fixos/orchestrator/rollback.py +0 -0
  117. {fixos-2.2.45 → fixos-2.2.46}/fixos/platform_utils.py +0 -0
  118. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/__init__.py +0 -0
  119. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/base.py +0 -0
  120. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/__init__.py +0 -0
  121. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/audio.py +0 -0
  122. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/disk.py +0 -0
  123. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/hardware.py +0 -0
  124. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/resources.py +0 -0
  125. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/security.py +0 -0
  126. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/builtin/thumbnails.py +0 -0
  127. {fixos-2.2.45 → fixos-2.2.46}/fixos/plugins/registry.py +0 -0
  128. {fixos-2.2.45 → fixos-2.2.46}/fixos/profiles/__init__.py +0 -0
  129. {fixos-2.2.45 → fixos-2.2.46}/fixos/providers/__init__.py +0 -0
  130. {fixos-2.2.45 → fixos-2.2.46}/fixos/providers/llm.py +0 -0
  131. {fixos-2.2.45 → fixos-2.2.46}/fixos/providers/llm_analyzer.py +0 -0
  132. {fixos-2.2.45 → fixos-2.2.46}/fixos/providers/schemas.py +0 -0
  133. {fixos-2.2.45 → fixos-2.2.46}/fixos/system_checks.py +0 -0
  134. {fixos-2.2.45 → fixos-2.2.46}/fixos/utils/__init__.py +0 -0
  135. {fixos-2.2.45 → fixos-2.2.46}/fixos/utils/anonymizer.py +0 -0
  136. {fixos-2.2.45 → fixos-2.2.46}/fixos/utils/terminal.py +0 -0
  137. {fixos-2.2.45 → fixos-2.2.46}/fixos/utils/timeout.py +0 -0
  138. {fixos-2.2.45 → fixos-2.2.46}/fixos/utils/web_search.py +0 -0
  139. {fixos-2.2.45 → fixos-2.2.46}/fixos/watch.py +0 -0
  140. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/SOURCES.txt +0 -0
  141. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/dependency_links.txt +0 -0
  142. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/entry_points.txt +0 -0
  143. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/requires.txt +0 -0
  144. {fixos-2.2.45 → fixos-2.2.46}/fixos.egg-info/top_level.txt +0 -0
  145. {fixos-2.2.45 → fixos-2.2.46}/pytest.ini +0 -0
  146. {fixos-2.2.45 → fixos-2.2.46}/requirements-dev.txt +0 -0
  147. {fixos-2.2.45 → fixos-2.2.46}/requirements.txt +0 -0
  148. {fixos-2.2.45 → fixos-2.2.46}/scripts/pyqual-calibrate.py +0 -0
  149. {fixos-2.2.45 → fixos-2.2.46}/setup.cfg +0 -0
  150. {fixos-2.2.45 → fixos-2.2.46}/tests/__init__.py +0 -0
  151. {fixos-2.2.45 → fixos-2.2.46}/tests/conftest.py +0 -0
  152. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/__init__.py +0 -0
  153. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_anonymization_layers.py +0 -0
  154. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_audio_broken.py +0 -0
  155. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_cli.py +0 -0
  156. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_diagnostics_integration.py +0 -0
  157. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_executor.py +0 -0
  158. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_multi_system.py +0 -0
  159. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_network_broken.py +0 -0
  160. {fixos-2.2.45 → fixos-2.2.46}/tests/e2e/test_thumbnails_broken.py +0 -0
  161. {fixos-2.2.45 → fixos-2.2.46}/tests/test_fixos.py +0 -0
  162. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/__init__.py +0 -0
  163. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_anonymizer.py +0 -0
  164. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_cache_discovery.py +0 -0
  165. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_cleanup_cmd.py +0 -0
  166. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_core.py +0 -0
  167. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_dev_project_analyzer.py +0 -0
  168. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_executor.py +0 -0
  169. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_llm_provider.py +0 -0
  170. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_orchestrator.py +0 -0
  171. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_project_scanner.py +0 -0
  172. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_projects_cmd.py +0 -0
  173. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_quick_cmd.py +0 -0
  174. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_quick_snapshot.py +0 -0
  175. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_resources_quick.py +0 -0
  176. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_security_quick.py +0 -0
  177. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_service_scanner.py +0 -0
  178. {fixos-2.2.45 → fixos-2.2.46}/tests/unit/test_system_checks_parallel.py +0 -0
@@ -2,6 +2,19 @@
2
2
 
3
3
  ### Added
4
4
 
5
+ - `fixos cleanup --docker-old [--days N]` oraz `-c docker-old`: usuwa wyłącznie
6
+ nieużywane obrazy Docker i build cache starsze niż N dni (domyślnie 30);
7
+ bez wolumenów i bez obrazów używanych przez kontenery.
8
+ - `fixos cleanup --ollama-old [--days N]` oraz `-c ollama-old`: usuwa modele
9
+ Ollama niezmieniane od N dni (domyślnie 90), z pominięciem modeli
10
+ aktualnie załadowanych (`/api/ps`).
11
+ - Opcja interaktywna **[1] Wszystkie bezpieczne** obejmuje teraz także:
12
+ - **Docker (nieużywane obrazy)** — pełną pulę unused (zgodną z RECLAIMABLE
13
+ Images+Build Cache), bez filtra wieku,
14
+ - **Ollama (modele >90 dni)** — gdy takie modele istnieją.
15
+ - Pomiar zwolnionego miejsca Dockera przez `docker system df` przed/po oraz
16
+ parsowanie `Total reclaimed space`; ostrzeżenie przy 0.00 GB mimo sukcesu
17
+ komendy; przypomnienie, że listy po `[1]` pochodzą ze skanu sprzed cleanup.
5
18
  - `fixos quick`: ograniczona czasowo analiza heurystyczna bez LLM, która od razu
6
19
  pokazuje presję CPU/RAM/dysku i maksymalny rozmiar jawnie odtwarzalnych cache.
7
20
  - Lokalna historia szybkich próbek z porównaniem ostatnich godzin i bieżącego
@@ -14,6 +27,9 @@
14
27
 
15
28
  ### Changed
16
29
 
30
+ - Dokumentacja README / getting-started / architecture opisuje model trzech
31
+ poziomów ryzyka z nowymi flagami `--docker-old` / `--ollama-old` i różnicą
32
+ między reclaimable a filtrem wieku.
17
33
  - Zakończone elementy historycznych roadmap zostały przeniesione z TODO do
18
34
  changelogu: modułowy pakiet CLI, klasowe sesje HITL, wspólny
19
35
  `SessionTimeout`, plugin registry, structured output LLM, rollback, profile
@@ -24,6 +40,9 @@
24
40
 
25
41
  ### Fixed
26
42
 
43
+ - Szacunek Dockera w opcji `[1]` nie obiecuje już całej puli RECLAIMABLE przy
44
+ filtrze `until=30d` (świeże unused images nie były kasowane, a UI pokazywało
45
+ 0.00 GB).
27
46
  - Zablokowano zbiorcze kasowanie chronionych danych LM Studio, Hugging Face,
28
47
  Podman/containerd, Jupyter, Minikube, AppImage, VirtualBox, VMware oraz
29
48
  rozszerzeń VS Code/Cursor. Takie wpisy pokazują teraz wyłącznie bezpieczny
@@ -51,6 +70,22 @@
51
70
  rekurencyjnych skanów `/` oraz `/home` z szybkich kontroli.
52
71
  - Czyszczenie cache JetBrains trafia w aktualną ścieżkę i wymaga zamknięcia IDE.
53
72
 
73
+ ## [2.2.46] - 2026-08-07
74
+
75
+ ### Docs
76
+ - Update CHANGELOG.md
77
+ - Update README.md
78
+ - Update docs/architecture.md
79
+ - Update docs/getting-started.md
80
+
81
+ ### Test
82
+ - Update tests/unit/test_service_cleanup.py
83
+
84
+ ### Other
85
+ - Update fixos/cli/cleanup_cmd.py
86
+ - Update fixos/diagnostics/service_cleanup.py
87
+ - Update project/planfile-tickets.yaml
88
+
54
89
  ## [2.2.45] - 2026-08-07
55
90
 
56
91
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.45
3
+ Version: 2.2.46
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.45-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.81-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-39.6h-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.46-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.84-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-40.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $4.8099 (150 commits)
70
- - 👤 **Human dev:** ~$3959 (39.6h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $4.8388 (151 commits)
70
+ - 👤 **Human dev:** ~$4059 (40.6h @ $100/h, 30min dedup)
71
71
 
72
72
  Generated on 2026-08-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
@@ -118,6 +118,8 @@ fixos quick --deep – szybki wynik, potem pełny skan danych usług
118
118
  fixos fix – diagnoza + sesja naprawcza z AI (HITL)
119
119
  fixos scan – diagnostyka systemu bez AI
120
120
  fixos cleanup – skanuj i czyść dane usług (Docker, npm, pip, ...)
121
+ fixos cleanup --docker-old – nieużywane obrazy Docker starsze niż N dni (--days)
122
+ fixos cleanup --ollama-old – modele Ollama niezmieniane od N dni (domyślnie 90)
121
123
  fixos cleanup --full – pełny audit systemu + dev projects
122
124
  fixos projects – skanuj projekty dev (venv, node_modules, ~/github/*/*)
123
125
  fixos orchestrate – zaawansowana orkiestracja (graf problemów DAG)
@@ -153,6 +155,17 @@ fixos fix --disc
153
155
  # Pełny audit systemu (DNF, Docker, Flatpak, dev projects)
154
156
  fixos cleanup --full --dry-run
155
157
 
158
+ # Podgląd usług + interaktywne czyszczenie (opcja [1] = wszystkie bezpieczne)
159
+ fixos cleanup --list
160
+ fixos cleanup --dry-run # tylko gdy -c / --docker-old / --ollama-old
161
+ fixos cleanup
162
+
163
+ # Nieużywane obrazy Docker starsze niż 30 dni (filtr wieku; może zwolnić mniej niż RECLAIMABLE)
164
+ fixos cleanup --docker-old --days 30 --dry-run
165
+
166
+ # Modele Ollama niezmieniane od 90+ dni (pomija uruchomione)
167
+ fixos cleanup --ollama-old --days 90 --dry-run
168
+
156
169
  # Usuń tylko foldery venv z projektów
157
170
  fixos cleanup --full
158
171
  > type:venv
@@ -259,20 +272,38 @@ Tryb DRY-RUN - żadne akcje nie zostaną wykonane
259
272
 
260
273
  Każda znaleziona usługa jest klasyfikowana do jednej z trzech grup:
261
274
 
262
- - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...).
263
- Tylko tę grupę można usunąć automatycznie opcją „wszystkie”.
275
+ - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...)
276
+ oraz ograniczone, jawnie bezpieczne akcje wiekowe / reclaimable:
277
+ - **Docker (nieużywane obrazy)** — wszystkie unused images + build cache
278
+ (bez wolumenów, bez obrazów podpiętych do kontenerów); odpowiada puli
279
+ `RECLAIMABLE` Images+Build Cache,
280
+ - **Ollama (modele >90 dni)** — modele niezmieniane od 90+ dni; pomija
281
+ modele aktualnie załadowane w pamięci.
282
+ Tylko tę grupę usuwa opcja **[1] Wszystkie bezpieczne**.
264
283
  - **do rozważenia** – reinstalowalne aplikacje, dane długo nieużywane, nierozpoznane
265
284
  foldery (JetBrains, Snap, Flatpak) – pokazywane z komendą i wymagają
266
285
  osobnego, świadomego potwierdzenia.
267
286
  - **chronione lub mieszane** – realne dane aplikacji albo magazyny łączące dane aktywne
268
- z cache (Ollama, LM Studio, HuggingFace hub, Docker/Podman/containerd, cała biblioteka
269
- Steam, `.cursor/extensions`/`.vscode/extensions`
270
- – zainstalowane rozszerzenia edytora, nie ich cache). Osobna sekcja z ostrzeżeniem;
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.
287
+ z cache (cały sklep Ollama/LM Studio/HuggingFace, cały Docker z wolumenami,
288
+ Steam, `.cursor/extensions`/`.vscode/extensions`). Osobna sekcja z ostrzeżeniem;
289
+ zbiorcze kasowanie modeli „na ślepo”, klastrów, maszyn wirtualnych, rozszerzeń i
290
+ wolumenów jest wyłączone.
291
+
292
+ Dedykowane flagi (też dostępne jako `-c docker-old` / `-c ollama-old`):
293
+
294
+ ```bash
295
+ fixos cleanup --docker-old --days 30 --dry-run # tylko unused starsze niż N dni
296
+ fixos cleanup --ollama-old --days 90 --dry-run # modele niezmieniane od N dni
297
+ ```
298
+
299
+ `RECLAIMABLE` Dockera obejmuje też świeże unused images. Filtr `--docker-old`
300
+ (domyślnie 30 dni) może więc zwolnić **znacznie mniej** niż szacunek całej puli —
301
+ dlatego opcja `[1]` używa prune **wszystkich** nieużywanych obrazów, a
302
+ `--docker-old` zostaje dla świadomego cięcia po wieku. Domyślne
303
+ `fixos cleanup -c docker` nadal ogranicza się do cache buildów >7 dni.
304
+
305
+ Po wykonaniu opcji `[1]` listy „do rozważenia / chronione” pochodzą ze skanu
306
+ sprzed czyszczenia — uruchom ponownie `fixos cleanup --list`, by zobaczyć stan.
276
307
 
277
308
  Opcja „Wybierz pojedyncze” przechodzi kolejno przez wszystkie znalezione
278
309
  usługi w tych trzech grupach. Dla pozycji bez bezpiecznej operacji zbiorczej
@@ -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.45-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.81-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-39.6h-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.46-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.84-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-40.6h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $4.8099 (150 commits)
26
- - 👤 **Human dev:** ~$3959 (39.6h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $4.8388 (151 commits)
26
+ - 👤 **Human dev:** ~$4059 (40.6h @ $100/h, 30min dedup)
27
27
 
28
28
  Generated on 2026-08-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
29
29
 
@@ -74,6 +74,8 @@ fixos quick --deep – szybki wynik, potem pełny skan danych usług
74
74
  fixos fix – diagnoza + sesja naprawcza z AI (HITL)
75
75
  fixos scan – diagnostyka systemu bez AI
76
76
  fixos cleanup – skanuj i czyść dane usług (Docker, npm, pip, ...)
77
+ fixos cleanup --docker-old – nieużywane obrazy Docker starsze niż N dni (--days)
78
+ fixos cleanup --ollama-old – modele Ollama niezmieniane od N dni (domyślnie 90)
77
79
  fixos cleanup --full – pełny audit systemu + dev projects
78
80
  fixos projects – skanuj projekty dev (venv, node_modules, ~/github/*/*)
79
81
  fixos orchestrate – zaawansowana orkiestracja (graf problemów DAG)
@@ -109,6 +111,17 @@ fixos fix --disc
109
111
  # Pełny audit systemu (DNF, Docker, Flatpak, dev projects)
110
112
  fixos cleanup --full --dry-run
111
113
 
114
+ # Podgląd usług + interaktywne czyszczenie (opcja [1] = wszystkie bezpieczne)
115
+ fixos cleanup --list
116
+ fixos cleanup --dry-run # tylko gdy -c / --docker-old / --ollama-old
117
+ fixos cleanup
118
+
119
+ # Nieużywane obrazy Docker starsze niż 30 dni (filtr wieku; może zwolnić mniej niż RECLAIMABLE)
120
+ fixos cleanup --docker-old --days 30 --dry-run
121
+
122
+ # Modele Ollama niezmieniane od 90+ dni (pomija uruchomione)
123
+ fixos cleanup --ollama-old --days 90 --dry-run
124
+
112
125
  # Usuń tylko foldery venv z projektów
113
126
  fixos cleanup --full
114
127
  > type:venv
@@ -215,20 +228,38 @@ Tryb DRY-RUN - żadne akcje nie zostaną wykonane
215
228
 
216
229
  Każda znaleziona usługa jest klasyfikowana do jednej z trzech grup:
217
230
 
218
- - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...).
219
- Tylko tę grupę można usunąć automatycznie opcją „wszystkie”.
231
+ - **bezpieczne** – cache do odtworzenia jednym poleceniem (pip/npm/cargo/conda/nix/brew...)
232
+ oraz ograniczone, jawnie bezpieczne akcje wiekowe / reclaimable:
233
+ - **Docker (nieużywane obrazy)** — wszystkie unused images + build cache
234
+ (bez wolumenów, bez obrazów podpiętych do kontenerów); odpowiada puli
235
+ `RECLAIMABLE` Images+Build Cache,
236
+ - **Ollama (modele >90 dni)** — modele niezmieniane od 90+ dni; pomija
237
+ modele aktualnie załadowane w pamięci.
238
+ Tylko tę grupę usuwa opcja **[1] Wszystkie bezpieczne**.
220
239
  - **do rozważenia** – reinstalowalne aplikacje, dane długo nieużywane, nierozpoznane
221
240
  foldery (JetBrains, Snap, Flatpak) – pokazywane z komendą i wymagają
222
241
  osobnego, świadomego potwierdzenia.
223
242
  - **chronione lub mieszane** – realne dane aplikacji albo magazyny łączące dane aktywne
224
- z cache (Ollama, LM Studio, HuggingFace hub, Docker/Podman/containerd, cała biblioteka
225
- Steam, `.cursor/extensions`/`.vscode/extensions`
226
- – zainstalowane rozszerzenia edytora, nie ich cache). Osobna sekcja z ostrzeżeniem;
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.
243
+ z cache (cały sklep Ollama/LM Studio/HuggingFace, cały Docker z wolumenami,
244
+ Steam, `.cursor/extensions`/`.vscode/extensions`). Osobna sekcja z ostrzeżeniem;
245
+ zbiorcze kasowanie modeli „na ślepo”, klastrów, maszyn wirtualnych, rozszerzeń i
246
+ wolumenów jest wyłączone.
247
+
248
+ Dedykowane flagi (też dostępne jako `-c docker-old` / `-c ollama-old`):
249
+
250
+ ```bash
251
+ fixos cleanup --docker-old --days 30 --dry-run # tylko unused starsze niż N dni
252
+ fixos cleanup --ollama-old --days 90 --dry-run # modele niezmieniane od N dni
253
+ ```
254
+
255
+ `RECLAIMABLE` Dockera obejmuje też świeże unused images. Filtr `--docker-old`
256
+ (domyślnie 30 dni) może więc zwolnić **znacznie mniej** niż szacunek całej puli —
257
+ dlatego opcja `[1]` używa prune **wszystkich** nieużywanych obrazów, a
258
+ `--docker-old` zostaje dla świadomego cięcia po wieku. Domyślne
259
+ `fixos cleanup -c docker` nadal ogranicza się do cache buildów >7 dni.
260
+
261
+ Po wykonaniu opcji `[1]` listy „do rozważenia / chronione” pochodzą ze skanu
262
+ sprzed czyszczenia — uruchom ponownie `fixos cleanup --list`, by zobaczyć stan.
232
263
 
233
264
  Opcja „Wybierz pojedyncze” przechodzi kolejno przez wszystkie znalezione
234
265
  usługi w tych trzech grupach. Dla pozycji bez bezpiecznej operacji zbiorczej
@@ -1,3 +1,3 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
2
 
3
- __version__ = "2.2.45"
3
+ __version__ = "2.2.46"
@@ -143,6 +143,8 @@ def _execute_planned_cleanup(scanner, svc: dict, *, dry_run: bool = False) -> di
143
143
  days=int(svc.get("days") or DEFAULT_OLLAMA_OLD_UNUSED_DAYS),
144
144
  dry_run=dry_run,
145
145
  )
146
+ if kind == "docker-unused":
147
+ return cleaner.cleanup_docker_unused(dry_run=dry_run)
146
148
  if kind == "docker-old":
147
149
  return cleaner.cleanup_docker_old_unused(
148
150
  days=int(svc.get("days") or DEFAULT_DOCKER_OLD_UNUSED_DAYS),
@@ -162,9 +164,23 @@ def _execute_safe_cleanup(services: list, scanner) -> float:
162
164
  click.echo(f"Czyszczenie {svc.get('name') or svc['service_type']}...")
163
165
  result = _execute_planned_cleanup(scanner, svc, dry_run=False)
164
166
  if result["success"]:
165
- freed = result.get("space_freed_gb", 0)
167
+ freed = float(result.get("space_freed_gb", 0) or 0)
166
168
  total_freed += freed
167
- click.echo(click.style(f" Zwolniono {freed:.2f} GB", fg="green"))
169
+ if freed > 0:
170
+ click.echo(click.style(f" Zwolniono {freed:.2f} GB", fg="green"))
171
+ else:
172
+ click.echo(
173
+ click.style(
174
+ " Brak mierzalnej zmiany rozmiaru (0.00 GB) — "
175
+ "komenda zakończyła się OK, ale cache mógł być już pusty "
176
+ "albo filtr wieku nic nie trafił.",
177
+ fg="yellow",
178
+ )
179
+ )
180
+ note = (result.get("output") or result.get("error") or "").strip()
181
+ if note:
182
+ for line in note.splitlines()[:4]:
183
+ click.echo(f" {line}")
168
184
  else:
169
185
  click.echo(click.style(f" Błąd: {_error_message(result)}", fg="red"))
170
186
  return total_freed
@@ -693,6 +709,14 @@ def _run_interactive_cleanup(plan: dict, list_only: bool, scanner) -> None:
693
709
  return
694
710
  if selected:
695
711
  _execute_safe_cleanup(selected, scanner)
712
+ click.echo()
713
+ click.echo(
714
+ click.style(
715
+ "Uwaga: listy poniżej pochodzą ze skanu sprzed czyszczenia — "
716
+ "uruchom ponownie `fixos cleanup --list`, by zobaczyć aktualny stan.",
717
+ fg="cyan",
718
+ )
719
+ )
696
720
  elif mode == "none":
697
721
  click.echo(click.style("Pominięto czyszczenie.", fg="yellow"))
698
722
  if plan["requires_review"] and not list_only:
@@ -263,31 +263,69 @@ class ServiceCleaner:
263
263
 
264
264
  return result
265
265
 
266
- def cleanup_docker_old_unused(
267
- self,
268
- days: int = DEFAULT_DOCKER_OLD_UNUSED_DAYS,
269
- dry_run: bool = False,
270
- ) -> Dict[str, Any]:
271
- """Remove unused Docker images (and rebuildable build cache) older than N days."""
272
- from .service_scanner import ServiceType
266
+ @staticmethod
267
+ def get_docker_unused_command() -> str:
268
+ """Prune all unused images and rebuildable build cache (no age filter).
273
269
 
274
- command = self.get_docker_old_unused_command(days)
275
- hours = self.docker_old_unused_until_hours(days)
270
+ Matches Docker's Images+Build Cache reclaimable pool. Does not touch
271
+ volumes or images still referenced by any container.
272
+ """
273
+ return "docker image prune -a --force && docker builder prune --force"
274
+
275
+ @staticmethod
276
+ def _parse_docker_reclaimed_gb(output: str) -> float | None:
277
+ """Parse ``Total reclaimed space: …`` from docker prune stdout/stderr."""
278
+ match = re.search(
279
+ r"Total reclaimed space:\s*([0-9]+(?:\.[0-9]+)?)\s*([KMGT]?B)",
280
+ output or "",
281
+ flags=re.IGNORECASE,
282
+ )
283
+ if not match:
284
+ return None
285
+ value = float(match.group(1))
286
+ unit = match.group(2).upper()
287
+ factor = {
288
+ "B": 1 / (1024**3),
289
+ "KB": 1 / (1024**2),
290
+ "MB": 1 / 1024,
291
+ "GB": 1.0,
292
+ "TB": 1024.0,
293
+ }.get(unit)
294
+ if factor is None:
295
+ return None
296
+ return round(value * factor, 3)
297
+
298
+ def _docker_images_and_cache_gb(self, *, refresh: bool = False) -> float | None:
299
+ """Current Images + Build Cache size from ``docker system df``."""
300
+ usage_fn = getattr(self.scanner, "_get_docker_daemon_usage", None)
301
+ if not callable(usage_fn):
302
+ return None
303
+ usage = usage_fn(refresh=refresh)
304
+ if not usage:
305
+ return None
306
+ rows = usage.get("rows") or {}
307
+ images = float((rows.get("Images") or {}).get("size_gb", 0.0))
308
+ cache = float((rows.get("Build Cache") or {}).get("size_gb", 0.0))
309
+ return round(images + cache, 3)
310
+
311
+ def cleanup_docker_unused(self, dry_run: bool = False) -> Dict[str, Any]:
312
+ """Remove all unused Docker images + build cache (safe reclaimable set)."""
313
+ command = self.get_docker_unused_command()
276
314
  result: Dict[str, Any] = {
277
- "service": "docker-old",
315
+ "service": "docker-unused",
278
316
  "dry_run": dry_run,
279
317
  "success": False,
280
318
  "space_freed_gb": 0,
281
319
  "output": "",
282
320
  "error": "",
283
321
  "command": command,
284
- "days": int(days),
285
- "until_hours": hours,
286
322
  }
287
323
 
288
324
  images_reclaimable = 0.0
289
325
  build_cache_reclaimable = 0.0
290
326
  try:
327
+ from .service_scanner import ServiceType
328
+
291
329
  services = self.scanner.scan_service(ServiceType.DOCKER)
292
330
  if services:
293
331
  usage = (services[0].details or {}).get("usage") or {}
@@ -308,35 +346,121 @@ class ServiceCleaner:
308
346
  result["space_freed_gb"] = estimated_gb
309
347
  result["output"] = (
310
348
  f"[DRY RUN] Would execute: {command}\n"
311
- f" Scope: unused images + build cache older than {int(days)} days "
312
- f"({hours}h). Volumes and in-use images are kept."
349
+ " Scope: all unused images + build cache. "
350
+ "Volumes and in-use images are kept."
313
351
  )
314
352
  return result
315
353
 
316
354
  try:
317
- measure = getattr(self.scanner, "measure_service_size_mb", None)
318
- before_mb = None
319
- if callable(measure):
320
- before_mb = measure(ServiceType.DOCKER, "/var/lib/docker", refresh=True)
355
+ before_gb = self._docker_images_and_cache_gb(refresh=True)
356
+ cleanup_result = subprocess.run(
357
+ command,
358
+ shell=True,
359
+ capture_output=True,
360
+ text=True,
361
+ timeout=max(DEFAULT_COMMAND_TIMEOUT, 1800),
362
+ )
363
+ combined = "\n".join(
364
+ part
365
+ for part in (cleanup_result.stdout, cleanup_result.stderr)
366
+ if part and part.strip()
367
+ )
368
+ result["returncode"] = cleanup_result.returncode
369
+ result["output"] = cleanup_result.stdout
370
+ result["error"] = cleanup_result.stderr
371
+ result["success"] = cleanup_result.returncode == 0
372
+
373
+ after_gb = self._docker_images_and_cache_gb(refresh=True)
374
+ if before_gb is not None and after_gb is not None:
375
+ result["space_freed_gb"] = round(max(0.0, before_gb - after_gb), 3)
376
+ else:
377
+ parsed = self._parse_docker_reclaimed_gb(combined)
378
+ if parsed is not None:
379
+ result["space_freed_gb"] = parsed
380
+ elif result["success"]:
381
+ result["space_freed_gb"] = 0
382
+ except Exception as exc:
383
+ result["error"] = str(exc)
384
+
385
+ return result
386
+
387
+ def cleanup_docker_old_unused(
388
+ self,
389
+ days: int = DEFAULT_DOCKER_OLD_UNUSED_DAYS,
390
+ dry_run: bool = False,
391
+ ) -> Dict[str, Any]:
392
+ """Remove unused Docker images (and rebuildable build cache) older than N days."""
393
+ command = self.get_docker_old_unused_command(days)
394
+ hours = self.docker_old_unused_until_hours(days)
395
+ result: Dict[str, Any] = {
396
+ "service": "docker-old",
397
+ "dry_run": dry_run,
398
+ "success": False,
399
+ "space_freed_gb": 0,
400
+ "output": "",
401
+ "error": "",
402
+ "command": command,
403
+ "days": int(days),
404
+ "until_hours": hours,
405
+ }
406
+
407
+ # Age-filtered prune usually frees less than the whole reclaimable pool.
408
+ result["estimated_max_gb"] = 0.0
409
+ try:
410
+ from .service_scanner import ServiceType
411
+
412
+ services = self.scanner.scan_service(ServiceType.DOCKER)
413
+ if services:
414
+ usage = (services[0].details or {}).get("usage") or {}
415
+ result["estimated_max_gb"] = round(
416
+ float((usage.get("Images") or {}).get("reclaimable_gb", 0.0))
417
+ + float((usage.get("Build Cache") or {}).get("reclaimable_gb", 0.0)),
418
+ 3,
419
+ )
420
+ result["estimate_note"] = (
421
+ "upper bound = all reclaimable Images+Build Cache; "
422
+ f"until={hours}h usually frees less"
423
+ )
424
+ except Exception:
425
+ pass
321
426
 
427
+ if dry_run:
428
+ result["success"] = True
429
+ result["space_freed_gb"] = 0
430
+ result["output"] = (
431
+ f"[DRY RUN] Would execute: {command}\n"
432
+ f" Scope: unused images + build cache older than {int(days)} days "
433
+ f"({hours}h). Volumes and in-use images are kept.\n"
434
+ f" Note: Docker reclaimable (~{result['estimated_max_gb']:.2f} GB) "
435
+ "includes younger unused images; age filter may free much less."
436
+ )
437
+ return result
438
+
439
+ try:
440
+ before_gb = self._docker_images_and_cache_gb(refresh=True)
322
441
  cleanup_result = subprocess.run(
323
442
  command,
324
443
  shell=True,
325
444
  capture_output=True,
326
445
  text=True,
327
- timeout=DEFAULT_COMMAND_TIMEOUT,
446
+ timeout=max(DEFAULT_COMMAND_TIMEOUT, 1800),
447
+ )
448
+ combined = "\n".join(
449
+ part
450
+ for part in (cleanup_result.stdout, cleanup_result.stderr)
451
+ if part and part.strip()
328
452
  )
329
453
  result["returncode"] = cleanup_result.returncode
330
454
  result["output"] = cleanup_result.stdout
331
455
  result["error"] = cleanup_result.stderr
332
456
  result["success"] = cleanup_result.returncode == 0
333
457
 
334
- if callable(measure) and before_mb is not None:
335
- after_mb = measure(ServiceType.DOCKER, "/var/lib/docker", refresh=True)
336
- result["space_freed_gb"] = round(max(0.0, (before_mb - after_mb) / 1024), 3)
337
- elif result["success"]:
338
- # Daemon may not expose path size to non-root; keep estimate as hint.
339
- result["space_freed_gb"] = 0
458
+ after_gb = self._docker_images_and_cache_gb(refresh=True)
459
+ if before_gb is not None and after_gb is not None:
460
+ result["space_freed_gb"] = round(max(0.0, before_gb - after_gb), 3)
461
+ else:
462
+ parsed = self._parse_docker_reclaimed_gb(combined)
463
+ result["space_freed_gb"] = parsed if parsed is not None else 0
340
464
  except Exception as exc:
341
465
  result["error"] = str(exc)
342
466
 
@@ -349,7 +473,7 @@ class ServiceCleaner:
349
473
 
350
474
  Includes:
351
475
  - Ollama models not modified for ``DEFAULT_OLLAMA_OLD_UNUSED_DAYS``
352
- - Unused Docker images older than ``DEFAULT_DOCKER_OLD_UNUSED_DAYS``
476
+ - All unused Docker images + build cache (matches daemon reclaimable)
353
477
  """
354
478
  allow = {item.strip() for item in (selected_services or []) if item.strip()}
355
479
 
@@ -405,7 +529,7 @@ class ServiceCleaner:
405
529
  }
406
530
  )
407
531
 
408
- if allowed("docker", "docker-old"):
532
+ if allowed("docker", "docker-old", "docker-unused"):
409
533
  estimated_gb = 0.0
410
534
  try:
411
535
  from .service_scanner import ServiceType
@@ -421,23 +545,18 @@ class ServiceCleaner:
421
545
  except Exception:
422
546
  estimated_gb = 0.0
423
547
  if estimated_gb > 0:
424
- command = self.get_docker_old_unused_command(
425
- DEFAULT_DOCKER_OLD_UNUSED_DAYS
426
- )
548
+ command = self.get_docker_unused_command()
427
549
  actions.append(
428
550
  {
429
- "service_type": "docker-old",
430
- "cleanup_kind": "docker-old",
431
- "name": (
432
- f"Docker (nieużywane obrazy >"
433
- f"{DEFAULT_DOCKER_OLD_UNUSED_DAYS} dni)"
434
- ),
551
+ "service_type": "docker-unused",
552
+ "cleanup_kind": "docker-unused",
553
+ "name": "Docker (nieużywane obrazy)",
435
554
  "path": "/var/lib/docker",
436
555
  "size_mb": round(estimated_gb * 1024, 1),
437
556
  "size_gb": round(estimated_gb, 3),
438
557
  "description": (
439
- "Nieużywane obrazy Docker i stary build cache "
440
- f"(>{DEFAULT_DOCKER_OLD_UNUSED_DAYS} dni); bez wolumenów"
558
+ "Wszystkie nieużywane obrazy Docker i build cache "
559
+ "(bez wolumenów, bez obrazów podpiętych do kontenerów)"
441
560
  ),
442
561
  "can_cleanup": True,
443
562
  "cleanup_command": command,
@@ -447,10 +566,8 @@ class ServiceCleaner:
447
566
  "impact": "low",
448
567
  "items_count": 0,
449
568
  "details": {
450
- "days": DEFAULT_DOCKER_OLD_UNUSED_DAYS,
451
569
  "estimated_max_gb": round(estimated_gb, 3),
452
570
  },
453
- "days": DEFAULT_DOCKER_OLD_UNUSED_DAYS,
454
571
  }
455
572
  )
456
573