fixos 2.2.35__tar.gz → 2.2.36__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.
- {fixos-2.2.35 → fixos-2.2.36}/CHANGELOG.md +23 -0
- {fixos-2.2.35 → fixos-2.2.36}/PKG-INFO +6 -6
- {fixos-2.2.35 → fixos-2.2.36}/README.md +5 -5
- {fixos-2.2.35 → fixos-2.2.36}/fixos/__init__.py +1 -1
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/cleanup_cmd.py +132 -21
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/cache_discovery.py +6 -1
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/service_cleanup.py +97 -22
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/service_scanner.py +100 -4
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/PKG-INFO +6 -6
- {fixos-2.2.35 → fixos-2.2.36}/pyproject.toml +1 -1
- {fixos-2.2.35 → fixos-2.2.36}/setup.py +1 -1
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_service_cleanup.py +37 -0
- fixos-2.2.36/tests/unit/test_service_scanner.py +243 -0
- fixos-2.2.35/tests/unit/test_service_scanner.py +0 -102
- {fixos-2.2.35 → fixos-2.2.36}/.env.example +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/LICENSE +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/MANIFEST.in +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/README.md +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/TEST_RESULTS.md +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/TEST_RESULTS_V2.md +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/alpine/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/arch/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/base/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/broken-audio/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/broken-full/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/broken-network/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/broken-thumbnails/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/debian/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/docker-compose.multi-system.yml +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/docker-compose.yml +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/fedora/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/test-multi-system.sh +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/test-scenarios.sh +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/ubuntu/Dockerfile +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docker/validate-scenario.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docs/examples/advanced_usage.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/docs/examples/quickstart.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/autonomous.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/autonomous_session.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/hitl.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/hitl_session.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/session_core.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/session_handlers.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/agent/session_io.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/anonymizer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/_cleanup_flatpak.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/_cleanup_home.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/_cleanup_snap.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/_cleanup_system.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/_cleanup_utils.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/ask_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/config_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/features_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/fix_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/history_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/main.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/orchestrate_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/output_formatter.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/profile_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/provider_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/quickfix_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/report_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/rollback_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/scan_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/shared.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/token_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli/watch_cmd.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/cli.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/config.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/config_interactive.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/constants.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_flatpak_analysis_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_flatpak_execution_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_flatpak_recommendations_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_storage_container_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_storage_system_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/_storage_user_mixin.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/_shared.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/audio.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/file_analysis.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/hardware.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/packages.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/resources.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/security.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/storage_optimization.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/system_core.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/checks/thumbnails.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/dev_project_analyzer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/disk_analyzer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/flatpak_analyzer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/service_details.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/storage_analyzer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/system_checks.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/diagnostics/utils.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/auditor.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/catalog.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/installer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/profiles.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/features/renderer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/fixes/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/interactive/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/interactive/cleanup_planner.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/llm_shell.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/orchestrator/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/orchestrator/executor.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/orchestrator/graph.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/orchestrator/orchestrator.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/orchestrator/rollback.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/platform_utils.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/base.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/audio.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/disk.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/hardware.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/resources.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/security.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/builtin/thumbnails.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/plugins/registry.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/profiles/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/providers/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/providers/llm.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/providers/llm_analyzer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/providers/schemas.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/system_checks.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/utils/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/utils/anonymizer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/utils/terminal.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/utils/timeout.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/utils/web_search.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos/watch.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/SOURCES.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/dependency_links.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/entry_points.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/requires.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/fixos.egg-info/top_level.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/pytest.ini +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/requirements-dev.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/requirements.txt +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/scripts/pyqual-calibrate.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/setup.cfg +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/conftest.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_anonymization_layers.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_audio_broken.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_cli.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_diagnostics_integration.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_executor.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_multi_system.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_network_broken.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/e2e/test_thumbnails_broken.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/test_fixos.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/__init__.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_anonymizer.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_cache_discovery.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_core.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_executor.py +0 -0
- {fixos-2.2.35 → fixos-2.2.36}/tests/unit/test_orchestrator.py +0 -0
|
@@ -150,6 +150,29 @@ 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.36] - 2026-07-15
|
|
154
|
+
|
|
155
|
+
### Docs
|
|
156
|
+
- Update project/README.md
|
|
157
|
+
- Update project/context.md
|
|
158
|
+
|
|
159
|
+
### Test
|
|
160
|
+
- Update tests/unit/test_service_cleanup.py
|
|
161
|
+
- Update tests/unit/test_service_scanner.py
|
|
162
|
+
|
|
163
|
+
### Other
|
|
164
|
+
- Update fixos/cli/cleanup_cmd.py
|
|
165
|
+
- Update fixos/diagnostics/cache_discovery.py
|
|
166
|
+
- Update fixos/diagnostics/service_cleanup.py
|
|
167
|
+
- Update fixos/diagnostics/service_scanner.py
|
|
168
|
+
- Update project.sh
|
|
169
|
+
- Update project/analysis.toon.yaml
|
|
170
|
+
- Update project/calls.mmd
|
|
171
|
+
- Update project/calls.png
|
|
172
|
+
- Update project/calls.toon.yaml
|
|
173
|
+
- Update project/calls.yaml
|
|
174
|
+
- ... and 13 more files
|
|
175
|
+
|
|
153
176
|
## [2.2.35] - 2026-06-16
|
|
154
177
|
|
|
155
178
|
### Other
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixos
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.36
|
|
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,13 +63,13 @@ AI-powered OS Diagnostics
|
|
|
63
63
|
|
|
64
64
|
## AI Cost Tracking
|
|
65
65
|
|
|
66
|
-
    
|
|
67
|
+
  
|
|
68
68
|
|
|
69
|
-
- 🤖 **LLM usage:** $3.
|
|
70
|
-
- 👤 **Human dev:** ~$
|
|
69
|
+
- 🤖 **LLM usage:** $3.9985 (137 commits)
|
|
70
|
+
- 👤 **Human dev:** ~$3270 (32.7h @ $100/h, 30min dedup)
|
|
71
71
|
|
|
72
|
-
Generated on 2026-06
|
|
72
|
+
Generated on 2026-07-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
73
73
|
|
|
74
74
|
---
|
|
75
75
|
|
|
@@ -19,13 +19,13 @@ AI-powered OS Diagnostics
|
|
|
19
19
|
|
|
20
20
|
## AI Cost Tracking
|
|
21
21
|
|
|
22
|
-
    
|
|
23
|
+
  
|
|
24
24
|
|
|
25
|
-
- 🤖 **LLM usage:** $3.
|
|
26
|
-
- 👤 **Human dev:** ~$
|
|
25
|
+
- 🤖 **LLM usage:** $3.9985 (137 commits)
|
|
26
|
+
- 👤 **Human dev:** ~$3270 (32.7h @ $100/h, 30min dedup)
|
|
27
27
|
|
|
28
|
-
Generated on 2026-06
|
|
28
|
+
Generated on 2026-07-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
@@ -33,11 +33,34 @@ def _display_cleanup_summary(plan: dict, threshold: int) -> None:
|
|
|
33
33
|
|
|
34
34
|
click.echo(f"Znaleziono {plan['services_found']} usług:")
|
|
35
35
|
click.echo(f" Całkowity rozmiar: {plan['total_size_gb']:.2f} GB")
|
|
36
|
-
click.echo(
|
|
37
|
-
|
|
36
|
+
click.echo(
|
|
37
|
+
click.style(
|
|
38
|
+
f" Bezpieczne do usunięcia: {plan['safe_cleanup_gb']:.2f} GB", fg="green"
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
click.echo(
|
|
42
|
+
click.style(
|
|
43
|
+
f" Do rozważenia: {plan['requires_review_gb']:.2f} GB", fg="yellow"
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
click.echo(
|
|
47
|
+
click.style(
|
|
48
|
+
f" Niebezpieczne (rzeczywiste dane, nie cache): "
|
|
49
|
+
f"{plan.get('dangerous_gb', 0):.2f} GB",
|
|
50
|
+
fg="red",
|
|
51
|
+
bold=True,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
38
54
|
click.echo()
|
|
39
55
|
|
|
40
56
|
|
|
57
|
+
_RISK_LABELS = {
|
|
58
|
+
"safe": ("(bezpieczne)", "green"),
|
|
59
|
+
"review": ("(do rozważenia)", "yellow"),
|
|
60
|
+
"dangerous": ("(NIEBEZPIECZNE — realne dane, nie cache)", "red"),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
41
64
|
def _display_service_item(svc: dict) -> None:
|
|
42
65
|
"""Display a single service item with details."""
|
|
43
66
|
size_str = (
|
|
@@ -45,11 +68,11 @@ def _display_service_item(svc: dict) -> None:
|
|
|
45
68
|
if svc["size_gb"] >= 1
|
|
46
69
|
else f"{svc['size_mb']:.0f} MB"
|
|
47
70
|
)
|
|
48
|
-
|
|
49
|
-
safe_text =
|
|
71
|
+
risk = svc.get("risk_level") or ("safe" if svc["safe_to_cleanup"] else "review")
|
|
72
|
+
safe_text, safe_color = _RISK_LABELS.get(risk, _RISK_LABELS["review"])
|
|
50
73
|
|
|
51
74
|
click.echo(
|
|
52
|
-
f"{
|
|
75
|
+
f" {click.style(svc['name'], fg='yellow', bold=True)} - {size_str}"
|
|
53
76
|
)
|
|
54
77
|
click.echo(f" {svc['description']}")
|
|
55
78
|
paths = svc.get("details", {}).get("paths") or [svc["path"]]
|
|
@@ -59,7 +82,7 @@ def _display_service_item(svc: dict) -> None:
|
|
|
59
82
|
click.echo(f" • {path}")
|
|
60
83
|
else:
|
|
61
84
|
click.echo(f" Ścieżka: {svc['path']}")
|
|
62
|
-
click.echo(f" {safe_text}")
|
|
85
|
+
click.echo(f" {click.style(safe_text, fg=safe_color, bold=(risk == 'dangerous'))}")
|
|
63
86
|
|
|
64
87
|
# Show details for specific services
|
|
65
88
|
if svc.get("details"):
|
|
@@ -130,42 +153,113 @@ def _display_service_group(service_type: str, svcs: list, type_map: dict) -> Non
|
|
|
130
153
|
_format_hint_line(hint)
|
|
131
154
|
|
|
132
155
|
|
|
156
|
+
def _group_by_service_type(services: list) -> dict:
|
|
157
|
+
groups: dict = {}
|
|
158
|
+
for svc in services:
|
|
159
|
+
service_type = svc.get("service_type", "unknown")
|
|
160
|
+
groups.setdefault(service_type, []).append(svc)
|
|
161
|
+
return groups
|
|
162
|
+
|
|
163
|
+
|
|
133
164
|
def _display_unsafe_services(services: list) -> None:
|
|
134
|
-
"""Display services that
|
|
165
|
+
"""Display services that are worth a look before deleting (reinstallable
|
|
166
|
+
apps, long-unused tool data, unrecognized cache directories)."""
|
|
135
167
|
from fixos.diagnostics.service_scanner import ServiceType
|
|
136
168
|
|
|
137
169
|
click.echo()
|
|
138
|
-
click.echo(click.style("
|
|
139
|
-
|
|
140
|
-
service_groups: dict = {}
|
|
141
|
-
for svc in services:
|
|
142
|
-
service_type = svc.get("service_type", "unknown")
|
|
143
|
-
if service_type not in service_groups:
|
|
144
|
-
service_groups[service_type] = []
|
|
145
|
-
service_groups[service_type].append(svc)
|
|
170
|
+
click.echo(click.style("Do rozważenia (nie usuwane automatycznie):", fg="yellow"))
|
|
146
171
|
|
|
147
172
|
type_map = {
|
|
148
173
|
"flatpak": ServiceType.FLATPAK,
|
|
174
|
+
"generic_cache": ServiceType.GENERIC_CACHE,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for service_type, svcs in _group_by_service_type(services).items():
|
|
178
|
+
_display_service_group(service_type, svcs, type_map)
|
|
179
|
+
|
|
180
|
+
click.echo()
|
|
181
|
+
click.echo(
|
|
182
|
+
click.style(
|
|
183
|
+
"💡 Wskazówki: Powyższe komendy są zwykle bezpieczne (usuwają nieużywane "
|
|
184
|
+
"wersje/runtime) i mogą odzyskać dużo miejsca — warto jednak zerknąć "
|
|
185
|
+
"zanim je uruchomisz",
|
|
186
|
+
fg="green",
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _display_dangerous_services(services: list) -> None:
|
|
192
|
+
"""Display services holding real installed application data (models,
|
|
193
|
+
containers/volumes, editor extensions, VM disks) rather than a cache.
|
|
194
|
+
|
|
195
|
+
Never auto-cleaned in bulk. Shown separately with an explicit warning so
|
|
196
|
+
it can't be mistaken for one of the "safe" cache entries.
|
|
197
|
+
"""
|
|
198
|
+
from fixos.diagnostics.service_scanner import ServiceType
|
|
199
|
+
|
|
200
|
+
if not services:
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
click.echo()
|
|
204
|
+
click.echo(
|
|
205
|
+
click.style(
|
|
206
|
+
"⚠️ Niebezpieczne — realne dane, nie cache (wymaga ręcznej, świadomej akcji):",
|
|
207
|
+
fg="red",
|
|
208
|
+
bold=True,
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
click.echo(
|
|
212
|
+
click.style(
|
|
213
|
+
" To nie jest cache do odtworzenia jednym poleceniem — to np. "
|
|
214
|
+
"zainstalowane modele AI, kontenery/woluminy, dyski maszyn "
|
|
215
|
+
"wirtualnych albo zainstalowane rozszerzenia edytora. Usunięcie "
|
|
216
|
+
"może oznaczać realną utratę danych lub długi re-download.",
|
|
217
|
+
fg="red",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
type_map = {
|
|
149
222
|
"docker": ServiceType.DOCKER,
|
|
223
|
+
"containerd": ServiceType.CONTAINERD,
|
|
224
|
+
"podman": ServiceType.PODMAN,
|
|
150
225
|
"ollama": ServiceType.OLLAMA,
|
|
151
|
-
"steam": ServiceType.STEAM,
|
|
152
|
-
"minikube": ServiceType.MINIKUBE,
|
|
153
226
|
"lmstudio": ServiceType.LMSTUDIO,
|
|
154
|
-
"
|
|
227
|
+
"huggingface": ServiceType.HUGGINGFACE,
|
|
228
|
+
"jupyter": ServiceType.JUPYTER,
|
|
229
|
+
"minikube": ServiceType.MINIKUBE,
|
|
230
|
+
"appimage": ServiceType.APPIMAGE,
|
|
231
|
+
"virtualbox": ServiceType.VBOX,
|
|
232
|
+
"vmware": ServiceType.VMWARE,
|
|
233
|
+
"steam": ServiceType.STEAM,
|
|
234
|
+
"cursor": ServiceType.CURSOR,
|
|
235
|
+
"vscode": ServiceType.VSCODE,
|
|
155
236
|
}
|
|
156
237
|
|
|
157
|
-
for service_type, svcs in
|
|
238
|
+
for service_type, svcs in _group_by_service_type(services).items():
|
|
158
239
|
_display_service_group(service_type, svcs, type_map)
|
|
159
240
|
|
|
160
241
|
click.echo()
|
|
161
242
|
click.echo(
|
|
162
243
|
click.style(
|
|
163
|
-
"
|
|
164
|
-
|
|
244
|
+
" Aby usunąć pojedynczą usługę świadomie: "
|
|
245
|
+
"fixos cleanup -c <usluga> (poprzedź --dry-run, by zobaczyć co zrobi)",
|
|
246
|
+
fg="yellow",
|
|
165
247
|
)
|
|
166
248
|
)
|
|
167
249
|
|
|
168
250
|
|
|
251
|
+
def _service_has_dangerous_data(service_name: str, scanner) -> bool:
|
|
252
|
+
"""True if any currently-scanned path for this service is 'dangerous'."""
|
|
253
|
+
from fixos.diagnostics.service_scanner import RiskLevel, ServiceType
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
service_enum = ServiceType(service_name)
|
|
257
|
+
except ValueError:
|
|
258
|
+
return False
|
|
259
|
+
scanned = scanner.scan_service(service_enum)
|
|
260
|
+
return any(s.risk_level == RiskLevel.DANGEROUS.value for s in scanned)
|
|
261
|
+
|
|
262
|
+
|
|
169
263
|
def _cleanup_single_service(
|
|
170
264
|
service_name: str, scanner, json_output: bool, dry_run: bool
|
|
171
265
|
) -> None:
|
|
@@ -177,6 +271,21 @@ def _cleanup_single_service(
|
|
|
177
271
|
click.echo(json.dumps(result, indent=2, default=str))
|
|
178
272
|
return
|
|
179
273
|
|
|
274
|
+
if not dry_run and _service_has_dangerous_data(service_name, scanner):
|
|
275
|
+
click.echo(
|
|
276
|
+
click.style(
|
|
277
|
+
f"⚠️ {service_name} zawiera realne dane (nie tylko cache) — np. "
|
|
278
|
+
"zainstalowane modele/rozszerzenia/kontenery. Tej operacji nie "
|
|
279
|
+
"da się cofnąć.",
|
|
280
|
+
fg="red",
|
|
281
|
+
bold=True,
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
click.echo(f" Podgląd bez usuwania: fixos cleanup -c {service_name} --dry-run")
|
|
285
|
+
if not click.confirm(f"Na pewno usunąć dane usługi {service_name}?"):
|
|
286
|
+
click.echo("Anulowano.")
|
|
287
|
+
return
|
|
288
|
+
|
|
180
289
|
click.echo(click.style(f"Czyszczenie usługi: {service_name}", fg="yellow"))
|
|
181
290
|
if dry_run:
|
|
182
291
|
click.echo(click.style("[TRYB DRY-RUN] - brak faktycznych zmian", fg="cyan"))
|
|
@@ -215,6 +324,8 @@ def _run_interactive_cleanup(plan: dict, list_only: bool, scanner) -> None:
|
|
|
215
324
|
_execute_safe_cleanup(plan["safe_to_cleanup"], scanner)
|
|
216
325
|
if plan["requires_review"] and not list_only:
|
|
217
326
|
_display_unsafe_services(plan["requires_review"])
|
|
327
|
+
if plan.get("dangerous") and not list_only:
|
|
328
|
+
_display_dangerous_services(plan["dangerous"])
|
|
218
329
|
|
|
219
330
|
|
|
220
331
|
# ── Main CLI command ──────────────────────────────────────────────────────
|
|
@@ -229,12 +229,16 @@ def _build_generic_entry(
|
|
|
229
229
|
service_type: "ServiceType" = None,
|
|
230
230
|
) -> "ServiceDataInfo":
|
|
231
231
|
from .service_cleanup import ServiceCleaner
|
|
232
|
-
from .service_scanner import ServiceDataInfo, ServiceType
|
|
232
|
+
from .service_scanner import RiskLevel, ServiceDataInfo, ServiceType
|
|
233
233
|
|
|
234
234
|
if service_type is None:
|
|
235
235
|
service_type = ServiceType.GENERIC_CACHE
|
|
236
236
|
size_gb = size_mb / 1024
|
|
237
237
|
cleanup_command = ServiceCleaner.get_cleanup_command(service_type, path)
|
|
238
|
+
# Discovered dirs are only ever "safe" (recognized cache name pattern) or
|
|
239
|
+
# "review" (unrecognized, look before deleting) — never "dangerous",
|
|
240
|
+
# since we have no basis to claim it's real installed-app data.
|
|
241
|
+
risk_level = RiskLevel.SAFE.value if safe else RiskLevel.REVIEW.value
|
|
238
242
|
return ServiceDataInfo(
|
|
239
243
|
service_type=service_type,
|
|
240
244
|
name=label,
|
|
@@ -248,4 +252,5 @@ def _build_generic_entry(
|
|
|
248
252
|
safe_to_cleanup=safe,
|
|
249
253
|
impact="high" if size_gb > 1.0 else "medium",
|
|
250
254
|
details={"discovered": True, "source": "cache_discovery"},
|
|
255
|
+
risk_level=risk_level,
|
|
251
256
|
)
|
|
@@ -20,7 +20,9 @@ class ServiceCleaner:
|
|
|
20
20
|
self.scanner = scanner
|
|
21
21
|
|
|
22
22
|
def get_cleanup_plan(self, selected_services: List[str] = None) -> Dict[str, Any]:
|
|
23
|
-
"""Generate cleanup plan for services."""
|
|
23
|
+
"""Generate cleanup plan for services, split into 3 risk tiers."""
|
|
24
|
+
from .service_scanner import RiskLevel
|
|
25
|
+
|
|
24
26
|
services = self.scanner.scan_all_services()
|
|
25
27
|
|
|
26
28
|
if selected_services:
|
|
@@ -29,18 +31,25 @@ class ServiceCleaner:
|
|
|
29
31
|
]
|
|
30
32
|
|
|
31
33
|
total_size_gb = sum(s.size_gb for s in services)
|
|
32
|
-
safe_services = [s for s in services if s.
|
|
33
|
-
|
|
34
|
+
safe_services = [s for s in services if s.risk_level == RiskLevel.SAFE.value]
|
|
35
|
+
review_services = [
|
|
36
|
+
s for s in services if s.risk_level == RiskLevel.REVIEW.value
|
|
37
|
+
]
|
|
38
|
+
dangerous_services = [
|
|
39
|
+
s for s in services if s.risk_level == RiskLevel.DANGEROUS.value
|
|
40
|
+
]
|
|
34
41
|
|
|
35
42
|
plan = {
|
|
36
43
|
"threshold_mb": self.scanner.threshold_mb,
|
|
37
44
|
"services_found": len(services),
|
|
38
45
|
"total_size_gb": round(total_size_gb, 2),
|
|
39
46
|
"safe_cleanup_gb": round(sum(s.size_gb for s in safe_services), 2),
|
|
40
|
-
"requires_review_gb": round(sum(s.size_gb for s in
|
|
47
|
+
"requires_review_gb": round(sum(s.size_gb for s in review_services), 2),
|
|
48
|
+
"dangerous_gb": round(sum(s.size_gb for s in dangerous_services), 2),
|
|
41
49
|
"services": [self._service_to_dict(s) for s in services],
|
|
42
50
|
"safe_to_cleanup": [self._service_to_dict(s) for s in safe_services],
|
|
43
|
-
"requires_review": [self._service_to_dict(s) for s in
|
|
51
|
+
"requires_review": [self._service_to_dict(s) for s in review_services],
|
|
52
|
+
"dangerous": [self._service_to_dict(s) for s in dangerous_services],
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
return plan
|
|
@@ -117,18 +126,32 @@ class ServiceCleaner:
|
|
|
117
126
|
"cleanup_command": service.cleanup_command,
|
|
118
127
|
"preview_command": service.preview_command,
|
|
119
128
|
"safe_to_cleanup": service.safe_to_cleanup,
|
|
129
|
+
"risk_level": service.risk_level,
|
|
120
130
|
"impact": service.impact,
|
|
121
131
|
"items_count": service.items_count,
|
|
122
132
|
"details": service.details,
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
@staticmethod
|
|
126
|
-
def
|
|
127
|
-
"""
|
|
128
|
-
|
|
136
|
+
def get_risk_level(service_type, path: str | None = None) -> str:
|
|
137
|
+
"""Classify cleanup risk for a service path.
|
|
138
|
+
|
|
139
|
+
Returns one of ``RiskLevel.SAFE`` / ``REVIEW`` / ``DANGEROUS``
|
|
140
|
+
(as their string values):
|
|
141
|
+
|
|
142
|
+
- safe: rebuildable cache — re-downloads or regenerates itself
|
|
143
|
+
(package/build caches, browser caches, system package caches).
|
|
144
|
+
- review: recoverable but worth a look before deleting — reinstallable
|
|
145
|
+
apps, long-unused tool data, or unrecognized cache directories.
|
|
146
|
+
- dangerous: real installed application data that is not a simple
|
|
147
|
+
cache (AI models, containers/volumes, VM disks, editor extensions)
|
|
148
|
+
and may not be trivially recoverable.
|
|
149
|
+
"""
|
|
150
|
+
from .service_scanner import RiskLevel, ServiceType
|
|
151
|
+
|
|
152
|
+
normalized_path = os.path.expanduser(path or "").rstrip("/")
|
|
129
153
|
|
|
130
154
|
if service_type == ServiceType.CHROME:
|
|
131
|
-
normalized_path = os.path.expanduser(path or "").rstrip("/")
|
|
132
155
|
chrome_cache_root = os.path.expanduser("~/.cache/google-chrome").rstrip("/")
|
|
133
156
|
chrome_cache_names = {
|
|
134
157
|
"Cache",
|
|
@@ -143,34 +166,70 @@ class ServiceCleaner:
|
|
|
143
166
|
if normalized_path == chrome_cache_root or normalized_path.startswith(
|
|
144
167
|
f"{chrome_cache_root}/"
|
|
145
168
|
):
|
|
146
|
-
return
|
|
169
|
+
return RiskLevel.SAFE.value
|
|
147
170
|
|
|
148
171
|
if os.path.basename(normalized_path) in chrome_cache_names:
|
|
149
|
-
return
|
|
172
|
+
return RiskLevel.SAFE.value
|
|
150
173
|
|
|
151
|
-
return
|
|
174
|
+
return RiskLevel.REVIEW.value
|
|
152
175
|
|
|
153
176
|
if service_type == ServiceType.BRAVE:
|
|
154
|
-
normalized_path = os.path.expanduser(path or "").rstrip("/")
|
|
155
177
|
if "/Cache" in normalized_path or normalized_path.endswith(
|
|
156
178
|
("GPUCache", "Code Cache")
|
|
157
179
|
):
|
|
158
|
-
return
|
|
159
|
-
|
|
180
|
+
return RiskLevel.SAFE.value
|
|
181
|
+
if normalized_path.endswith("BraveSoftware") or "/BraveSoftware/" in (
|
|
160
182
|
normalized_path
|
|
161
|
-
)
|
|
183
|
+
):
|
|
184
|
+
return RiskLevel.SAFE.value
|
|
185
|
+
return RiskLevel.REVIEW.value
|
|
162
186
|
|
|
163
187
|
if service_type == ServiceType.STEAM:
|
|
164
|
-
normalized_path = os.path.expanduser(path or "").rstrip("/")
|
|
165
188
|
safe_suffixes = ("shadercache", "appcache")
|
|
166
|
-
|
|
189
|
+
if any(normalized_path.endswith(suffix) for suffix in safe_suffixes):
|
|
190
|
+
return RiskLevel.SAFE.value
|
|
191
|
+
# Anything else is the actual Steam library/client: installed
|
|
192
|
+
# games and their save data, not a cache.
|
|
193
|
+
return RiskLevel.DANGEROUS.value
|
|
194
|
+
|
|
195
|
+
if service_type in (ServiceType.CURSOR, ServiceType.VSCODE):
|
|
196
|
+
# The extensions directory holds real installed extensions (and
|
|
197
|
+
# any local-only extension data), not a cache — reinstalling
|
|
198
|
+
# each one by hand is real work and some local state won't come
|
|
199
|
+
# back. Everything else these two scan (Cache/CachedData/logs)
|
|
200
|
+
# is a plain, rebuildable cache.
|
|
201
|
+
if os.path.basename(normalized_path) == "extensions":
|
|
202
|
+
return RiskLevel.DANGEROUS.value
|
|
203
|
+
return RiskLevel.SAFE.value
|
|
167
204
|
|
|
168
205
|
if service_type in (ServiceType.GENERIC_CACHE, ServiceType.ELECTRON):
|
|
169
|
-
base = os.path.basename(
|
|
170
|
-
|
|
206
|
+
base = os.path.basename(normalized_path).lower()
|
|
207
|
+
if any(
|
|
171
208
|
hint in base
|
|
172
209
|
for hint in ("cache", "shader", "tmp", "temp", "log", "crash", "thumb")
|
|
173
|
-
)
|
|
210
|
+
):
|
|
211
|
+
return RiskLevel.SAFE.value
|
|
212
|
+
return RiskLevel.REVIEW.value
|
|
213
|
+
|
|
214
|
+
dangerous_services = {
|
|
215
|
+
# Bulk-wipe commands that remove every installed model/container,
|
|
216
|
+
# not just unused/old ones.
|
|
217
|
+
ServiceType.DOCKER,
|
|
218
|
+
ServiceType.CONTAINERD,
|
|
219
|
+
ServiceType.PODMAN,
|
|
220
|
+
ServiceType.OLLAMA,
|
|
221
|
+
ServiceType.LMSTUDIO,
|
|
222
|
+
ServiceType.HUGGINGFACE,
|
|
223
|
+
ServiceType.JUPYTER,
|
|
224
|
+
ServiceType.MINIKUBE,
|
|
225
|
+
# Actual installed apps, not a cache directory.
|
|
226
|
+
ServiceType.APPIMAGE,
|
|
227
|
+
# VM disks/snapshots: unique state, not redownloadable.
|
|
228
|
+
ServiceType.VBOX,
|
|
229
|
+
ServiceType.VMWARE,
|
|
230
|
+
}
|
|
231
|
+
if service_type in dangerous_services:
|
|
232
|
+
return RiskLevel.DANGEROUS.value
|
|
174
233
|
|
|
175
234
|
safe_services = {
|
|
176
235
|
# Package caches (can be re-downloaded)
|
|
@@ -178,6 +237,7 @@ class ServiceCleaner:
|
|
|
178
237
|
ServiceType.YARN,
|
|
179
238
|
ServiceType.PNPM,
|
|
180
239
|
ServiceType.PIP,
|
|
240
|
+
ServiceType.CONDA,
|
|
181
241
|
ServiceType.POETRY,
|
|
182
242
|
ServiceType.GRADLE,
|
|
183
243
|
ServiceType.MAVEN,
|
|
@@ -196,6 +256,8 @@ class ServiceCleaner:
|
|
|
196
256
|
ServiceType.SLACK,
|
|
197
257
|
ServiceType.SPOTIFY,
|
|
198
258
|
ServiceType.BRAVE,
|
|
259
|
+
ServiceType.NIX,
|
|
260
|
+
ServiceType.BREW,
|
|
199
261
|
# System caches
|
|
200
262
|
ServiceType.APT,
|
|
201
263
|
ServiceType.DNF,
|
|
@@ -216,7 +278,20 @@ class ServiceCleaner:
|
|
|
216
278
|
ServiceType.TERRAFORM,
|
|
217
279
|
ServiceType.PULUMI,
|
|
218
280
|
}
|
|
219
|
-
|
|
281
|
+
if service_type in safe_services:
|
|
282
|
+
return RiskLevel.SAFE.value
|
|
283
|
+
|
|
284
|
+
# Default: reinstallable apps / long-unused tool data / unclassified
|
|
285
|
+
# (Flatpak, Snap, Android SDK, JetBrains, Vagrant, Unity, ...) —
|
|
286
|
+
# worth a look before deleting, but not flagged as high-risk.
|
|
287
|
+
return RiskLevel.REVIEW.value
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def is_safe_cleanup(service_type, path: str | None = None) -> bool:
|
|
291
|
+
"""Backward-compatible bool view of get_risk_level() == SAFE."""
|
|
292
|
+
from .service_scanner import RiskLevel
|
|
293
|
+
|
|
294
|
+
return ServiceCleaner.get_risk_level(service_type, path) == RiskLevel.SAFE.value
|
|
220
295
|
|
|
221
296
|
@staticmethod
|
|
222
297
|
def get_cleanup_hints(service_type, size_gb: float) -> List[str]:
|