fixos 2.2.28__tar.gz → 2.2.29__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.28 → fixos-2.2.29}/CHANGELOG.md +26 -0
- {fixos-2.2.28 → fixos-2.2.29}/PKG-INFO +4 -6
- {fixos-2.2.28 → fixos-2.2.29}/README.md +3 -5
- {fixos-2.2.28 → fixos-2.2.29}/fixos/__init__.py +1 -1
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/session_core.py +84 -34
- fixos-2.2.29/fixos/cli/_cleanup_flatpak.py +229 -0
- fixos-2.2.29/fixos/cli/_cleanup_home.py +132 -0
- fixos-2.2.29/fixos/cli/_cleanup_snap.py +116 -0
- fixos-2.2.29/fixos/cli/_cleanup_system.py +444 -0
- fixos-2.2.29/fixos/cli/_cleanup_utils.py +114 -0
- fixos-2.2.29/fixos/cli/cleanup_cmd.py +250 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/config_cmd.py +37 -23
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/scan_cmd.py +6 -1
- {fixos-2.2.28 → fixos-2.2.29}/fixos/constants.py +13 -0
- fixos-2.2.29/fixos/diagnostics/_flatpak_analysis_mixin.py +298 -0
- fixos-2.2.29/fixos/diagnostics/_flatpak_execution_mixin.py +232 -0
- fixos-2.2.29/fixos/diagnostics/_flatpak_recommendations_mixin.py +259 -0
- fixos-2.2.29/fixos/diagnostics/_storage_container_mixin.py +155 -0
- fixos-2.2.29/fixos/diagnostics/_storage_system_mixin.py +259 -0
- fixos-2.2.29/fixos/diagnostics/_storage_user_mixin.py +348 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/__init__.py +6 -0
- fixos-2.2.29/fixos/diagnostics/checks/file_analysis.py +334 -0
- fixos-2.2.29/fixos/diagnostics/checks/packages.py +220 -0
- fixos-2.2.29/fixos/diagnostics/checks/storage_optimization.py +224 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/dev_project_analyzer.py +3 -5
- fixos-2.2.29/fixos/diagnostics/flatpak_analyzer.py +161 -0
- fixos-2.2.29/fixos/diagnostics/storage_analyzer.py +274 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/system_checks.py +9 -0
- fixos-2.2.29/fixos/diagnostics/utils.py +12 -0
- fixos-2.2.29/fixos/fixes/__init__.py +2 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/utils/terminal.py +10 -15
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/PKG-INFO +4 -6
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/SOURCES.txt +16 -0
- {fixos-2.2.28 → fixos-2.2.29}/pyproject.toml +1 -1
- {fixos-2.2.28 → fixos-2.2.29}/pytest.ini +1 -1
- {fixos-2.2.28 → fixos-2.2.29}/setup.py +1 -1
- fixos-2.2.29/tests/e2e/test_diagnostics_integration.py +132 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_core.py +31 -0
- fixos-2.2.28/fixos/cli/cleanup_cmd.py +0 -1228
- fixos-2.2.28/fixos/diagnostics/flatpak_analyzer.py +0 -937
- fixos-2.2.28/fixos/diagnostics/storage_analyzer.py +0 -1091
- fixos-2.2.28/fixos/fixes/__init__.py +0 -4
- {fixos-2.2.28 → fixos-2.2.29}/.env.example +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/LICENSE +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/MANIFEST.in +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/README.md +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/TEST_RESULTS.md +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/TEST_RESULTS_V2.md +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/alpine/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/arch/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/base/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/broken-audio/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/broken-full/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/broken-network/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/broken-thumbnails/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/debian/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/docker-compose.multi-system.yml +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/docker-compose.yml +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/fedora/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/test-multi-system.sh +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/test-scenarios.sh +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/ubuntu/Dockerfile +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docker/validate-scenario.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docs/examples/advanced_usage.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/docs/examples/quickstart.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/autonomous.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/autonomous_session.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/hitl.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/hitl_session.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/session_handlers.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/agent/session_io.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/anonymizer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/ask_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/features_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/fix_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/history_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/main.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/orchestrate_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/output_formatter.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/profile_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/provider_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/quickfix_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/report_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/rollback_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/shared.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/token_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli/watch_cmd.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/cli.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/config.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/config_interactive.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/_shared.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/audio.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/hardware.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/resources.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/security.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/system_core.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/checks/thumbnails.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/disk_analyzer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/service_cleanup.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/service_details.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/diagnostics/service_scanner.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/auditor.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/catalog.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/installer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/profiles.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/features/renderer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/interactive/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/interactive/cleanup_planner.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/llm_shell.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/orchestrator/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/orchestrator/executor.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/orchestrator/graph.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/orchestrator/orchestrator.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/orchestrator/rollback.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/platform_utils.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/base.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/audio.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/disk.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/hardware.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/resources.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/security.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/builtin/thumbnails.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/plugins/registry.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/profiles/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/providers/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/providers/llm.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/providers/llm_analyzer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/providers/schemas.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/system_checks.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/utils/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/utils/anonymizer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/utils/timeout.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/utils/web_search.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos/watch.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/dependency_links.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/entry_points.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/requires.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/fixos.egg-info/top_level.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/requirements-dev.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/requirements.txt +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/scripts/pyqual-calibrate.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/setup.cfg +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/conftest.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_anonymization_layers.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_audio_broken.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_cli.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_executor.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_multi_system.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_network_broken.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/e2e/test_thumbnails_broken.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/test_fixos.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/__init__.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_anonymizer.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_executor.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_orchestrator.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_service_cleanup.py +0 -0
- {fixos-2.2.28 → fixos-2.2.29}/tests/unit/test_service_scanner.py +0 -0
|
@@ -150,6 +150,32 @@ 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.29] - 2026-05-08
|
|
154
|
+
|
|
155
|
+
### Docs
|
|
156
|
+
- Update README.md
|
|
157
|
+
- Update SUMD.md
|
|
158
|
+
- Update SUMR.md
|
|
159
|
+
- Update project/README.md
|
|
160
|
+
- Update project/context.md
|
|
161
|
+
|
|
162
|
+
### Test
|
|
163
|
+
- Update tests/e2e/test_diagnostics_integration.py
|
|
164
|
+
- Update tests/unit/test_core.py
|
|
165
|
+
|
|
166
|
+
### Other
|
|
167
|
+
- Update app.doql.less
|
|
168
|
+
- Update fixos/agent/session_core.py
|
|
169
|
+
- Update fixos/cli/_cleanup_flatpak.py
|
|
170
|
+
- Update fixos/cli/_cleanup_home.py
|
|
171
|
+
- Update fixos/cli/_cleanup_snap.py
|
|
172
|
+
- Update fixos/cli/_cleanup_system.py
|
|
173
|
+
- Update fixos/cli/_cleanup_utils.py
|
|
174
|
+
- Update fixos/cli/cleanup_cmd.py
|
|
175
|
+
- Update fixos/cli/config_cmd.py
|
|
176
|
+
- Update fixos/cli/scan_cmd.py
|
|
177
|
+
- ... and 36 more files
|
|
178
|
+
|
|
153
179
|
## [2.2.28] - 2026-05-06
|
|
154
180
|
|
|
155
181
|
### Docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixos
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.29
|
|
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,11 @@ AI-powered OS Diagnostics
|
|
|
63
63
|
|
|
64
64
|
## AI Cost Tracking
|
|
65
65
|
|
|
66
|
-
  
|
|
66
|
+
 
|
|
68
67
|
|
|
69
|
-
-
|
|
70
|
-
- 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
|
|
68
|
+
This project uses AI-generated code. Total cost: **$7.5000** with **128** AI commits.
|
|
71
69
|
|
|
72
|
-
Generated on 2026-05-
|
|
70
|
+
Generated on 2026-05-08 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/models/openrouter/qwen/qwen3-coder-next)
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
@@ -19,13 +19,11 @@ AI-powered OS Diagnostics
|
|
|
19
19
|
|
|
20
20
|
## AI Cost Tracking
|
|
21
21
|
|
|
22
|
-
  
|
|
22
|
+
 
|
|
24
23
|
|
|
25
|
-
-
|
|
26
|
-
- 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
|
|
24
|
+
This project uses AI-generated code. Total cost: **$7.5000** with **128** AI commits.
|
|
27
25
|
|
|
28
|
-
Generated on 2026-05-
|
|
26
|
+
Generated on 2026-05-08 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/models/openrouter/qwen/qwen3-coder-next)
|
|
29
27
|
|
|
30
28
|
---
|
|
31
29
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""fixos – AI-powered Linux/Windows diagnostics and repair."""
|
|
2
|
-
__version__ = "2.2.
|
|
2
|
+
__version__ = "2.2.29"
|
|
@@ -51,6 +51,31 @@ IMPORTANT RULES:
|
|
|
51
51
|
- When disk usage is critically high (>90%), ALWAYS propose cleanup commands FIRST.
|
|
52
52
|
- NEVER suggest package upgrades or installations BEFORE cleanup has freed sufficient space and been verified.
|
|
53
53
|
|
|
54
|
+
PACKAGE ANALYSIS rules (when "packages" data is present):
|
|
55
|
+
- For orphaned packages: propose `sudo dnf autoremove` or specific `sudo dnf remove <pkg>`.
|
|
56
|
+
- For debug/devel packages on desktop: propose `sudo dnf remove '*-debuginfo*'` or specific removals.
|
|
57
|
+
- For duplicate RPM+Flatpak apps: propose removing one version (prefer Flatpak for GUI apps).
|
|
58
|
+
- For unused Flatpak runtimes: propose `flatpak uninstall --unused`.
|
|
59
|
+
- For leaf packages not used in 90+ days: propose specific `sudo dnf remove <pkg>`.
|
|
60
|
+
- Always warn user about dependencies that will be removed.
|
|
61
|
+
|
|
62
|
+
STORAGE OPTIMIZATION rules (when "storage" data is present):
|
|
63
|
+
- If unallocated disk space exists: propose `sudo growpart` or `sudo lvextend + resize2fs/xfs_growfs`.
|
|
64
|
+
- For btrfs without compression: propose adding `compress=zstd:1` to fstab.
|
|
65
|
+
- For old btrfs snapshots: propose `sudo snapper delete <id>` for specific old snapshots.
|
|
66
|
+
- For swap/zram optimization: propose tuning swappiness or enabling zram.
|
|
67
|
+
- If fstrim.timer is disabled on SSD: propose `sudo systemctl enable --now fstrim.timer`.
|
|
68
|
+
|
|
69
|
+
FILE ANALYSIS rules (when "files" data is present):
|
|
70
|
+
- For large files >200MB: list them and propose review/deletion commands.
|
|
71
|
+
- For duplicate files: propose `fdupes -d` or `rdfind` commands for interactive dedup.
|
|
72
|
+
- For media files (ebooks, mp3, mp4, images): propose organizing/archiving commands:
|
|
73
|
+
- `mkdir -p ~/Archive/{ebooks,muzyka,wideo,obrazy}` + `mv` commands.
|
|
74
|
+
- For compression: `tar czf ~/Archive/ebooks.tar.gz ~/path/to/ebooks/`.
|
|
75
|
+
- For old downloads (>30 days): propose cleanup of ~/Downloads.
|
|
76
|
+
- For trash: propose `rm -rf ~/.local/share/Trash/files/*`.
|
|
77
|
+
- Always group suggestions by category (video, music, ebooks, archives, etc.).
|
|
78
|
+
|
|
54
79
|
Always end with:
|
|
55
80
|
━━━ DOSTĘPNE AKCJE ━━━
|
|
56
81
|
[1] Fix problem 1 – `command`
|
|
@@ -117,11 +142,9 @@ def _extract_co_robi(text: str) -> str:
|
|
|
117
142
|
return m.group(1).strip() if m else ""
|
|
118
143
|
|
|
119
144
|
|
|
120
|
-
def
|
|
121
|
-
"""
|
|
145
|
+
def _pattern_strict_bold(reply: str) -> List[Tuple[str, str]]:
|
|
146
|
+
"""Pattern 1: **Komenda:** `command` (strict: bold + backticks)."""
|
|
122
147
|
fixes: List[Tuple[str, str]] = []
|
|
123
|
-
|
|
124
|
-
# Pattern 1: **Komenda:** `command` (strict: bold + backticks)
|
|
125
148
|
for m in re.finditer(
|
|
126
149
|
r"\*\*Komenda:\*\*\s*`([^`]+)`(?:[^\n]*?\*\*Co robi:\*\*\s*(.+?))?(?=\n|$)",
|
|
127
150
|
reply, re.IGNORECASE,
|
|
@@ -129,45 +152,72 @@ def extract_fixes(reply: str) -> List[Tuple[str, str]]:
|
|
|
129
152
|
cmd = m.group(1).strip()
|
|
130
153
|
if cmd:
|
|
131
154
|
fixes.append((cmd, (m.group(2) or "").strip()))
|
|
155
|
+
return fixes
|
|
132
156
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
fixes.append((m.
|
|
159
|
-
|
|
157
|
+
|
|
158
|
+
def _pattern_backticks(reply: str) -> List[Tuple[str, str]]:
|
|
159
|
+
"""Pattern 2: Komenda: `command` (backticks, optional bold)."""
|
|
160
|
+
fixes: List[Tuple[str, str]] = []
|
|
161
|
+
for m in re.finditer(
|
|
162
|
+
r"\*{0,2}Komenda:\*{0,2}\s*`([^`]+)`",
|
|
163
|
+
reply, re.IGNORECASE,
|
|
164
|
+
):
|
|
165
|
+
cmd = m.group(1).strip()
|
|
166
|
+
if cmd:
|
|
167
|
+
fixes.append((cmd, _extract_co_robi(reply[m.end():])))
|
|
168
|
+
return fixes
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _pattern_no_backticks(reply: str) -> List[Tuple[str, str]]:
|
|
172
|
+
"""Pattern 3: Komenda: command (no backticks — until next section)."""
|
|
173
|
+
fixes: List[Tuple[str, str]] = []
|
|
174
|
+
for m in re.finditer(
|
|
175
|
+
r"\*{0,2}Komenda:\*{0,2}\s*"
|
|
176
|
+
r"(.+?)"
|
|
177
|
+
r"(?=\n\s*\*{0,2}Co robi:|\n[🔴🟡🟢]|\n━|\n─|\n\[[\dA-Z]|\Z)",
|
|
178
|
+
reply, re.IGNORECASE | re.DOTALL,
|
|
179
|
+
):
|
|
180
|
+
cmd = re.sub(r"\s*\n\s*", " ", m.group(1)).strip()
|
|
181
|
+
if cmd:
|
|
182
|
+
fixes.append((cmd, _extract_co_robi(reply[m.end():])))
|
|
183
|
+
return fixes
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _pattern_fallbacks(reply: str) -> List[Tuple[str, str]]:
|
|
187
|
+
"""Fallback patterns: → Fix, [N] command, EXEC."""
|
|
188
|
+
fixes: List[Tuple[str, str]] = []
|
|
189
|
+
for m in re.finditer(r"→\s*Fix:\s*`([^`]+)`", reply, re.IGNORECASE):
|
|
190
|
+
fixes.append((m.group(1).strip(), ""))
|
|
160
191
|
if not fixes:
|
|
161
192
|
for m in re.finditer(r"\[(\d+)\][^`\n]+`([^`]+)`", reply):
|
|
162
193
|
fixes.append((m.group(2).strip(), f"Fix #{m.group(1)}"))
|
|
163
|
-
# Fallback: EXEC: `command`
|
|
164
194
|
if not fixes:
|
|
165
195
|
for m in re.finditer(r"EXEC:\s*`([^`]+)`", reply, re.IGNORECASE):
|
|
166
196
|
fixes.append((m.group(1).strip(), ""))
|
|
197
|
+
return fixes
|
|
198
|
+
|
|
167
199
|
|
|
168
|
-
|
|
200
|
+
def _deduplicate(fixes: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
|
|
201
|
+
"""Remove diagnostic-only commands and deduplicate."""
|
|
169
202
|
filtered = [(cmd, comment) for cmd, comment in fixes if not _is_diagnostic_only_command(cmd)]
|
|
170
|
-
|
|
203
|
+
seen: set[str] = set()
|
|
204
|
+
unique: List[Tuple[str, str]] = []
|
|
205
|
+
for cmd, comment in filtered:
|
|
206
|
+
if cmd not in seen:
|
|
207
|
+
seen.add(cmd)
|
|
208
|
+
unique.append((cmd, comment))
|
|
209
|
+
return unique
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def extract_fixes(reply: str) -> List[Tuple[str, str]]:
|
|
213
|
+
"""Extract (command, comment) pairs from LLM reply."""
|
|
214
|
+
fixes = (
|
|
215
|
+
_pattern_strict_bold(reply)
|
|
216
|
+
or _pattern_backticks(reply)
|
|
217
|
+
or _pattern_no_backticks(reply)
|
|
218
|
+
or _pattern_fallbacks(reply)
|
|
219
|
+
)
|
|
220
|
+
return _deduplicate(fixes)
|
|
171
221
|
|
|
172
222
|
|
|
173
223
|
def extract_search_topic(llm_reply: str) -> str:
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Flatpak-specific cleanup CLI handlers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from fixos.cli._cleanup_utils import (
|
|
7
|
+
_format_bytes,
|
|
8
|
+
_parse_selection,
|
|
9
|
+
_parse_size_to_bytes,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Detailed interactive Flatpak cleanup with menu selection.
|
|
16
|
+
|
|
17
|
+
Wyświetla menu z opcjami i pokazuje korzyści na bazie rzeczywistych danych.
|
|
18
|
+
"""
|
|
19
|
+
from fixos.diagnostics.flatpak_analyzer import FlatpakAnalyzer
|
|
20
|
+
|
|
21
|
+
analyzer = FlatpakAnalyzer()
|
|
22
|
+
|
|
23
|
+
# Najpierw wykonaj pełną analizę
|
|
24
|
+
click.echo(click.style("\n🔍 Analizuję Flatpak...", fg="cyan"))
|
|
25
|
+
analysis = analyzer.analyze()
|
|
26
|
+
|
|
27
|
+
# JSON output mode
|
|
28
|
+
if json_output:
|
|
29
|
+
import json
|
|
30
|
+
click.echo(json.dumps(analysis, indent=2, default=str))
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Wyświetl podsumowanie stanu
|
|
34
|
+
_display_flatpak_status(analysis)
|
|
35
|
+
|
|
36
|
+
# Pobierz rekomendacje
|
|
37
|
+
recommendations = analyzer.get_cleanup_recommendations()
|
|
38
|
+
|
|
39
|
+
if not recommendations:
|
|
40
|
+
click.echo(click.style("\n✅ Brak rekomendacji czyszczenia - Flatpak jest w dobrym stanie.", fg="green"))
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Wyświetl menu z opcjami
|
|
44
|
+
click.echo(f"\n{click.style('='*60, fg='cyan')}")
|
|
45
|
+
click.echo(click.style("📋 WYBIERZ OPCJE DO WYKONANIA", fg="cyan", bold=True))
|
|
46
|
+
click.echo(click.style(f"{'='*60}", fg="cyan"))
|
|
47
|
+
|
|
48
|
+
if dry_run:
|
|
49
|
+
click.echo(click.style("\n[TRYB DRY-RUN] - brak faktycznych zmian\n", fg="yellow"))
|
|
50
|
+
|
|
51
|
+
# Wyświetl każdą opcję z korzyściami
|
|
52
|
+
total_potential_savings = 0
|
|
53
|
+
for i, rec in enumerate(recommendations, 1):
|
|
54
|
+
priority_icon = {"critical": "💣", "high": "🔥", "medium": "⚠️", "low": "💡"}.get(rec['priority'], "•")
|
|
55
|
+
risk_color = {"none": "green", "low": "green", "medium": "yellow", "high": "red"}.get(rec['risk'], "white")
|
|
56
|
+
|
|
57
|
+
click.echo(f"\n{click.style(f'[{i}]', fg='cyan', bold=True)} {priority_icon} {click.style(rec['description'], fg='yellow', bold=True)}")
|
|
58
|
+
click.echo(f" Komenda: {click.style(rec['action'], fg='white', dim=True)}")
|
|
59
|
+
|
|
60
|
+
# Korzyść na bazie rzeczywistych danych
|
|
61
|
+
savings = rec.get('estimated_savings', '0 B')
|
|
62
|
+
click.echo(f" {click.style('Korzyść:', fg='green')} ~{savings}")
|
|
63
|
+
total_potential_savings += _parse_size_to_bytes(savings)
|
|
64
|
+
|
|
65
|
+
click.echo(f" {click.style('Ryzyko:', fg='white')} {click.style(rec['risk'].upper(), fg=risk_color)}")
|
|
66
|
+
|
|
67
|
+
# Dodatkowe info
|
|
68
|
+
if rec.get('items'):
|
|
69
|
+
n_items = len(rec['items'])
|
|
70
|
+
click.echo(f" {click.style(f'Elementów: {n_items}', fg='white', dim=True)}")
|
|
71
|
+
|
|
72
|
+
# Podsumowanie potencjalnych korzyści
|
|
73
|
+
click.echo(f"\n{click.style('-'*60, fg='cyan')}")
|
|
74
|
+
click.echo(f"💰 {click.style('ŁĄCZNA POTENCJALNA KORZYŚĆ:', fg='green', bold=True)} ~{_format_bytes(total_potential_savings)}")
|
|
75
|
+
click.echo(click.style(f"{'-'*60}", fg="cyan"))
|
|
76
|
+
|
|
77
|
+
# Menu wyboru
|
|
78
|
+
click.echo(f"\n{click.style('Dostępne opcje:', fg='white', bold=True)}")
|
|
79
|
+
click.echo(f" {click.style('1,2,3...', fg='cyan')} - wybierz konkretne opcje (np. '1,3')")
|
|
80
|
+
click.echo(f" {click.style('all', fg='green')} - wykonaj wszystkie bezpieczne (ryzyko low/none)")
|
|
81
|
+
click.echo(f" {click.style('critical', fg='red')} - wykonaj tylko krytyczne")
|
|
82
|
+
click.echo(f" {click.style('none', fg='yellow')} - pomiń wszystko")
|
|
83
|
+
click.echo(f" {click.style('?', fg='white')} - pokaż szczegóły każdej opcji")
|
|
84
|
+
|
|
85
|
+
# Pytaj o wybór
|
|
86
|
+
click.echo()
|
|
87
|
+
selection = click.prompt(
|
|
88
|
+
click.style("Twój wybór", fg="cyan"),
|
|
89
|
+
default="none",
|
|
90
|
+
show_default=False
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Obsłuż '?'
|
|
94
|
+
if selection.strip() == '?':
|
|
95
|
+
_display_detailed_recommendations(recommendations)
|
|
96
|
+
selection = click.prompt(
|
|
97
|
+
click.style("\nTwój wybór", fg="cyan"),
|
|
98
|
+
default="none",
|
|
99
|
+
show_default=False
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Parsuj wybór
|
|
103
|
+
selected_indices = _parse_selection(selection, len(recommendations))
|
|
104
|
+
|
|
105
|
+
if not selected_indices:
|
|
106
|
+
click.echo(click.style("\n⏭️ Nie wybrano żadnych akcji.", fg="yellow"))
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Wykonaj wybrane akcje
|
|
110
|
+
results = {
|
|
111
|
+
"executed": [],
|
|
112
|
+
"skipped": [],
|
|
113
|
+
"failed": [],
|
|
114
|
+
"space_reclaimed": 0,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
click.echo(f"\n{click.style('='*60, fg='cyan')}")
|
|
118
|
+
click.echo(click.style("🚀 WYKONYWANIE WYBRANYCH AKCJI", fg="cyan", bold=True))
|
|
119
|
+
click.echo(f"{click.style('='*60, fg='cyan')}\n")
|
|
120
|
+
|
|
121
|
+
for idx in selected_indices:
|
|
122
|
+
rec = recommendations[idx]
|
|
123
|
+
click.echo(f"\n[{idx+1}/{len(recommendations)}] {rec['description']}")
|
|
124
|
+
|
|
125
|
+
if dry_run:
|
|
126
|
+
click.echo(click.style(f" [DRY-RUN] Wykonano by: {rec['action']}", fg="cyan"))
|
|
127
|
+
results['executed'].append({"action": rec['action'], "dry_run": True})
|
|
128
|
+
savings_bytes = _parse_size_to_bytes(rec.get('estimated_savings', '0 B'))
|
|
129
|
+
results['space_reclaimed'] += savings_bytes
|
|
130
|
+
else:
|
|
131
|
+
click.echo(f" 🚀 Wykonuję: {rec['action']}")
|
|
132
|
+
result = analyzer._execute_cleanup_action(rec)
|
|
133
|
+
|
|
134
|
+
if result['success']:
|
|
135
|
+
results['executed'].append({
|
|
136
|
+
"action": rec['action'],
|
|
137
|
+
"output": result.get('output', ''),
|
|
138
|
+
})
|
|
139
|
+
results['space_reclaimed'] += result.get('bytes_reclaimed', 0)
|
|
140
|
+
click.echo(click.style(" ✅ Sukces", fg="green"))
|
|
141
|
+
else:
|
|
142
|
+
results['failed'].append({
|
|
143
|
+
"action": rec['action'],
|
|
144
|
+
"error": result.get('error', 'Unknown error'),
|
|
145
|
+
})
|
|
146
|
+
click.echo(click.style(f" ❌ Błąd: {result.get('error', 'Unknown error')}", fg="red"))
|
|
147
|
+
|
|
148
|
+
# Podsumowanie końcowe
|
|
149
|
+
click.echo(f"\n{click.style('='*60, fg='cyan')}")
|
|
150
|
+
click.echo(click.style("📊 PODSUMOWANIE", fg="cyan", bold=True))
|
|
151
|
+
click.echo(click.style(f"{'='*60}", fg="cyan"))
|
|
152
|
+
click.echo(f" ✅ Wykonano: {len(results['executed'])}")
|
|
153
|
+
click.echo(f" ⏭️ Pominięto: {len(results['skipped'])}")
|
|
154
|
+
click.echo(f" ❌ Błędy: {len(results['failed'])}")
|
|
155
|
+
|
|
156
|
+
freed_gb = results['space_reclaimed'] / (1024**3)
|
|
157
|
+
if dry_run:
|
|
158
|
+
click.echo(click.style(f"\n 💰 [DRY-RUN] Zwolniono by: {freed_gb:.2f} GB", fg="cyan"))
|
|
159
|
+
else:
|
|
160
|
+
click.echo(click.style(f"\n 💰 Odzyskano: {freed_gb:.2f} GB", fg="green"))
|
|
161
|
+
|
|
162
|
+
click.echo(click.style("="*60 + "\n", fg="cyan"))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _display_flatpak_status(analysis: dict) -> None:
|
|
166
|
+
"""Wyświetl status Flatpak z rzeczywistymi danymi."""
|
|
167
|
+
click.echo(f"\n{click.style('='*60, fg='cyan')}")
|
|
168
|
+
click.echo(click.style("📊 STATUS FLATPAK", fg="cyan", bold=True))
|
|
169
|
+
click.echo(click.style(f"{'='*60}", fg="cyan"))
|
|
170
|
+
|
|
171
|
+
# Aplikacje
|
|
172
|
+
apps_count = len(analysis.get('installed_apps', []))
|
|
173
|
+
apps_size = sum(a.get('size_bytes', 0) for a in analysis.get('installed_apps', []))
|
|
174
|
+
click.echo(f"\n📱 Aplikacje: {click.style(str(apps_count), fg='yellow')} ({_format_bytes(apps_size)})")
|
|
175
|
+
|
|
176
|
+
# Runtimes
|
|
177
|
+
runtimes_count = len(analysis.get('installed_runtimes', []))
|
|
178
|
+
runtimes_size = sum(r.get('size_bytes', 0) for r in analysis.get('installed_runtimes', []))
|
|
179
|
+
click.echo(f"🔧 Runtime'y: {click.style(str(runtimes_count), fg='yellow')} ({_format_bytes(runtimes_size)})")
|
|
180
|
+
|
|
181
|
+
# Repo bloat
|
|
182
|
+
repo_bloat = analysis.get('repo_bloat', {})
|
|
183
|
+
if repo_bloat.get('bloat_detected'):
|
|
184
|
+
click.echo(f"\n{click.style('⚠️ PROBLEM: Repo zajmuje za dużo miejsca!', fg='red', bold=True)}")
|
|
185
|
+
click.echo(f" Repo: {_format_bytes(repo_bloat.get('repo_total_size', 0))}")
|
|
186
|
+
click.echo(f" Aplikacje+Runtime'y: {_format_bytes(repo_bloat.get('installed_size', 0))}")
|
|
187
|
+
click.echo(f" Ratio: {repo_bloat.get('ratio', 0):.1f}x (powinno być ~1-1.5x)")
|
|
188
|
+
click.echo(f" {click.style('Marnotrawstwo:', fg='red')} {_format_bytes(repo_bloat.get('wasted_size', 0))}")
|
|
189
|
+
|
|
190
|
+
# Duplikaty
|
|
191
|
+
duplicates = analysis.get('duplicate_apps', [])
|
|
192
|
+
if duplicates:
|
|
193
|
+
dup_size = sum(d.get('total_size', 0) for d in duplicates)
|
|
194
|
+
click.echo(f"\n🔄 Duplikaty aplikacji: {click.style(str(len(duplicates)), fg='yellow')} ({_format_bytes(dup_size)})")
|
|
195
|
+
for dup in duplicates[:3]:
|
|
196
|
+
click.echo(f" • {dup.get('name', '?')} ({dup.get('count', 0)} wersje)")
|
|
197
|
+
|
|
198
|
+
# Nieużywane runtime'y
|
|
199
|
+
unused = analysis.get('unused_runtimes', [])
|
|
200
|
+
if unused:
|
|
201
|
+
unused_size = analysis.get('total_size_unused', 0)
|
|
202
|
+
click.echo(f"\n🗑️ Nieużywane runtime'y: {click.style(str(len(unused)), fg='yellow')} ({_format_bytes(unused_size)})")
|
|
203
|
+
|
|
204
|
+
# Leftover data
|
|
205
|
+
leftover = analysis.get('leftover_data', [])
|
|
206
|
+
if leftover:
|
|
207
|
+
leftover_size = analysis.get('total_size_leftover', 0)
|
|
208
|
+
click.echo(f"\n📁 Dane po odinstalowanych: {click.style(str(len(leftover)), fg='yellow')} ({_format_bytes(leftover_size)})")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _display_detailed_recommendations(recommendations: list) -> None:
|
|
212
|
+
"""Wyświetl szczegółowe informacje o każdej rekomendacji."""
|
|
213
|
+
click.echo(f"\n{click.style('='*60, fg='cyan')}")
|
|
214
|
+
click.echo(click.style("📖 SZCZEGÓŁY REKOMENDACJI", fg="cyan", bold=True))
|
|
215
|
+
click.echo(click.style(f"{'='*60}", fg="cyan"))
|
|
216
|
+
|
|
217
|
+
for i, rec in enumerate(recommendations, 1):
|
|
218
|
+
click.echo(f"\n{click.style(f'[{i}]', fg='cyan', bold=True)} {rec['description']}")
|
|
219
|
+
click.echo(click.style(f"{'-'*50}", fg="white", dim=True))
|
|
220
|
+
click.echo(f"\n{rec['explanation']}")
|
|
221
|
+
|
|
222
|
+
if rec.get('items'):
|
|
223
|
+
click.echo(f"\nElementy do usunięcia ({len(rec['items'])}):")
|
|
224
|
+
for item in rec['items'][:10]:
|
|
225
|
+
name = item.get('name', '?')
|
|
226
|
+
size = item.get('size_human', '?')
|
|
227
|
+
click.echo(f" • {name} ({size})")
|
|
228
|
+
if len(rec['items']) > 10:
|
|
229
|
+
click.echo(f" ... i {len(rec['items']) - 10} więcej")
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Home directory analysis and cleanup CLI handlers."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from fixos.cli._cleanup_utils import _format_bytes, _parse_numeric_range_set
|
|
7
|
+
from fixos.constants import MAX_HOME_LARGE_FILES_DISPLAY, MAX_HOME_LARGE_DIRS_DISPLAY
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _display_home_items(large_files: list, large_dirs: list) -> None:
|
|
11
|
+
"""Print large files and directories found in home."""
|
|
12
|
+
if large_files:
|
|
13
|
+
click.echo(f"\n{click.style('📄 DUŻE PLIKI (>200MB):', fg='red', bold=True)}")
|
|
14
|
+
for i, f in enumerate(large_files[:MAX_HOME_LARGE_FILES_DISPLAY], 1):
|
|
15
|
+
click.echo(f" [{i:3d}] 📄 {click.style(f['path'], fg='cyan')}: {f['size_human']}")
|
|
16
|
+
if len(large_files) > MAX_HOME_LARGE_FILES_DISPLAY:
|
|
17
|
+
click.echo(f" ... i {len(large_files) - MAX_HOME_LARGE_FILES_DISPLAY} więcej")
|
|
18
|
+
if large_dirs:
|
|
19
|
+
click.echo(f"\n{click.style('📁 DUŻE FOLDERY (>500MB):', fg='magenta', bold=True)}")
|
|
20
|
+
offset = len(large_files)
|
|
21
|
+
for i, d in enumerate(large_dirs[:MAX_HOME_LARGE_DIRS_DISPLAY], 1):
|
|
22
|
+
click.echo(f" [{offset + i:3d}] 📁 {click.style(d['path'], fg='yellow')}: {d['size_human']}")
|
|
23
|
+
if len(large_dirs) > MAX_HOME_LARGE_DIRS_DISPLAY:
|
|
24
|
+
click.echo(f" ... i {len(large_dirs) - MAX_HOME_LARGE_DIRS_DISPLAY} więcej")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_home_selection(nums: str, large_files: list, large_dirs: list) -> list:
|
|
28
|
+
"""Convert index string into the list of selected home items."""
|
|
29
|
+
total_items = len(large_files) + len(large_dirs)
|
|
30
|
+
selected_indices = _parse_numeric_range_set(nums)
|
|
31
|
+
items = []
|
|
32
|
+
for i in selected_indices:
|
|
33
|
+
if 1 <= i <= len(large_files):
|
|
34
|
+
items.append(large_files[i - 1])
|
|
35
|
+
elif len(large_files) < i <= total_items:
|
|
36
|
+
items.append(large_dirs[i - len(large_files) - 1])
|
|
37
|
+
return items
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _remove_home_items(items_to_remove: list) -> None:
|
|
41
|
+
"""Delete files/directories and report result."""
|
|
42
|
+
import os
|
|
43
|
+
import shutil
|
|
44
|
+
click.echo(f"\n{click.style('🚀 USUWANIE ELEMENTÓW:', fg='cyan', bold=True)}")
|
|
45
|
+
for item in items_to_remove:
|
|
46
|
+
click.echo(f"\n• {item['path']}")
|
|
47
|
+
try:
|
|
48
|
+
if item.get('type') == 'file':
|
|
49
|
+
os.remove(item['path'])
|
|
50
|
+
else:
|
|
51
|
+
shutil.rmtree(item['path'])
|
|
52
|
+
click.echo(click.style(" ✅ Usunięto", fg="green"))
|
|
53
|
+
except Exception as e:
|
|
54
|
+
click.echo(click.style(f" ❌ Błąd: {e}", fg="red"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _show_home_item_info(nums: str, large_files: list, large_dirs: list, total_items: int) -> None:
|
|
58
|
+
"""Display info about a single home item by index."""
|
|
59
|
+
import os
|
|
60
|
+
import mimetypes
|
|
61
|
+
try:
|
|
62
|
+
idx = int(nums[5:])
|
|
63
|
+
if 1 <= idx <= len(large_files):
|
|
64
|
+
f = large_files[idx - 1]
|
|
65
|
+
click.echo(f"\n{click.style('📦 SZCZEGÓŁY PLIKU:', fg='yellow', bold=True)}")
|
|
66
|
+
click.echo(f" Ścieżka: {f['path']}")
|
|
67
|
+
click.echo(f" Rozmiar: {f['size_human']}")
|
|
68
|
+
if os.path.exists(f['path']):
|
|
69
|
+
click.echo(click.style(" ✅ Istnieje", fg="green"))
|
|
70
|
+
mime_type, _ = mimetypes.guess_type(f['path'])
|
|
71
|
+
if mime_type:
|
|
72
|
+
click.echo(f" Typ: {mime_type}")
|
|
73
|
+
else:
|
|
74
|
+
click.echo(click.style(" ⚠️ Nie istnieje", fg="yellow"))
|
|
75
|
+
elif len(large_files) < idx <= total_items:
|
|
76
|
+
d = large_dirs[idx - len(large_files) - 1]
|
|
77
|
+
click.echo(f"\n{click.style('📦 SZCZEGÓŁY FOLDERU:', fg='yellow', bold=True)}")
|
|
78
|
+
click.echo(f" Ścieżka: {d['path']}")
|
|
79
|
+
click.echo(f" Rozmiar: {d['size_human']}")
|
|
80
|
+
else:
|
|
81
|
+
click.echo(click.style(f"❌ Nieprawidłowy numer: {idx}", fg="red"))
|
|
82
|
+
except ValueError:
|
|
83
|
+
click.echo(click.style("❌ Format: info:N (np. info:5)", fg="red"))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _handle_home_analysis(analyzer, dry_run: bool) -> None:
|
|
87
|
+
"""Handle home directory large-file analysis and removal."""
|
|
88
|
+
large_files = getattr(analyzer, 'home_large_files', [])
|
|
89
|
+
large_dirs = getattr(analyzer, 'home_large_dirs', [])
|
|
90
|
+
|
|
91
|
+
if not large_files and not large_dirs:
|
|
92
|
+
click.echo(click.style("\n❌ Brak dużych plików w home do analizy.", fg="red"))
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
click.echo(f"\n{click.style('🏠 ANALIZA HOME DIRECTORY:', fg='yellow', bold=True)}")
|
|
96
|
+
click.echo(click.style(f"Znaleziono {len(large_files)} dużych plików (>200MB) i {len(large_dirs)} folderów (>500MB)", fg="white"))
|
|
97
|
+
_display_home_items(large_files, large_dirs)
|
|
98
|
+
|
|
99
|
+
total_items = len(large_files) + len(large_dirs)
|
|
100
|
+
click.echo(f"\n{click.style('💡 Wybierz numery plików/folderów do usunięcia:', fg='cyan')}")
|
|
101
|
+
click.echo(click.style(" • Pliki: 1-{}, Foldery: {}-{}".format(len(large_files), len(large_files)+1, total_items), fg="white"))
|
|
102
|
+
|
|
103
|
+
nums = click.prompt(click.style("\nWybierz numery (np. 1,3,5-10) lub 'info:N' dla szczegółów", fg="cyan"), default="")
|
|
104
|
+
if not nums.strip():
|
|
105
|
+
click.echo(click.style("⏭️ Nie wybrano żadnych elementów.", fg="yellow"))
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
nums = nums.strip().lower()
|
|
109
|
+
if nums.startswith('info:'):
|
|
110
|
+
_show_home_item_info(nums, large_files, large_dirs, total_items)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
items_to_remove = _resolve_home_selection(nums, large_files, large_dirs)
|
|
114
|
+
if not items_to_remove:
|
|
115
|
+
click.echo(click.style("❌ Nie wybrano żadnych elementów.", fg="red"))
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
click.echo(f"\n{click.style('📦 WYBRANE ELEMENTY DO USUNIĘCIA:', fg='red', bold=True)}")
|
|
119
|
+
for i, item in enumerate(items_to_remove, 1):
|
|
120
|
+
icon = "📄" if item.get('type') == 'file' else "📁"
|
|
121
|
+
click.echo(f" {i:3d}. {icon} {click.style(item['path'], fg='cyan')}: {item['size_human']}")
|
|
122
|
+
total_size = sum(item['size'] for item in items_to_remove)
|
|
123
|
+
click.echo(f"\n{click.style('💰 Łącznie do usunięcia:', fg='red', bold=True)} {_format_bytes(total_size)}")
|
|
124
|
+
|
|
125
|
+
if dry_run:
|
|
126
|
+
click.echo(click.style("\n[DRY-RUN] - symulacja, nic nie zostanie usunięte", fg="cyan"))
|
|
127
|
+
return
|
|
128
|
+
if not click.confirm(click.style("\n⚠️ Potwierdzasz usunięcie tych elementów?", fg="yellow"), default=False):
|
|
129
|
+
click.echo(click.style("⏭️ Anulowano.", fg="yellow"))
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
_remove_home_items(items_to_remove)
|