fixos 2.2.30__tar.gz → 2.2.32__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 (162) hide show
  1. {fixos-2.2.30 → fixos-2.2.32}/CHANGELOG.md +20 -0
  2. {fixos-2.2.30 → fixos-2.2.32}/PKG-INFO +2 -2
  3. {fixos-2.2.30 → fixos-2.2.32}/README.md +1 -1
  4. {fixos-2.2.30 → fixos-2.2.32}/fixos/__init__.py +1 -1
  5. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/cleanup_cmd.py +7 -1
  6. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/main.py +3 -1
  7. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/output_formatter.py +4 -2
  8. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/shared.py +12 -2
  9. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/service_cleanup.py +12 -5
  10. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/service_scanner.py +93 -6
  11. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/PKG-INFO +2 -2
  12. {fixos-2.2.30 → fixos-2.2.32}/pyproject.toml +1 -1
  13. {fixos-2.2.30 → fixos-2.2.32}/setup.py +1 -1
  14. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/test_service_cleanup.py +8 -0
  15. fixos-2.2.32/tests/unit/test_service_scanner.py +102 -0
  16. fixos-2.2.30/tests/unit/test_service_scanner.py +0 -37
  17. {fixos-2.2.30 → fixos-2.2.32}/.env.example +0 -0
  18. {fixos-2.2.30 → fixos-2.2.32}/LICENSE +0 -0
  19. {fixos-2.2.30 → fixos-2.2.32}/MANIFEST.in +0 -0
  20. {fixos-2.2.30 → fixos-2.2.32}/docker/README.md +0 -0
  21. {fixos-2.2.30 → fixos-2.2.32}/docker/TEST_RESULTS.md +0 -0
  22. {fixos-2.2.30 → fixos-2.2.32}/docker/TEST_RESULTS_V2.md +0 -0
  23. {fixos-2.2.30 → fixos-2.2.32}/docker/alpine/Dockerfile +0 -0
  24. {fixos-2.2.30 → fixos-2.2.32}/docker/arch/Dockerfile +0 -0
  25. {fixos-2.2.30 → fixos-2.2.32}/docker/base/Dockerfile +0 -0
  26. {fixos-2.2.30 → fixos-2.2.32}/docker/broken-audio/Dockerfile +0 -0
  27. {fixos-2.2.30 → fixos-2.2.32}/docker/broken-full/Dockerfile +0 -0
  28. {fixos-2.2.30 → fixos-2.2.32}/docker/broken-network/Dockerfile +0 -0
  29. {fixos-2.2.30 → fixos-2.2.32}/docker/broken-thumbnails/Dockerfile +0 -0
  30. {fixos-2.2.30 → fixos-2.2.32}/docker/debian/Dockerfile +0 -0
  31. {fixos-2.2.30 → fixos-2.2.32}/docker/docker-compose.multi-system.yml +0 -0
  32. {fixos-2.2.30 → fixos-2.2.32}/docker/docker-compose.yml +0 -0
  33. {fixos-2.2.30 → fixos-2.2.32}/docker/fedora/Dockerfile +0 -0
  34. {fixos-2.2.30 → fixos-2.2.32}/docker/test-multi-system.sh +0 -0
  35. {fixos-2.2.30 → fixos-2.2.32}/docker/test-scenarios.sh +0 -0
  36. {fixos-2.2.30 → fixos-2.2.32}/docker/ubuntu/Dockerfile +0 -0
  37. {fixos-2.2.30 → fixos-2.2.32}/docker/validate-scenario.py +0 -0
  38. {fixos-2.2.30 → fixos-2.2.32}/docs/examples/advanced_usage.py +0 -0
  39. {fixos-2.2.30 → fixos-2.2.32}/docs/examples/quickstart.py +0 -0
  40. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/__init__.py +0 -0
  41. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/autonomous.py +0 -0
  42. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/autonomous_session.py +0 -0
  43. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/hitl.py +0 -0
  44. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/hitl_session.py +0 -0
  45. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/session_core.py +0 -0
  46. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/session_handlers.py +0 -0
  47. {fixos-2.2.30 → fixos-2.2.32}/fixos/agent/session_io.py +0 -0
  48. {fixos-2.2.30 → fixos-2.2.32}/fixos/anonymizer.py +0 -0
  49. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/__init__.py +0 -0
  50. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/_cleanup_flatpak.py +0 -0
  51. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/_cleanup_home.py +0 -0
  52. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/_cleanup_snap.py +0 -0
  53. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/_cleanup_system.py +0 -0
  54. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/_cleanup_utils.py +0 -0
  55. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/ask_cmd.py +0 -0
  56. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/config_cmd.py +0 -0
  57. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/features_cmd.py +0 -0
  58. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/fix_cmd.py +0 -0
  59. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/history_cmd.py +0 -0
  60. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/orchestrate_cmd.py +0 -0
  61. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/profile_cmd.py +0 -0
  62. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/provider_cmd.py +0 -0
  63. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/quickfix_cmd.py +0 -0
  64. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/report_cmd.py +0 -0
  65. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/rollback_cmd.py +0 -0
  66. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/scan_cmd.py +0 -0
  67. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/token_cmd.py +0 -0
  68. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli/watch_cmd.py +0 -0
  69. {fixos-2.2.30 → fixos-2.2.32}/fixos/cli.py +0 -0
  70. {fixos-2.2.30 → fixos-2.2.32}/fixos/config.py +0 -0
  71. {fixos-2.2.30 → fixos-2.2.32}/fixos/config_interactive.py +0 -0
  72. {fixos-2.2.30 → fixos-2.2.32}/fixos/constants.py +0 -0
  73. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/__init__.py +0 -0
  74. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_flatpak_analysis_mixin.py +0 -0
  75. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_flatpak_execution_mixin.py +0 -0
  76. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_flatpak_recommendations_mixin.py +0 -0
  77. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_storage_container_mixin.py +0 -0
  78. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_storage_system_mixin.py +0 -0
  79. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/_storage_user_mixin.py +0 -0
  80. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/__init__.py +0 -0
  81. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/_shared.py +0 -0
  82. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/audio.py +0 -0
  83. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/file_analysis.py +0 -0
  84. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/hardware.py +0 -0
  85. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/packages.py +0 -0
  86. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/resources.py +0 -0
  87. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/security.py +0 -0
  88. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/storage_optimization.py +0 -0
  89. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/system_core.py +0 -0
  90. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/checks/thumbnails.py +0 -0
  91. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  92. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/disk_analyzer.py +0 -0
  93. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  94. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/service_details.py +0 -0
  95. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/storage_analyzer.py +0 -0
  96. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/system_checks.py +0 -0
  97. {fixos-2.2.30 → fixos-2.2.32}/fixos/diagnostics/utils.py +0 -0
  98. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/__init__.py +0 -0
  99. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/auditor.py +0 -0
  100. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/catalog.py +0 -0
  101. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/installer.py +0 -0
  102. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/profiles.py +0 -0
  103. {fixos-2.2.30 → fixos-2.2.32}/fixos/features/renderer.py +0 -0
  104. {fixos-2.2.30 → fixos-2.2.32}/fixos/fixes/__init__.py +0 -0
  105. {fixos-2.2.30 → fixos-2.2.32}/fixos/interactive/__init__.py +0 -0
  106. {fixos-2.2.30 → fixos-2.2.32}/fixos/interactive/cleanup_planner.py +0 -0
  107. {fixos-2.2.30 → fixos-2.2.32}/fixos/llm_shell.py +0 -0
  108. {fixos-2.2.30 → fixos-2.2.32}/fixos/orchestrator/__init__.py +0 -0
  109. {fixos-2.2.30 → fixos-2.2.32}/fixos/orchestrator/executor.py +0 -0
  110. {fixos-2.2.30 → fixos-2.2.32}/fixos/orchestrator/graph.py +0 -0
  111. {fixos-2.2.30 → fixos-2.2.32}/fixos/orchestrator/orchestrator.py +0 -0
  112. {fixos-2.2.30 → fixos-2.2.32}/fixos/orchestrator/rollback.py +0 -0
  113. {fixos-2.2.30 → fixos-2.2.32}/fixos/platform_utils.py +0 -0
  114. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/__init__.py +0 -0
  115. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/base.py +0 -0
  116. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/__init__.py +0 -0
  117. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/audio.py +0 -0
  118. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/disk.py +0 -0
  119. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/hardware.py +0 -0
  120. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/resources.py +0 -0
  121. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/security.py +0 -0
  122. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/builtin/thumbnails.py +0 -0
  123. {fixos-2.2.30 → fixos-2.2.32}/fixos/plugins/registry.py +0 -0
  124. {fixos-2.2.30 → fixos-2.2.32}/fixos/profiles/__init__.py +0 -0
  125. {fixos-2.2.30 → fixos-2.2.32}/fixos/providers/__init__.py +0 -0
  126. {fixos-2.2.30 → fixos-2.2.32}/fixos/providers/llm.py +0 -0
  127. {fixos-2.2.30 → fixos-2.2.32}/fixos/providers/llm_analyzer.py +0 -0
  128. {fixos-2.2.30 → fixos-2.2.32}/fixos/providers/schemas.py +0 -0
  129. {fixos-2.2.30 → fixos-2.2.32}/fixos/system_checks.py +0 -0
  130. {fixos-2.2.30 → fixos-2.2.32}/fixos/utils/__init__.py +0 -0
  131. {fixos-2.2.30 → fixos-2.2.32}/fixos/utils/anonymizer.py +0 -0
  132. {fixos-2.2.30 → fixos-2.2.32}/fixos/utils/terminal.py +0 -0
  133. {fixos-2.2.30 → fixos-2.2.32}/fixos/utils/timeout.py +0 -0
  134. {fixos-2.2.30 → fixos-2.2.32}/fixos/utils/web_search.py +0 -0
  135. {fixos-2.2.30 → fixos-2.2.32}/fixos/watch.py +0 -0
  136. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/SOURCES.txt +0 -0
  137. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/dependency_links.txt +0 -0
  138. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/entry_points.txt +0 -0
  139. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/requires.txt +0 -0
  140. {fixos-2.2.30 → fixos-2.2.32}/fixos.egg-info/top_level.txt +0 -0
  141. {fixos-2.2.30 → fixos-2.2.32}/pytest.ini +0 -0
  142. {fixos-2.2.30 → fixos-2.2.32}/requirements-dev.txt +0 -0
  143. {fixos-2.2.30 → fixos-2.2.32}/requirements.txt +0 -0
  144. {fixos-2.2.30 → fixos-2.2.32}/scripts/pyqual-calibrate.py +0 -0
  145. {fixos-2.2.30 → fixos-2.2.32}/setup.cfg +0 -0
  146. {fixos-2.2.30 → fixos-2.2.32}/tests/__init__.py +0 -0
  147. {fixos-2.2.30 → fixos-2.2.32}/tests/conftest.py +0 -0
  148. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/__init__.py +0 -0
  149. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_anonymization_layers.py +0 -0
  150. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_audio_broken.py +0 -0
  151. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_cli.py +0 -0
  152. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_diagnostics_integration.py +0 -0
  153. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_executor.py +0 -0
  154. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_multi_system.py +0 -0
  155. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_network_broken.py +0 -0
  156. {fixos-2.2.30 → fixos-2.2.32}/tests/e2e/test_thumbnails_broken.py +0 -0
  157. {fixos-2.2.30 → fixos-2.2.32}/tests/test_fixos.py +0 -0
  158. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/__init__.py +0 -0
  159. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/test_anonymizer.py +0 -0
  160. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/test_core.py +0 -0
  161. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/test_executor.py +0 -0
  162. {fixos-2.2.30 → fixos-2.2.32}/tests/unit/test_orchestrator.py +0 -0
@@ -150,6 +150,26 @@ 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.32] - 2026-06-16
154
+
155
+ ### Test
156
+ - Update tests/unit/test_service_cleanup.py
157
+ - Update tests/unit/test_service_scanner.py
158
+
159
+ ### Other
160
+ - Update fixos/cli/cleanup_cmd.py
161
+ - Update fixos/cli/main.py
162
+ - Update fixos/cli/output_formatter.py
163
+ - Update fixos/cli/shared.py
164
+ - Update fixos/diagnostics/service_cleanup.py
165
+ - Update fixos/diagnostics/service_scanner.py
166
+ - Update uv.lock
167
+
168
+ ## [2.2.31] - 2026-06-16
169
+
170
+ ### Other
171
+ - Update uv.lock
172
+
153
173
  ## [2.2.30] - 2026-06-16
154
174
 
155
175
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.30
3
+ Version: 2.2.32
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.30-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.32-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.30-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.32-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.30"
3
+ __version__ = "2.2.32"
@@ -52,7 +52,13 @@ def _display_service_item(svc: dict) -> None:
52
52
  f"{safe_icon} {click.style(svc['name'], fg='yellow', bold=True)} - {size_str}"
53
53
  )
54
54
  click.echo(f" {svc['description']}")
55
- click.echo(f" Ścieżka: {svc['path']}")
55
+ paths = svc.get("details", {}).get("paths") or [svc["path"]]
56
+ if len(paths) > 1:
57
+ click.echo(f" Ścieżki ({len(paths)}):")
58
+ for path in paths:
59
+ click.echo(f" • {path}")
60
+ else:
61
+ click.echo(f" Ścieżka: {svc['path']}")
56
62
  click.echo(f" {safe_text}")
57
63
 
58
64
  # Show details for specific services
@@ -3,6 +3,8 @@ Main CLI entry point for fixOS
3
3
  """
4
4
 
5
5
  import click
6
+
7
+ from fixos import __version__
6
8
  from fixos.cli.shared import BANNER, NaturalLanguageGroup
7
9
  from fixos.config import FixOsConfig
8
10
 
@@ -38,7 +40,7 @@ def cli(ctx, dry_run, version) -> None:
38
40
  fixos fix --help
39
41
  """
40
42
  if version:
41
- click.echo("fixos v2.0.0")
43
+ click.echo(f"fixos v{__version__}")
42
44
  return
43
45
 
44
46
  if ctx.invoked_subcommand is None:
@@ -18,6 +18,8 @@ import yaml
18
18
 
19
19
  import click
20
20
 
21
+ from fixos import __version__
22
+
21
23
 
22
24
  class OutputFormat(Enum):
23
25
  """Supported output formats."""
@@ -114,7 +116,7 @@ class OutputFormatter:
114
116
  ) -> str:
115
117
  """Format full diagnostic result with metadata envelope."""
116
118
  envelope = {
117
- "fixos_version": "2.0.0",
119
+ "fixos_version": __version__,
118
120
  "timestamp": timestamp or datetime.now().isoformat(),
119
121
  "format": self.fmt.value,
120
122
  }
@@ -132,7 +134,7 @@ class OutputFormatter:
132
134
  ) -> str:
133
135
  """Format scan results with optional disk analysis."""
134
136
  result: dict[str, Any] = {
135
- "fixos_version": "2.0.0",
137
+ "fixos_version": __version__,
136
138
  "timestamp": datetime.now().isoformat(),
137
139
  "scan": data,
138
140
  }
@@ -4,15 +4,25 @@ Shared utilities for fixOS CLI commands
4
4
 
5
5
  import click
6
6
 
7
- BANNER = r"""
7
+ from fixos import __version__
8
+
9
+ _BANNER_TEMPLATE = r"""
8
10
  ___ _ ___ ____
9
11
  / _(_)_ __ / _ \/ ___|
10
12
  | |_| \ \/ / | | | \___ \
11
13
  | _| |> < | |_| |___) |
12
14
  |_| |_/_/\_\ \___/|____/
13
- AI-powered OS Diagnostics • v2.0.0
15
+ AI-powered OS Diagnostics • v{version}
14
16
  """
15
17
 
18
+
19
+ def get_banner() -> str:
20
+ """Return the CLI banner with the installed package version."""
21
+ return _BANNER_TEMPLATE.format(version=__version__)
22
+
23
+
24
+ BANNER = get_banner()
25
+
16
26
  COMMON_OPTIONS = [
17
27
  click.option(
18
28
  "--provider",
@@ -265,7 +265,7 @@ class ServiceCleaner:
265
265
  descriptions = {
266
266
  # Containers
267
267
  ServiceType.DOCKER: "Docker images, containers, and volumes",
268
- ServiceType.OLLAMA: "Ollama AI models and cache",
268
+ ServiceType.OLLAMA: "Ollama AI model files",
269
269
  ServiceType.CONTAINERD: "Containerd container runtime data",
270
270
  ServiceType.PODMAN: "Podman containers and images",
271
271
  # JS/Node
@@ -274,7 +274,7 @@ class ServiceCleaner:
274
274
  ServiceType.PNPM: "PNPM store cache",
275
275
  # Python
276
276
  ServiceType.PIP: "Python pip cache",
277
- ServiceType.CONDA: "Conda/Anaconda environments and packages",
277
+ ServiceType.CONDA: "Conda package cache (pkgs directories)",
278
278
  ServiceType.POETRY: "Poetry virtual environments and cache",
279
279
  # Java
280
280
  ServiceType.GRADLE: "Gradle build cache",
@@ -410,7 +410,13 @@ class ServiceCleaner:
410
410
  largest profile caches untouched, so we remove common cache
411
411
  directories under the scanned profile path as well.
412
412
  """
413
- quoted_path = shlex.quote(path)
413
+ expanded_path = os.path.expanduser(path).rstrip("/")
414
+ cache_root = os.path.expanduser("~/.cache/google-chrome").rstrip("/")
415
+ quoted_path = shlex.quote(expanded_path)
416
+
417
+ if expanded_path == cache_root or expanded_path.startswith(f"{cache_root}/"):
418
+ return f"rm -rf {quoted_path}"
419
+
414
420
  cache_dir_names = [
415
421
  "Cache",
416
422
  "Code Cache",
@@ -424,8 +430,9 @@ class ServiceCleaner:
424
430
  f"-name {shlex.quote(name)}" for name in cache_dir_names
425
431
  )
426
432
  return (
427
- "rm -rf ~/.cache/google-chrome && "
428
- f"find {quoted_path} -type d \\( {find_expr} \\) -prune -exec rm -rf {{}} +"
433
+ "rm -rf ~/.cache/google-chrome 2>/dev/null || true; "
434
+ f"find {quoted_path} -type d \\( {find_expr} \\) "
435
+ "-prune -exec rm -rf {} + 2>/dev/null || true"
429
436
  )
430
437
 
431
438
  @staticmethod
@@ -9,6 +9,7 @@ Refactored: Now uses ServiceDetailsProvider and ServiceCleaner for detailed oper
9
9
  import os
10
10
  import glob
11
11
  import json
12
+ import subprocess
12
13
  from typing import Dict, List, Any, Optional
13
14
  from dataclasses import dataclass, field
14
15
  from enum import Enum
@@ -98,14 +99,24 @@ class ServiceDataScanner:
98
99
 
99
100
  SERVICE_PATHS = {
100
101
  ServiceType.DOCKER: ["/var/lib/docker", "~/.docker"],
101
- ServiceType.OLLAMA: ["~/.ollama", "/usr/share/ollama"],
102
+ ServiceType.OLLAMA: [
103
+ "~/.ollama/models",
104
+ "~/.ollama/blobs",
105
+ "/usr/share/ollama/.ollama/models",
106
+ ],
102
107
  ServiceType.CONTAINERD: ["/var/lib/containerd", "/run/containerd"],
103
108
  ServiceType.PODMAN: ["~/.local/share/containers", "~/.config/containers"],
104
109
  ServiceType.NPM: ["~/.npm", "~/.cache/npm"],
105
110
  ServiceType.YARN: ["~/.cache/yarn", "~/.yarn", "~/.config/yarn"],
106
111
  ServiceType.PNPM: ["~/.pnpm-store", "~/.local/share/pnpm"],
107
112
  ServiceType.PIP: ["~/.cache/pip"],
108
- ServiceType.CONDA: ["~/anaconda3", "~/miniconda3", "~/.conda"],
113
+ ServiceType.CONDA: [
114
+ "~/miniconda3/pkgs",
115
+ "~/anaconda3/pkgs",
116
+ "~/.conda/pkgs",
117
+ "~/miniconda3/envs/*/pkgs",
118
+ "~/anaconda3/envs/*/pkgs",
119
+ ],
109
120
  ServiceType.POETRY: ["~/.cache/pypoetry"],
110
121
  ServiceType.GRADLE: ["~/.gradle", "~/.cache/gradle"],
111
122
  ServiceType.MAVEN: ["~/.m2"],
@@ -114,7 +125,11 @@ class ServiceDataScanner:
114
125
  ServiceType.FLUTTER: ["~/.flutter-sdk", "~/flutter", "~/.pub-cache"],
115
126
  ServiceType.DART: ["~/.pub-cache"],
116
127
  ServiceType.ANDROID: ["~/Android/Sdk", "~/.android"],
117
- ServiceType.SNAP: ["/var/snap", "/snap"],
128
+ ServiceType.SNAP: [
129
+ "/var/lib/snapd/snaps",
130
+ "/var/snap",
131
+ "/var/lib/snapd/cache",
132
+ ],
118
133
  ServiceType.FLATPAK: ["~/.local/share/flatpak", "/var/lib/flatpak"],
119
134
  ServiceType.APPIMAGE: ["~/.local/share/AppImage", "~/.cache/AppImage"],
120
135
  ServiceType.APT: ["/var/cache/apt/archives"],
@@ -140,8 +155,18 @@ class ServiceDataScanner:
140
155
  ServiceType.FIREFOX: ["~/.cache/mozilla", "~/.mozilla/firefox/*/cache2"],
141
156
  ServiceType.EDGE: ["~/.cache/microsoft-edge"],
142
157
  ServiceType.VSCODE: ["~/.vscode/extensions", "~/.config/Code/Cache"],
143
- ServiceType.CURSOR: ["~/.cursor/extensions", "~/.config/Cursor"],
144
- ServiceType.JETBRAINS: ["~/.JetBrains", "~/.cache/JetBrains"],
158
+ ServiceType.CURSOR: [
159
+ "~/.config/Cursor/Cache",
160
+ "~/.config/Cursor/CachedData",
161
+ "~/.config/Cursor/CachedExtensionVSIXs",
162
+ "~/.config/Cursor/logs",
163
+ "~/.cursor/extensions",
164
+ ],
165
+ ServiceType.JETBRAINS: [
166
+ "~/.cache/JetBrains",
167
+ "~/.local/share/JetBrains/*/caches",
168
+ "~/.local/share/JetBrains/*/index",
169
+ ],
145
170
  ServiceType.HUGGINGFACE: ["~/.cache/huggingface"],
146
171
  ServiceType.AWS: ["~/.aws/sso/cache", "~/.aws/cli/cache"],
147
172
  ServiceType.GCLOUD: ["~/.config/gcloud/logs", "~/.cache/gcloud"],
@@ -185,8 +210,38 @@ class ServiceDataScanner:
185
210
  info = self._analyze_service_path(service_type, path)
186
211
  if info and info.size_mb >= self.threshold_mb:
187
212
  results.append(info)
213
+ if len(results) > 1:
214
+ return [self._merge_service_entries(results)]
188
215
  return results
189
216
 
217
+ def _merge_service_entries(
218
+ self, results: List[ServiceDataInfo]
219
+ ) -> ServiceDataInfo:
220
+ """Combine multiple paths for the same service into one summary entry."""
221
+ primary = max(results, key=lambda item: item.size_mb)
222
+ total_mb = sum(item.size_mb for item in results)
223
+ paths = [item.path for item in results]
224
+
225
+ return ServiceDataInfo(
226
+ service_type=primary.service_type,
227
+ name=primary.name,
228
+ path=primary.path,
229
+ size_mb=round(total_mb, 2),
230
+ size_gb=round(total_mb / 1024, 3),
231
+ description=primary.description,
232
+ can_cleanup=primary.can_cleanup,
233
+ cleanup_command=primary.cleanup_command,
234
+ preview_command=primary.preview_command,
235
+ safe_to_cleanup=all(item.safe_to_cleanup for item in results),
236
+ impact="high" if total_mb / 1024 > 1.0 else "medium",
237
+ items_count=primary.items_count,
238
+ details={
239
+ **primary.details,
240
+ "paths": paths,
241
+ "merged_count": len(results),
242
+ },
243
+ )
244
+
190
245
  def _analyze_service_path(
191
246
  self, service_type: ServiceType, path: str
192
247
  ) -> Optional[ServiceDataInfo]:
@@ -229,12 +284,36 @@ class ServiceDataScanner:
229
284
  )
230
285
 
231
286
  def _get_path_size_mb(self, path: str) -> float:
232
- """Get size of path in MB."""
287
+ """Get size of path in MB using du, falling back to os.walk."""
288
+ try:
289
+ result = subprocess.run(
290
+ ["du", "-sk", "--", path],
291
+ capture_output=True,
292
+ text=True,
293
+ timeout=120,
294
+ check=False,
295
+ )
296
+ if result.returncode == 0 and result.stdout.strip():
297
+ kb = int(result.stdout.strip().splitlines()[-1].split()[0])
298
+ return kb / 1024
299
+ except (OSError, ValueError, subprocess.TimeoutExpired, IndexError):
300
+ pass
301
+
233
302
  total_size = 0
234
303
  if os.path.isfile(path):
235
304
  return os.path.getsize(path) / (1024 * 1024)
305
+
236
306
  try:
307
+ root_dev = os.stat(path).st_dev if os.path.exists(path) else None
237
308
  for dirpath, dirnames, filenames in os.walk(path):
309
+ if root_dev is not None:
310
+ dirnames[:] = [
311
+ name
312
+ for name in dirnames
313
+ if self._should_descend(
314
+ os.path.join(dirpath, name), root_dev
315
+ )
316
+ ]
238
317
  for filename in filenames:
239
318
  filepath = os.path.join(dirpath, filename)
240
319
  try:
@@ -245,6 +324,14 @@ class ServiceDataScanner:
245
324
  pass
246
325
  return total_size / (1024 * 1024)
247
326
 
327
+ @staticmethod
328
+ def _should_descend(path: str, root_dev: int) -> bool:
329
+ """Skip mount points so loop-mounted snaps are not double-counted."""
330
+ try:
331
+ return os.stat(path).st_dev == root_dev
332
+ except OSError:
333
+ return False
334
+
248
335
  def get_cleanup_plan(self, selected_services: List[str] = None) -> Dict[str, Any]:
249
336
  """Generate cleanup plan for services."""
250
337
  return self._cleaner.get_cleanup_plan(selected_services)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.30
3
+ Version: 2.2.32
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.30-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.32-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)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixos"
7
- version = "2.2.30"
7
+ version = "2.2.32"
8
8
  description = "AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -5,7 +5,7 @@ long_description = (Path(__file__).parent / "README.md").read_text(encoding="utf
5
5
 
6
6
  setup(
7
7
  name="fixos",
8
- version="2.2.30",
8
+ version="2.2.32",
9
9
  description="AI-powered Linux/Windows diagnostics and repair with anonymization",
10
10
  long_description=long_description,
11
11
  long_description_content_type="text/markdown",
@@ -19,6 +19,14 @@ class TestChromeCleanup:
19
19
  assert "GPUCache" in command
20
20
  assert "Service Worker" in command
21
21
 
22
+ def test_chrome_cache_cleanup_does_not_run_find_on_removed_path(self):
23
+ path = "/home/tom/.cache/google-chrome"
24
+
25
+ command = ServiceCleaner.get_cleanup_command(ServiceType.CHROME, path)
26
+
27
+ assert command == f"rm -rf {path}"
28
+ assert "find" not in command
29
+
22
30
  def test_cleanup_service_reports_freed_space_for_chrome(self, monkeypatch):
23
31
  path = "/home/tom/.config/google-chrome"
24
32
  initial_size_mb = 537.0
@@ -0,0 +1,102 @@
1
+ """Testy jednostkowe dla ServiceDataScanner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fixos.diagnostics.service_scanner import (
6
+ ServiceDataInfo,
7
+ ServiceDataScanner,
8
+ ServiceType,
9
+ )
10
+
11
+
12
+ class TestChromeSafetyClassification:
13
+ def test_chrome_profile_is_marked_for_review(self, monkeypatch):
14
+ scanner = ServiceDataScanner(threshold_mb=1)
15
+ profile_path = "/home/tom/.config/google-chrome"
16
+
17
+ monkeypatch.setattr(scanner, "_get_path_size_mb", lambda path: 537.0)
18
+ monkeypatch.setattr(
19
+ scanner._details_provider, "get_details", lambda service_type, path: {}
20
+ )
21
+
22
+ info = scanner._analyze_service_path(ServiceType.CHROME, profile_path)
23
+
24
+ assert info is not None
25
+ assert info.safe_to_cleanup is False
26
+ assert profile_path in info.cleanup_command
27
+
28
+ def test_chrome_cache_path_is_marked_safe(self, monkeypatch):
29
+ scanner = ServiceDataScanner(threshold_mb=1)
30
+ cache_path = "/home/tom/.cache/google-chrome"
31
+
32
+ monkeypatch.setattr(scanner, "_get_path_size_mb", lambda path: 40.0)
33
+ monkeypatch.setattr(
34
+ scanner._details_provider, "get_details", lambda service_type, path: {}
35
+ )
36
+
37
+ info = scanner._analyze_service_path(ServiceType.CHROME, cache_path)
38
+
39
+ assert info is not None
40
+ assert info.safe_to_cleanup is True
41
+ assert cache_path in info.cleanup_command
42
+
43
+
44
+ class TestServiceMerge:
45
+ def test_scan_service_merges_multiple_paths(self, monkeypatch):
46
+ scanner = ServiceDataScanner(threshold_mb=1)
47
+
48
+ def fake_analyze(service_type, path):
49
+ sizes = {
50
+ "/home/tom/.config/Cursor/Cache": 16000.0,
51
+ "/home/tom/.cursor/extensions": 800.0,
52
+ }
53
+ size_mb = sizes.get(path, 0.0)
54
+ if size_mb <= 0:
55
+ return None
56
+ return ServiceDataInfo(
57
+ service_type=service_type,
58
+ name=service_type.value.title(),
59
+ path=path,
60
+ size_mb=size_mb,
61
+ size_gb=round(size_mb / 1024, 3),
62
+ description="Cursor editor cache",
63
+ can_cleanup=True,
64
+ cleanup_command="rm -rf cache",
65
+ preview_command="du -sh",
66
+ safe_to_cleanup=False,
67
+ )
68
+
69
+ monkeypatch.setattr(scanner, "_analyze_service_path", fake_analyze)
70
+ monkeypatch.setattr(
71
+ "fixos.diagnostics.service_scanner.glob.glob",
72
+ lambda pattern: [pattern],
73
+ )
74
+ monkeypatch.setattr(
75
+ "fixos.diagnostics.service_scanner.os.path.exists", lambda path: True
76
+ )
77
+
78
+ results = scanner.scan_service(ServiceType.CURSOR)
79
+
80
+ assert len(results) == 1
81
+ assert results[0].size_mb == 16800.0
82
+ assert results[0].details["merged_count"] == 2
83
+ assert len(results[0].details["paths"]) == 2
84
+
85
+
86
+ class TestServicePathTargets:
87
+ def test_conda_paths_scan_package_cache_only(self):
88
+ paths = ServiceDataScanner.SERVICE_PATHS[ServiceType.CONDA]
89
+ assert paths
90
+ assert all("pkgs" in path for path in paths)
91
+ assert "~/miniconda3" not in paths
92
+
93
+ def test_ollama_paths_avoid_whole_system_tree(self):
94
+ paths = ServiceDataScanner.SERVICE_PATHS[ServiceType.OLLAMA]
95
+ assert paths
96
+ assert "/usr/share/ollama" not in paths
97
+ assert "~/.ollama/models" in paths
98
+
99
+ def test_jetbrains_paths_target_cache_directories(self):
100
+ paths = ServiceDataScanner.SERVICE_PATHS[ServiceType.JETBRAINS]
101
+ assert "~/.cache/JetBrains" in paths
102
+ assert "~/.JetBrains" not in paths
@@ -1,37 +0,0 @@
1
- """Testy jednostkowe dla ServiceDataScanner."""
2
-
3
- from __future__ import annotations
4
-
5
- from fixos.diagnostics.service_scanner import ServiceDataScanner, ServiceType
6
-
7
-
8
- class TestChromeSafetyClassification:
9
- def test_chrome_profile_is_marked_for_review(self, monkeypatch):
10
- scanner = ServiceDataScanner(threshold_mb=1)
11
- profile_path = "/home/tom/.config/google-chrome"
12
-
13
- monkeypatch.setattr(scanner, "_get_path_size_mb", lambda path: 537.0)
14
- monkeypatch.setattr(
15
- scanner._details_provider, "get_details", lambda service_type, path: {}
16
- )
17
-
18
- info = scanner._analyze_service_path(ServiceType.CHROME, profile_path)
19
-
20
- assert info is not None
21
- assert info.safe_to_cleanup is False
22
- assert profile_path in info.cleanup_command
23
-
24
- def test_chrome_cache_path_is_marked_safe(self, monkeypatch):
25
- scanner = ServiceDataScanner(threshold_mb=1)
26
- cache_path = "/home/tom/.cache/google-chrome"
27
-
28
- monkeypatch.setattr(scanner, "_get_path_size_mb", lambda path: 40.0)
29
- monkeypatch.setattr(
30
- scanner._details_provider, "get_details", lambda service_type, path: {}
31
- )
32
-
33
- info = scanner._analyze_service_path(ServiceType.CHROME, cache_path)
34
-
35
- assert info is not None
36
- assert info.safe_to_cleanup is True
37
- assert cache_path in info.cleanup_command
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes