fixos 2.2.32__tar.gz → 2.2.34__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 (163) hide show
  1. {fixos-2.2.32 → fixos-2.2.34}/CHANGELOG.md +18 -0
  2. {fixos-2.2.32 → fixos-2.2.34}/PKG-INFO +2 -2
  3. {fixos-2.2.32 → fixos-2.2.34}/README.md +1 -1
  4. {fixos-2.2.32 → fixos-2.2.34}/fixos/__init__.py +1 -1
  5. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/cleanup_cmd.py +4 -0
  6. {fixos-2.2.32 → fixos-2.2.34}/fixos/constants.py +1 -0
  7. fixos-2.2.34/fixos/diagnostics/cache_discovery.py +251 -0
  8. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/service_cleanup.py +161 -0
  9. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/service_scanner.py +90 -2
  10. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/PKG-INFO +2 -2
  11. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/SOURCES.txt +2 -0
  12. {fixos-2.2.32 → fixos-2.2.34}/pyproject.toml +1 -1
  13. {fixos-2.2.32 → fixos-2.2.34}/setup.py +1 -1
  14. fixos-2.2.34/tests/unit/test_cache_discovery.py +116 -0
  15. {fixos-2.2.32 → fixos-2.2.34}/.env.example +0 -0
  16. {fixos-2.2.32 → fixos-2.2.34}/LICENSE +0 -0
  17. {fixos-2.2.32 → fixos-2.2.34}/MANIFEST.in +0 -0
  18. {fixos-2.2.32 → fixos-2.2.34}/docker/README.md +0 -0
  19. {fixos-2.2.32 → fixos-2.2.34}/docker/TEST_RESULTS.md +0 -0
  20. {fixos-2.2.32 → fixos-2.2.34}/docker/TEST_RESULTS_V2.md +0 -0
  21. {fixos-2.2.32 → fixos-2.2.34}/docker/alpine/Dockerfile +0 -0
  22. {fixos-2.2.32 → fixos-2.2.34}/docker/arch/Dockerfile +0 -0
  23. {fixos-2.2.32 → fixos-2.2.34}/docker/base/Dockerfile +0 -0
  24. {fixos-2.2.32 → fixos-2.2.34}/docker/broken-audio/Dockerfile +0 -0
  25. {fixos-2.2.32 → fixos-2.2.34}/docker/broken-full/Dockerfile +0 -0
  26. {fixos-2.2.32 → fixos-2.2.34}/docker/broken-network/Dockerfile +0 -0
  27. {fixos-2.2.32 → fixos-2.2.34}/docker/broken-thumbnails/Dockerfile +0 -0
  28. {fixos-2.2.32 → fixos-2.2.34}/docker/debian/Dockerfile +0 -0
  29. {fixos-2.2.32 → fixos-2.2.34}/docker/docker-compose.multi-system.yml +0 -0
  30. {fixos-2.2.32 → fixos-2.2.34}/docker/docker-compose.yml +0 -0
  31. {fixos-2.2.32 → fixos-2.2.34}/docker/fedora/Dockerfile +0 -0
  32. {fixos-2.2.32 → fixos-2.2.34}/docker/test-multi-system.sh +0 -0
  33. {fixos-2.2.32 → fixos-2.2.34}/docker/test-scenarios.sh +0 -0
  34. {fixos-2.2.32 → fixos-2.2.34}/docker/ubuntu/Dockerfile +0 -0
  35. {fixos-2.2.32 → fixos-2.2.34}/docker/validate-scenario.py +0 -0
  36. {fixos-2.2.32 → fixos-2.2.34}/docs/examples/advanced_usage.py +0 -0
  37. {fixos-2.2.32 → fixos-2.2.34}/docs/examples/quickstart.py +0 -0
  38. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/__init__.py +0 -0
  39. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/autonomous.py +0 -0
  40. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/autonomous_session.py +0 -0
  41. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/hitl.py +0 -0
  42. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/hitl_session.py +0 -0
  43. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/session_core.py +0 -0
  44. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/session_handlers.py +0 -0
  45. {fixos-2.2.32 → fixos-2.2.34}/fixos/agent/session_io.py +0 -0
  46. {fixos-2.2.32 → fixos-2.2.34}/fixos/anonymizer.py +0 -0
  47. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/__init__.py +0 -0
  48. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/_cleanup_flatpak.py +0 -0
  49. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/_cleanup_home.py +0 -0
  50. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/_cleanup_snap.py +0 -0
  51. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/_cleanup_system.py +0 -0
  52. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/_cleanup_utils.py +0 -0
  53. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/ask_cmd.py +0 -0
  54. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/config_cmd.py +0 -0
  55. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/features_cmd.py +0 -0
  56. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/fix_cmd.py +0 -0
  57. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/history_cmd.py +0 -0
  58. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/main.py +0 -0
  59. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/orchestrate_cmd.py +0 -0
  60. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/output_formatter.py +0 -0
  61. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/profile_cmd.py +0 -0
  62. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/provider_cmd.py +0 -0
  63. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/quickfix_cmd.py +0 -0
  64. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/report_cmd.py +0 -0
  65. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/rollback_cmd.py +0 -0
  66. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/scan_cmd.py +0 -0
  67. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/shared.py +0 -0
  68. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/token_cmd.py +0 -0
  69. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli/watch_cmd.py +0 -0
  70. {fixos-2.2.32 → fixos-2.2.34}/fixos/cli.py +0 -0
  71. {fixos-2.2.32 → fixos-2.2.34}/fixos/config.py +0 -0
  72. {fixos-2.2.32 → fixos-2.2.34}/fixos/config_interactive.py +0 -0
  73. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/__init__.py +0 -0
  74. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_flatpak_analysis_mixin.py +0 -0
  75. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_flatpak_execution_mixin.py +0 -0
  76. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_flatpak_recommendations_mixin.py +0 -0
  77. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_storage_container_mixin.py +0 -0
  78. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_storage_system_mixin.py +0 -0
  79. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/_storage_user_mixin.py +0 -0
  80. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/__init__.py +0 -0
  81. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/_shared.py +0 -0
  82. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/audio.py +0 -0
  83. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/file_analysis.py +0 -0
  84. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/hardware.py +0 -0
  85. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/packages.py +0 -0
  86. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/resources.py +0 -0
  87. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/security.py +0 -0
  88. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/storage_optimization.py +0 -0
  89. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/system_core.py +0 -0
  90. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/checks/thumbnails.py +0 -0
  91. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  92. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/disk_analyzer.py +0 -0
  93. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  94. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/service_details.py +0 -0
  95. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/storage_analyzer.py +0 -0
  96. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/system_checks.py +0 -0
  97. {fixos-2.2.32 → fixos-2.2.34}/fixos/diagnostics/utils.py +0 -0
  98. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/__init__.py +0 -0
  99. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/auditor.py +0 -0
  100. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/catalog.py +0 -0
  101. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/installer.py +0 -0
  102. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/profiles.py +0 -0
  103. {fixos-2.2.32 → fixos-2.2.34}/fixos/features/renderer.py +0 -0
  104. {fixos-2.2.32 → fixos-2.2.34}/fixos/fixes/__init__.py +0 -0
  105. {fixos-2.2.32 → fixos-2.2.34}/fixos/interactive/__init__.py +0 -0
  106. {fixos-2.2.32 → fixos-2.2.34}/fixos/interactive/cleanup_planner.py +0 -0
  107. {fixos-2.2.32 → fixos-2.2.34}/fixos/llm_shell.py +0 -0
  108. {fixos-2.2.32 → fixos-2.2.34}/fixos/orchestrator/__init__.py +0 -0
  109. {fixos-2.2.32 → fixos-2.2.34}/fixos/orchestrator/executor.py +0 -0
  110. {fixos-2.2.32 → fixos-2.2.34}/fixos/orchestrator/graph.py +0 -0
  111. {fixos-2.2.32 → fixos-2.2.34}/fixos/orchestrator/orchestrator.py +0 -0
  112. {fixos-2.2.32 → fixos-2.2.34}/fixos/orchestrator/rollback.py +0 -0
  113. {fixos-2.2.32 → fixos-2.2.34}/fixos/platform_utils.py +0 -0
  114. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/__init__.py +0 -0
  115. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/base.py +0 -0
  116. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/__init__.py +0 -0
  117. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/audio.py +0 -0
  118. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/disk.py +0 -0
  119. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/hardware.py +0 -0
  120. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/resources.py +0 -0
  121. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/security.py +0 -0
  122. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/builtin/thumbnails.py +0 -0
  123. {fixos-2.2.32 → fixos-2.2.34}/fixos/plugins/registry.py +0 -0
  124. {fixos-2.2.32 → fixos-2.2.34}/fixos/profiles/__init__.py +0 -0
  125. {fixos-2.2.32 → fixos-2.2.34}/fixos/providers/__init__.py +0 -0
  126. {fixos-2.2.32 → fixos-2.2.34}/fixos/providers/llm.py +0 -0
  127. {fixos-2.2.32 → fixos-2.2.34}/fixos/providers/llm_analyzer.py +0 -0
  128. {fixos-2.2.32 → fixos-2.2.34}/fixos/providers/schemas.py +0 -0
  129. {fixos-2.2.32 → fixos-2.2.34}/fixos/system_checks.py +0 -0
  130. {fixos-2.2.32 → fixos-2.2.34}/fixos/utils/__init__.py +0 -0
  131. {fixos-2.2.32 → fixos-2.2.34}/fixos/utils/anonymizer.py +0 -0
  132. {fixos-2.2.32 → fixos-2.2.34}/fixos/utils/terminal.py +0 -0
  133. {fixos-2.2.32 → fixos-2.2.34}/fixos/utils/timeout.py +0 -0
  134. {fixos-2.2.32 → fixos-2.2.34}/fixos/utils/web_search.py +0 -0
  135. {fixos-2.2.32 → fixos-2.2.34}/fixos/watch.py +0 -0
  136. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/dependency_links.txt +0 -0
  137. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/entry_points.txt +0 -0
  138. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/requires.txt +0 -0
  139. {fixos-2.2.32 → fixos-2.2.34}/fixos.egg-info/top_level.txt +0 -0
  140. {fixos-2.2.32 → fixos-2.2.34}/pytest.ini +0 -0
  141. {fixos-2.2.32 → fixos-2.2.34}/requirements-dev.txt +0 -0
  142. {fixos-2.2.32 → fixos-2.2.34}/requirements.txt +0 -0
  143. {fixos-2.2.32 → fixos-2.2.34}/scripts/pyqual-calibrate.py +0 -0
  144. {fixos-2.2.32 → fixos-2.2.34}/setup.cfg +0 -0
  145. {fixos-2.2.32 → fixos-2.2.34}/tests/__init__.py +0 -0
  146. {fixos-2.2.32 → fixos-2.2.34}/tests/conftest.py +0 -0
  147. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/__init__.py +0 -0
  148. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_anonymization_layers.py +0 -0
  149. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_audio_broken.py +0 -0
  150. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_cli.py +0 -0
  151. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_diagnostics_integration.py +0 -0
  152. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_executor.py +0 -0
  153. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_multi_system.py +0 -0
  154. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_network_broken.py +0 -0
  155. {fixos-2.2.32 → fixos-2.2.34}/tests/e2e/test_thumbnails_broken.py +0 -0
  156. {fixos-2.2.32 → fixos-2.2.34}/tests/test_fixos.py +0 -0
  157. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/__init__.py +0 -0
  158. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_anonymizer.py +0 -0
  159. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_core.py +0 -0
  160. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_executor.py +0 -0
  161. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_orchestrator.py +0 -0
  162. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_service_cleanup.py +0 -0
  163. {fixos-2.2.32 → fixos-2.2.34}/tests/unit/test_service_scanner.py +0 -0
@@ -150,6 +150,24 @@ fix(goal): code analysis engine
150
150
  - **refactor(cli):** Usunięto zduplikowany kod ujednolicając funkcje analizy dysku do wspólnego helpera `_run_disk_analysis`.
151
151
  - **refactor(ui):** Usunięto ikony Unicode z CLI i sformatowano wyjście `stderr` oraz standardowego logowania na czysty kod Markdown dla poprawy czytelności w oknach terminalowych.
152
152
 
153
+ ## [2.2.34] - 2026-06-16
154
+
155
+ ### Test
156
+ - Update tests/unit/test_cache_discovery.py
157
+
158
+ ### Other
159
+ - Update fixos/cli/cleanup_cmd.py
160
+ - Update fixos/constants.py
161
+ - Update fixos/diagnostics/cache_discovery.py
162
+ - Update fixos/diagnostics/service_cleanup.py
163
+ - Update fixos/diagnostics/service_scanner.py
164
+ - Update uv.lock
165
+
166
+ ## [2.2.33] - 2026-06-16
167
+
168
+ ### Other
169
+ - Update uv.lock
170
+
153
171
  ## [2.2.32] - 2026-06-16
154
172
 
155
173
  ### Test
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.32
3
+ Version: 2.2.34
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,7 +63,7 @@ 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.32-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.34-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
67
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.66-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-30.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
69
  - 🤖 **LLM usage:** $3.6649 (131 commits)
@@ -19,7 +19,7 @@ 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.32-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
22
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.34-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
23
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.66-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-30.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
25
  - 🤖 **LLM usage:** $3.6649 (131 commits)
@@ -1,3 +1,3 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
2
 
3
- __version__ = "2.2.32"
3
+ __version__ = "2.2.34"
@@ -148,6 +148,10 @@ def _display_unsafe_services(services: list) -> None:
148
148
  "flatpak": ServiceType.FLATPAK,
149
149
  "docker": ServiceType.DOCKER,
150
150
  "ollama": ServiceType.OLLAMA,
151
+ "steam": ServiceType.STEAM,
152
+ "minikube": ServiceType.MINIKUBE,
153
+ "lmstudio": ServiceType.LMSTUDIO,
154
+ "generic_cache": ServiceType.GENERIC_CACHE,
151
155
  }
152
156
 
153
157
  for service_type, svcs in service_groups.items():
@@ -98,6 +98,7 @@ MIN_ORPHANED_PACKAGES = 5
98
98
  MIN_HOME_LARGE_FILE_MB = 200
99
99
  MIN_HOME_LARGE_DIR_MB = 500
100
100
  DEFAULT_CLEANUP_THRESHOLD_MB = 500
101
+ GENERIC_CACHE_THRESHOLD_MB = 1024
101
102
  MAX_HOME_LARGE_FILES_DISPLAY = 30
102
103
  MAX_HOME_LARGE_DIRS_DISPLAY = 20
103
104
  MIN_STALE_DAYS = 90
@@ -0,0 +1,251 @@
1
+ """
2
+ Generic cache discovery for fixOS cleanup.
3
+
4
+ Finds large cache directories under ~/.cache and Electron app caches under
5
+ ~/.config that are not already covered by known service scanners.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import glob
11
+ import os
12
+ from typing import Callable, Iterable, Set, TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from .service_scanner import ServiceDataInfo, ServiceType
16
+
17
+ from ..constants import GENERIC_CACHE_THRESHOLD_MB
18
+
19
+ # Top-level ~/.cache names already handled by dedicated ServiceType scanners.
20
+ KNOWN_CACHE_DIR_NAMES = frozenset(
21
+ {
22
+ "pip",
23
+ "npm",
24
+ "yarn",
25
+ "pypoetry",
26
+ "huggingface",
27
+ "google-chrome",
28
+ "microsoft-edge",
29
+ "mozilla",
30
+ "thumbnails",
31
+ "gcloud",
32
+ "JetBrains",
33
+ "gradle",
34
+ "uv",
35
+ "torch",
36
+ "nvidia",
37
+ "mesa_shader_cache",
38
+ "ms-playwright",
39
+ "puppeteer",
40
+ "helm",
41
+ "bazel",
42
+ "gh",
43
+ "lm-studio",
44
+ "BraveSoftware",
45
+ "spotify",
46
+ "log",
47
+ }
48
+ )
49
+
50
+ # Electron/Chromium apps scanned explicitly elsewhere or via dedicated types.
51
+ KNOWN_CONFIG_APPS = frozenset(
52
+ {
53
+ "google-chrome",
54
+ "microsoft-edge",
55
+ "BraveSoftware",
56
+ "Code",
57
+ "Cursor",
58
+ "discord",
59
+ "Slack",
60
+ "spotify",
61
+ }
62
+ )
63
+
64
+ ELECTRON_CACHE_DIR_NAMES = (
65
+ "Cache",
66
+ "Code Cache",
67
+ "GPUCache",
68
+ "DawnCache",
69
+ "GrShaderCache",
70
+ "ShaderCache",
71
+ "Service Worker",
72
+ )
73
+
74
+ GENERIC_SAFE_NAME_HINTS = (
75
+ "cache",
76
+ "shader",
77
+ "tmp",
78
+ "temp",
79
+ "log",
80
+ "crash",
81
+ "thumb",
82
+ )
83
+
84
+
85
+ def normalize_path(path: str) -> str:
86
+ return os.path.realpath(os.path.expanduser(path))
87
+
88
+
89
+ def path_is_covered(path: str, covered_paths: Iterable[str]) -> bool:
90
+ """Return True when path is already represented by a known service scan."""
91
+ normalized = normalize_path(path)
92
+ for covered in covered_paths:
93
+ covered_norm = normalize_path(covered)
94
+ if normalized == covered_norm:
95
+ return True
96
+ if normalized.startswith(f"{covered_norm}/"):
97
+ return True
98
+ if covered_norm.startswith(f"{normalized}/"):
99
+ return True
100
+ return False
101
+
102
+
103
+ def is_generic_cache_safe(path: str) -> bool:
104
+ """Heuristic safety check for unknown cache directories."""
105
+ base = os.path.basename(path.rstrip("/")).lower()
106
+ return any(hint in base for hint in GENERIC_SAFE_NAME_HINTS)
107
+
108
+
109
+ def discover_additional_caches(
110
+ get_size_mb: Callable[[str], float],
111
+ threshold_mb: int,
112
+ covered_paths: Set[str],
113
+ ) -> list["ServiceDataInfo"]:
114
+ """Discover large caches not already covered by known service scanners."""
115
+ from .service_scanner import ServiceDataInfo, ServiceType
116
+
117
+ generic_threshold = max(threshold_mb, GENERIC_CACHE_THRESHOLD_MB)
118
+ results: list[ServiceDataInfo] = []
119
+ seen_paths: set[str] = set()
120
+
121
+ for info in _discover_xdg_cache_dirs(get_size_mb, generic_threshold, covered_paths):
122
+ norm = normalize_path(info.path)
123
+ if norm not in seen_paths:
124
+ seen_paths.add(norm)
125
+ results.append(info)
126
+
127
+ for info in _discover_electron_caches(get_size_mb, threshold_mb, covered_paths):
128
+ norm = normalize_path(info.path)
129
+ if norm not in seen_paths:
130
+ seen_paths.add(norm)
131
+ results.append(info)
132
+
133
+ results.sort(key=lambda item: item.size_mb, reverse=True)
134
+ return results
135
+
136
+
137
+ def _discover_xdg_cache_dirs(
138
+ get_size_mb: Callable[[str], float],
139
+ threshold_mb: int,
140
+ covered_paths: Set[str],
141
+ ) -> list["ServiceDataInfo"]:
142
+ cache_root = os.path.expanduser("~/.cache")
143
+ if not os.path.isdir(cache_root):
144
+ return []
145
+
146
+ results: list[ServiceDataInfo] = []
147
+ try:
148
+ entries = sorted(os.listdir(cache_root))
149
+ except OSError:
150
+ return []
151
+
152
+ for entry in entries:
153
+ if entry in KNOWN_CACHE_DIR_NAMES:
154
+ continue
155
+
156
+ path = os.path.join(cache_root, entry)
157
+ if not os.path.isdir(path):
158
+ continue
159
+ if path_is_covered(path, covered_paths):
160
+ continue
161
+
162
+ size_mb = get_size_mb(path)
163
+ if size_mb < threshold_mb:
164
+ continue
165
+
166
+ safe = is_generic_cache_safe(path)
167
+ results.append(
168
+ _build_generic_entry(
169
+ label=f"Cache: {entry}",
170
+ path=path,
171
+ size_mb=size_mb,
172
+ safe=safe,
173
+ description=f"Discovered cache directory (~/.cache/{entry})",
174
+ )
175
+ )
176
+
177
+ return results
178
+
179
+
180
+ def _discover_electron_caches(
181
+ get_size_mb: Callable[[str], float],
182
+ threshold_mb: int,
183
+ covered_paths: Set[str],
184
+ ) -> list["ServiceDataInfo"]:
185
+ from .service_scanner import ServiceType
186
+
187
+ config_root = os.path.expanduser("~/.config")
188
+ if not os.path.isdir(config_root):
189
+ return []
190
+
191
+ results: list[ServiceDataInfo] = []
192
+ for app_dir in sorted(glob.glob(os.path.join(config_root, "*"))):
193
+ app_name = os.path.basename(app_dir)
194
+ if app_name in KNOWN_CONFIG_APPS or not os.path.isdir(app_dir):
195
+ continue
196
+
197
+ for cache_name in ELECTRON_CACHE_DIR_NAMES:
198
+ for path in glob.glob(os.path.join(app_dir, "*", cache_name)):
199
+ if not os.path.isdir(path):
200
+ continue
201
+ if path_is_covered(path, covered_paths):
202
+ continue
203
+
204
+ size_mb = get_size_mb(path)
205
+ if size_mb < threshold_mb:
206
+ continue
207
+
208
+ results.append(
209
+ _build_generic_entry(
210
+ label=f"{app_name} cache",
211
+ path=path,
212
+ size_mb=size_mb,
213
+ safe=True,
214
+ description=f"Electron/Chromium cache for {app_name}",
215
+ service_type=ServiceType.ELECTRON,
216
+ )
217
+ )
218
+
219
+ return results
220
+
221
+
222
+ def _build_generic_entry(
223
+ *,
224
+ label: str,
225
+ path: str,
226
+ size_mb: float,
227
+ safe: bool,
228
+ description: str,
229
+ service_type: "ServiceType" = None,
230
+ ) -> "ServiceDataInfo":
231
+ from .service_cleanup import ServiceCleaner
232
+ from .service_scanner import ServiceDataInfo, ServiceType
233
+
234
+ if service_type is None:
235
+ service_type = ServiceType.GENERIC_CACHE
236
+ size_gb = size_mb / 1024
237
+ cleanup_command = ServiceCleaner.get_cleanup_command(service_type, path)
238
+ return ServiceDataInfo(
239
+ service_type=service_type,
240
+ name=label,
241
+ path=path,
242
+ size_mb=round(size_mb, 2),
243
+ size_gb=round(size_gb, 3),
244
+ description=description,
245
+ can_cleanup=True,
246
+ cleanup_command=cleanup_command,
247
+ preview_command=ServiceCleaner.get_preview_command(service_type, path),
248
+ safe_to_cleanup=safe,
249
+ impact="high" if size_gb > 1.0 else "medium",
250
+ details={"discovered": True, "source": "cache_discovery"},
251
+ )
@@ -150,6 +150,28 @@ class ServiceCleaner:
150
150
 
151
151
  return False
152
152
 
153
+ if service_type == ServiceType.BRAVE:
154
+ normalized_path = os.path.expanduser(path or "").rstrip("/")
155
+ if "/Cache" in normalized_path or normalized_path.endswith(
156
+ ("GPUCache", "Code Cache")
157
+ ):
158
+ return True
159
+ return normalized_path.endswith("BraveSoftware") or "/BraveSoftware/" in (
160
+ normalized_path
161
+ )
162
+
163
+ if service_type == ServiceType.STEAM:
164
+ normalized_path = os.path.expanduser(path or "").rstrip("/")
165
+ safe_suffixes = ("shadercache", "appcache")
166
+ return any(normalized_path.endswith(suffix) for suffix in safe_suffixes)
167
+
168
+ if service_type in (ServiceType.GENERIC_CACHE, ServiceType.ELECTRON):
169
+ base = os.path.basename(os.path.expanduser(path or "").rstrip("/")).lower()
170
+ return any(
171
+ hint in base
172
+ for hint in ("cache", "shader", "tmp", "temp", "log", "crash", "thumb")
173
+ )
174
+
153
175
  safe_services = {
154
176
  # Package caches (can be re-downloaded)
155
177
  ServiceType.NPM,
@@ -161,6 +183,19 @@ class ServiceCleaner:
161
183
  ServiceType.MAVEN,
162
184
  ServiceType.CARGO,
163
185
  ServiceType.GO,
186
+ ServiceType.UV,
187
+ ServiceType.TORCH,
188
+ ServiceType.BUN,
189
+ ServiceType.PLAYWRIGHT,
190
+ ServiceType.CCACHE,
191
+ ServiceType.HELM,
192
+ ServiceType.BAZEL,
193
+ ServiceType.GH,
194
+ ServiceType.NVIDIA,
195
+ ServiceType.DISCORD,
196
+ ServiceType.SLACK,
197
+ ServiceType.SPOTIFY,
198
+ ServiceType.BRAVE,
164
199
  # System caches
165
200
  ServiceType.APT,
166
201
  ServiceType.DNF,
@@ -255,6 +290,57 @@ class ServiceCleaner:
255
290
  ]
256
291
  )
257
292
 
293
+ elif service_type == ServiceType.STEAM:
294
+ hints.extend(
295
+ [
296
+ "🎮 STEAM CLEANUP:",
297
+ " rm -rf ~/.local/share/Steam/steamapps/shadercache",
298
+ " # Safe: shader cache rebuilds automatically",
299
+ "",
300
+ " steamcmd +app_update ...",
301
+ " # Games themselves require manual uninstall in Steam UI",
302
+ "",
303
+ "📊 Check usage:",
304
+ " du -sh ~/.local/share/Steam/steamapps/common/* | sort -hr | head",
305
+ ]
306
+ )
307
+
308
+ elif service_type == ServiceType.MINIKUBE:
309
+ hints.extend(
310
+ [
311
+ "☸️ MINIKUBE CLEANUP:",
312
+ " minikube stop",
313
+ " minikube delete --all",
314
+ " # Removes local Kubernetes cluster and VM data",
315
+ "",
316
+ "💡 Docker driver images may remain in Docker cache",
317
+ ]
318
+ )
319
+
320
+ elif service_type == ServiceType.LMSTUDIO:
321
+ hints.extend(
322
+ [
323
+ "🤖 LM STUDIO CLEANUP:",
324
+ " ls ~/.lmstudio/models",
325
+ " # Review downloaded models before deleting",
326
+ "",
327
+ " rm -rf ~/.lmstudio/models/<model>",
328
+ " # Remove one model at a time",
329
+ ]
330
+ )
331
+
332
+ elif service_type == ServiceType.GENERIC_CACHE:
333
+ hints.extend(
334
+ [
335
+ "📂 UNKNOWN CACHE:",
336
+ " du -sh ~/.cache/<dir>",
337
+ " # Inspect contents before deleting",
338
+ "",
339
+ " rm -rf <path>",
340
+ " # Only if you recognize it as rebuildable cache",
341
+ ]
342
+ )
343
+
258
344
  return hints
259
345
 
260
346
  @staticmethod
@@ -326,6 +412,24 @@ class ServiceCleaner:
326
412
  ServiceType.THUMBNAILS: "Thumbnail cache",
327
413
  ServiceType.TRASH: "Trash/Recycle Bin",
328
414
  ServiceType.LOGS: "Application logs",
415
+ ServiceType.NVIDIA: "NVIDIA and Mesa GPU shader cache",
416
+ ServiceType.UV: "uv Python package manager cache",
417
+ ServiceType.TORCH: "PyTorch hub and model cache",
418
+ ServiceType.BUN: "Bun JavaScript runtime cache",
419
+ ServiceType.PLAYWRIGHT: "Playwright/Puppeteer browser binaries",
420
+ ServiceType.CCACHE: "C/C++ compiler cache (ccache/sccache)",
421
+ ServiceType.HELM: "Helm chart cache",
422
+ ServiceType.MINIKUBE: "Minikube local Kubernetes cluster data",
423
+ ServiceType.STEAM: "Steam games, shaders and client cache",
424
+ ServiceType.LMSTUDIO: "LM Studio local AI models",
425
+ ServiceType.BRAVE: "Brave browser cache",
426
+ ServiceType.DISCORD: "Discord client cache",
427
+ ServiceType.SLACK: "Slack client cache",
428
+ ServiceType.SPOTIFY: "Spotify offline/cache data",
429
+ ServiceType.BAZEL: "Bazel build cache",
430
+ ServiceType.GH: "GitHub CLI cache",
431
+ ServiceType.ELECTRON: "Electron/Chromium application cache",
432
+ ServiceType.GENERIC_CACHE: "Discovered cache directory",
329
433
  }
330
434
  return descriptions.get(service_type, f"{service_type.value} data")
331
435
 
@@ -398,6 +502,24 @@ class ServiceCleaner:
398
502
  ServiceType.THUMBNAILS: "rm -rf ~/.cache/thumbnails/* ~/.thumbnails/*",
399
503
  ServiceType.TRASH: "rm -rf ~/.local/share/Trash/* ~/.Trash/*",
400
504
  ServiceType.LOGS: "find ~/.cache/log ~/.local/state -name '*.log' -mtime +7 -delete 2>/dev/null; journalctl --vacuum-time=7d 2>/dev/null || true",
505
+ ServiceType.NVIDIA: "rm -rf ~/.cache/nvidia ~/.nv/ComputeCache ~/.cache/mesa_shader_cache",
506
+ ServiceType.UV: "uv cache clean || rm -rf ~/.cache/uv ~/.local/share/uv",
507
+ ServiceType.TORCH: "rm -rf ~/.cache/torch ~/.torch",
508
+ ServiceType.BUN: "rm -rf ~/.bun/install/cache",
509
+ ServiceType.PLAYWRIGHT: "rm -rf ~/.cache/ms-playwright ~/.cache/puppeteer",
510
+ ServiceType.CCACHE: "ccache -C 2>/dev/null || rm -rf ~/.ccache; rm -rf ~/.cache/sccache",
511
+ ServiceType.HELM: "helm cache cleanup 2>/dev/null || rm -rf ~/.cache/helm",
512
+ ServiceType.MINIKUBE: "minikube delete --all 2>/dev/null || rm -rf ~/.minikube",
513
+ ServiceType.STEAM: ServiceCleaner._steam_cleanup_command(path),
514
+ ServiceType.LMSTUDIO: "rm -rf ~/.lmstudio/models/* ~/.cache/lm-studio",
515
+ ServiceType.BRAVE: ServiceCleaner._brave_cleanup_command(path),
516
+ ServiceType.DISCORD: "rm -rf ~/.config/discord/Cache ~/.config/discord/Code Cache ~/.config/discord/GPUCache",
517
+ ServiceType.SLACK: "rm -rf ~/.config/Slack/Cache ~/.config/Slack/Code Cache ~/.config/Slack/Service Worker",
518
+ ServiceType.SPOTIFY: "rm -rf ~/.cache/spotify ~/.config/spotify/Data",
519
+ ServiceType.BAZEL: "rm -rf ~/.cache/bazel",
520
+ ServiceType.GH: "rm -rf ~/.cache/gh",
521
+ ServiceType.ELECTRON: f"rm -rf {shlex.quote(path)}",
522
+ ServiceType.GENERIC_CACHE: f"rm -rf {shlex.quote(path)}",
401
523
  }
402
524
  return commands.get(service_type, f"rm -rf {path}")
403
525
 
@@ -435,6 +557,29 @@ class ServiceCleaner:
435
557
  "-prune -exec rm -rf {} + 2>/dev/null || true"
436
558
  )
437
559
 
560
+ @staticmethod
561
+ def _brave_cleanup_command(path: str) -> str:
562
+ expanded_path = os.path.expanduser(path).rstrip("/")
563
+ quoted_path = shlex.quote(expanded_path)
564
+ cache_root = os.path.expanduser("~/.cache/BraveSoftware").rstrip("/")
565
+ if expanded_path == cache_root or expanded_path.startswith(f"{cache_root}/"):
566
+ return f"rm -rf {quoted_path}"
567
+ return (
568
+ "rm -rf ~/.cache/BraveSoftware 2>/dev/null || true; "
569
+ f"rm -rf {quoted_path} 2>/dev/null || true"
570
+ )
571
+
572
+ @staticmethod
573
+ def _steam_cleanup_command(path: str) -> str:
574
+ expanded_path = os.path.expanduser(path).rstrip("/")
575
+ quoted_path = shlex.quote(expanded_path)
576
+ if expanded_path.endswith(("shadercache", "appcache")):
577
+ return f"rm -rf {quoted_path}"
578
+ return (
579
+ "rm -rf ~/.local/share/Steam/steamapps/shadercache "
580
+ "~/.local/share/Steam/appcache 2>/dev/null || true"
581
+ )
582
+
438
583
  @staticmethod
439
584
  def get_preview_command(service_type, path: str) -> str:
440
585
  """Get preview command for service."""
@@ -504,6 +649,22 @@ class ServiceCleaner:
504
649
  ServiceType.THUMBNAILS: "du -sh ~/.cache/thumbnails 2>/dev/null && find ~/.cache/thumbnails -type f | wc -l",
505
650
  ServiceType.TRASH: "du -sh ~/.local/share/Trash 2>/dev/null || du -sh ~/.Trash",
506
651
  ServiceType.LOGS: "find ~/.cache/log ~/.local/state /var/log ~/.var/log 2>/dev/null -name '*.log' | wc -l && du -sh ~/.cache/log 2>/dev/null || du -sh /var/log 2>/dev/null",
652
+ ServiceType.NVIDIA: "du -sh ~/.cache/nvidia ~/.nv/ComputeCache 2>/dev/null",
653
+ ServiceType.UV: "uv cache dir 2>/dev/null || du -sh ~/.cache/uv",
654
+ ServiceType.TORCH: "du -sh ~/.cache/torch 2>/dev/null",
655
+ ServiceType.BUN: "du -sh ~/.bun/install/cache 2>/dev/null",
656
+ ServiceType.PLAYWRIGHT: "du -sh ~/.cache/ms-playwright ~/.cache/puppeteer 2>/dev/null",
657
+ ServiceType.CCACHE: "ccache -s 2>/dev/null || du -sh ~/.ccache",
658
+ ServiceType.HELM: "helm cache stats 2>/dev/null || du -sh ~/.cache/helm",
659
+ ServiceType.MINIKUBE: "minikube status 2>/dev/null || du -sh ~/.minikube",
660
+ ServiceType.STEAM: "du -sh ~/.local/share/Steam/steamapps/common 2>/dev/null | sort -hr | head -10",
661
+ ServiceType.LMSTUDIO: "du -sh ~/.lmstudio/models 2>/dev/null || ls ~/.lmstudio/models",
662
+ ServiceType.BRAVE: "du -sh ~/.cache/BraveSoftware 2>/dev/null",
663
+ ServiceType.DISCORD: "du -sh ~/.config/discord/Cache 2>/dev/null",
664
+ ServiceType.SLACK: "du -sh ~/.config/Slack/Cache 2>/dev/null",
665
+ ServiceType.SPOTIFY: "du -sh ~/.cache/spotify 2>/dev/null",
666
+ ServiceType.BAZEL: "du -sh ~/.cache/bazel 2>/dev/null",
667
+ ServiceType.GH: "du -sh ~/.cache/gh 2>/dev/null",
507
668
  }
508
669
  return previews.get(
509
670
  service_type, f"du -sh {path} 2>/dev/null && ls -la {path} | head -20"
@@ -70,6 +70,24 @@ class ServiceType(Enum):
70
70
  THUMBNAILS = "thumbnails"
71
71
  TRASH = "trash"
72
72
  LOGS = "logs"
73
+ NVIDIA = "nvidia"
74
+ UV = "uv"
75
+ TORCH = "torch"
76
+ BUN = "bun"
77
+ PLAYWRIGHT = "playwright"
78
+ CCACHE = "ccache"
79
+ HELM = "helm"
80
+ MINIKUBE = "minikube"
81
+ STEAM = "steam"
82
+ LMSTUDIO = "lmstudio"
83
+ BRAVE = "brave"
84
+ DISCORD = "discord"
85
+ SLACK = "slack"
86
+ SPOTIFY = "spotify"
87
+ BAZEL = "bazel"
88
+ GH = "gh"
89
+ ELECTRON = "electron"
90
+ GENERIC_CACHE = "generic_cache"
73
91
  UNKNOWN = "unknown"
74
92
 
75
93
 
@@ -179,8 +197,54 @@ class ServiceDataScanner:
179
197
  ServiceType.THUMBNAILS: ["~/.cache/thumbnails", "~/.thumbnails"],
180
198
  ServiceType.TRASH: ["~/.local/share/Trash", "~/.Trash"],
181
199
  ServiceType.LOGS: ["~/.cache/log", "~/.local/state"],
200
+ ServiceType.NVIDIA: [
201
+ "~/.cache/nvidia",
202
+ "~/.nv/ComputeCache",
203
+ "~/.cache/mesa_shader_cache",
204
+ ],
205
+ ServiceType.UV: ["~/.cache/uv", "~/.local/share/uv"],
206
+ ServiceType.TORCH: ["~/.cache/torch", "~/.torch"],
207
+ ServiceType.BUN: ["~/.bun/install/cache"],
208
+ ServiceType.PLAYWRIGHT: ["~/.cache/ms-playwright", "~/.cache/puppeteer"],
209
+ ServiceType.CCACHE: ["~/.ccache", "~/.cache/sccache"],
210
+ ServiceType.HELM: ["~/.cache/helm"],
211
+ ServiceType.MINIKUBE: ["~/.minikube"],
212
+ ServiceType.STEAM: [
213
+ "~/.local/share/Steam/steamapps/shadercache",
214
+ "~/.local/share/Steam/appcache",
215
+ "~/.local/share/Steam",
216
+ "~/.steam",
217
+ ],
218
+ ServiceType.LMSTUDIO: [
219
+ "~/.cache/lm-studio",
220
+ "~/.lmstudio/models",
221
+ "~/.lmstudio/.internal/cache",
222
+ ],
223
+ ServiceType.BRAVE: [
224
+ "~/.cache/BraveSoftware",
225
+ "~/.config/BraveSoftware/Brave-Browser/*/Cache",
226
+ "~/.config/BraveSoftware/Brave-Browser/*/Code Cache",
227
+ "~/.config/BraveSoftware/Brave-Browser/*/GPUCache",
228
+ ],
229
+ ServiceType.DISCORD: [
230
+ "~/.config/discord/Cache",
231
+ "~/.config/discord/Code Cache",
232
+ "~/.config/discord/GPUCache",
233
+ ],
234
+ ServiceType.SLACK: [
235
+ "~/.config/Slack/Cache",
236
+ "~/.config/Slack/Code Cache",
237
+ "~/.config/Slack/Service Worker",
238
+ ],
239
+ ServiceType.SPOTIFY: ["~/.cache/spotify", "~/.config/spotify/Data"],
240
+ ServiceType.BAZEL: ["~/.cache/bazel"],
241
+ ServiceType.GH: ["~/.cache/gh"],
182
242
  }
183
243
 
244
+ _SKIP_ENUM_SCAN = frozenset(
245
+ {ServiceType.UNKNOWN, ServiceType.GENERIC_CACHE, ServiceType.ELECTRON}
246
+ )
247
+
184
248
  def __init__(self, threshold_mb: int = None):
185
249
  self.threshold_mb = threshold_mb or self.DEFAULT_THRESHOLD_MB
186
250
  self.threshold_gb = self.threshold_mb / 1024
@@ -189,15 +253,39 @@ class ServiceDataScanner:
189
253
 
190
254
  def scan_all_services(self) -> List[ServiceDataInfo]:
191
255
  """Scan all known services for data above threshold."""
192
- results = []
256
+ from .cache_discovery import discover_additional_caches
257
+
258
+ results: List[ServiceDataInfo] = []
193
259
  for service_type in ServiceType:
194
- if service_type == ServiceType.UNKNOWN:
260
+ if service_type in self._SKIP_ENUM_SCAN:
195
261
  continue
196
262
  service_data = self.scan_service(service_type)
197
263
  results.extend(service_data)
264
+
265
+ covered_paths = self._collect_covered_paths(results)
266
+ results.extend(
267
+ discover_additional_caches(
268
+ self._get_path_size_mb, self.threshold_mb, covered_paths
269
+ )
270
+ )
198
271
  results.sort(key=lambda x: x.size_mb, reverse=True)
199
272
  return results
200
273
 
274
+ def _collect_covered_paths(self, results: List[ServiceDataInfo]) -> set[str]:
275
+ """Paths already represented by dedicated service scanners."""
276
+ covered: set[str] = set()
277
+ for result in results:
278
+ covered.add(result.path)
279
+ for path in result.details.get("paths", []):
280
+ covered.add(path)
281
+
282
+ for paths in self.SERVICE_PATHS.values():
283
+ for pattern in paths:
284
+ expanded = os.path.expanduser(pattern)
285
+ for path in glob.glob(expanded) or [expanded]:
286
+ covered.add(path)
287
+ return covered
288
+
201
289
  def scan_service(self, service_type: ServiceType) -> List[ServiceDataInfo]:
202
290
  """Scan specific service type for data."""
203
291
  results = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.32
3
+ Version: 2.2.34
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,7 +63,7 @@ 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.32-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.34-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
67
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$3.66-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-30.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
69
  - 🤖 **LLM usage:** $3.6649 (131 commits)