fixos 2.2.20__tar.gz → 2.2.22__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.20 → fixos-2.2.22}/CHANGELOG.md +23 -0
- {fixos-2.2.20 → fixos-2.2.22}/PKG-INFO +5 -5
- {fixos-2.2.20 → fixos-2.2.22}/README.md +4 -4
- {fixos-2.2.20 → fixos-2.2.22}/fixos/__init__.py +1 -1
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/session_core.py +20 -1
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/session_handlers.py +48 -3
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/session_io.py +6 -6
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/ask_cmd.py +6 -5
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/cleanup_cmd.py +33 -24
- {fixos-2.2.20 → fixos-2.2.22}/fixos/platform_utils.py +18 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/PKG-INFO +5 -5
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/top_level.txt +1 -0
- {fixos-2.2.20 → fixos-2.2.22}/pyproject.toml +1 -1
- {fixos-2.2.20 → fixos-2.2.22}/setup.py +1 -1
- {fixos-2.2.20 → fixos-2.2.22}/.env.example +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/LICENSE +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/MANIFEST.in +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/README.md +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/TEST_RESULTS.md +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/TEST_RESULTS_V2.md +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/alpine/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/arch/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/base/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/broken-audio/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/broken-full/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/broken-network/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/broken-thumbnails/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/debian/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/docker-compose.multi-system.yml +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/docker-compose.yml +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/fedora/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/test-multi-system.sh +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docker/ubuntu/Dockerfile +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docs/examples/advanced_usage.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/docs/examples/quickstart.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/autonomous.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/autonomous_session.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/hitl.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/agent/hitl_session.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/anonymizer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/config_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/features_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/fix_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/history_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/main.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/orchestrate_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/profile_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/provider_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/quickfix_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/report_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/rollback_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/scan_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/shared.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/token_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli/watch_cmd.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/cli.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/config.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/config_interactive.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/constants.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/_shared.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/audio.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/hardware.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/resources.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/security.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/system_core.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/checks/thumbnails.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/dev_project_analyzer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/disk_analyzer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/flatpak_analyzer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/service_cleanup.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/service_details.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/service_scanner.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/storage_analyzer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/diagnostics/system_checks.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/auditor.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/catalog.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/installer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/profiles.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/features/renderer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/fixes/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/interactive/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/interactive/cleanup_planner.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/llm_shell.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/orchestrator/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/orchestrator/executor.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/orchestrator/graph.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/orchestrator/orchestrator.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/orchestrator/rollback.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/base.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/audio.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/disk.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/hardware.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/resources.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/security.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/builtin/thumbnails.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/plugins/registry.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/profiles/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/providers/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/providers/llm.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/providers/llm_analyzer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/providers/schemas.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/system_checks.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/utils/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/utils/anonymizer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/utils/terminal.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/utils/timeout.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/utils/web_search.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos/watch.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/SOURCES.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/dependency_links.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/entry_points.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/fixos.egg-info/requires.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/pytest.ini +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/requirements-dev.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/requirements.txt +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/scripts/pyqual-calibrate.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/setup.cfg +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/conftest.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_anonymization_layers.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_audio_broken.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_cli.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_executor.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_multi_system.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_network_broken.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/e2e/test_thumbnails_broken.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/test_fixos.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/__init__.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_anonymizer.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_core.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_executor.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_orchestrator.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_service_cleanup.py +0 -0
- {fixos-2.2.20 → fixos-2.2.22}/tests/unit/test_service_scanner.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.22] - 2026-05-04
|
|
154
|
+
|
|
155
|
+
### Docs
|
|
156
|
+
- Update README.md
|
|
157
|
+
- Update REFACTORING_PROGRESS.md
|
|
158
|
+
|
|
159
|
+
### Other
|
|
160
|
+
- Update fixos/agent/session_core.py
|
|
161
|
+
- Update fixos/agent/session_handlers.py
|
|
162
|
+
- Update fixos/agent/session_io.py
|
|
163
|
+
- Update fixos/cli/ask_cmd.py
|
|
164
|
+
- Update fixos/cli/cleanup_cmd.py
|
|
165
|
+
- Update fixos/platform_utils.py
|
|
166
|
+
- Update uv.lock
|
|
167
|
+
|
|
168
|
+
## [2.2.21] - 2026-05-04
|
|
169
|
+
|
|
170
|
+
### Docs
|
|
171
|
+
- Update README.md
|
|
172
|
+
|
|
173
|
+
### Other
|
|
174
|
+
- Update uv.lock
|
|
175
|
+
|
|
153
176
|
## [2.2.20] - 2026-05-04
|
|
154
177
|
|
|
155
178
|
### Docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixos
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.22
|
|
4
4
|
Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
|
|
5
5
|
Home-page: https://github.com/wronai/fixos
|
|
6
6
|
Author: fixos contributors
|
|
@@ -63,11 +63,11 @@ AI-powered OS Diagnostics
|
|
|
63
63
|
|
|
64
64
|
## AI Cost Tracking
|
|
65
65
|
|
|
66
|
-
     
|
|
67
|
+
  
|
|
68
68
|
|
|
69
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
70
|
-
- 👤 **Human dev:** ~$
|
|
69
|
+
- 🤖 **LLM usage:** $7.5000 (121 commits)
|
|
70
|
+
- 👤 **Human dev:** ~$2280 (22.8h @ $100/h, 30min dedup)
|
|
71
71
|
|
|
72
72
|
Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
73
73
|
|
|
@@ -19,11 +19,11 @@ AI-powered OS Diagnostics
|
|
|
19
19
|
|
|
20
20
|
## AI Cost Tracking
|
|
21
21
|
|
|
22
|
-
     
|
|
23
|
+
  
|
|
24
24
|
|
|
25
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
26
|
-
- 👤 **Human dev:** ~$
|
|
25
|
+
- 🤖 **LLM usage:** $7.5000 (121 commits)
|
|
26
|
+
- 👤 **Human dev:** ~$2280 (22.8h @ $100/h, 30min dedup)
|
|
27
27
|
|
|
28
28
|
Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
29
29
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""fixos – AI-powered Linux/Windows diagnostics and repair."""
|
|
2
|
-
__version__ = "2.2.
|
|
2
|
+
__version__ = "2.2.22"
|
|
@@ -47,6 +47,8 @@ IMPORTANT RULES:
|
|
|
47
47
|
- Do NOT use read-only diagnostics as fixes (e.g. `df -h`, `free -h`, `ls`, `cat`, `grep`, `systemctl status`).
|
|
48
48
|
- If needed, mention diagnostics in explanation, but propose executable repair steps in `Komenda`.
|
|
49
49
|
- For package upgrades and heavy operations, provide the real fix command (e.g. `dnf upgrade -y`).
|
|
50
|
+
- When disk usage is critically high (>90%), ALWAYS propose cleanup commands FIRST.
|
|
51
|
+
- NEVER suggest package upgrades or installations BEFORE cleanup has freed sufficient space and been verified.
|
|
50
52
|
|
|
51
53
|
Always end with:
|
|
52
54
|
━━━ DOSTĘPNE AKCJE ━━━
|
|
@@ -63,10 +65,27 @@ IMPORTANT: Adapt commands to the detected OS (Linux/Windows/macOS).
|
|
|
63
65
|
|
|
64
66
|
def _is_diagnostic_only_command(cmd: str) -> bool:
|
|
65
67
|
"""Return True if command is read-only and not a repair action."""
|
|
66
|
-
|
|
68
|
+
# Split by common shell delimiters to check each part
|
|
69
|
+
parts = re.split(r' && | \|\| |; ', cmd)
|
|
70
|
+
|
|
71
|
+
# If any part of a compound command looks like a repair, the whole thing is actionable
|
|
72
|
+
for part in parts:
|
|
73
|
+
if not _is_part_diagnostic_only(part):
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _is_part_diagnostic_only(part: str) -> bool:
|
|
79
|
+
"""Helper for _is_diagnostic_only_command to check a single command part."""
|
|
80
|
+
normalized = part.strip().lower()
|
|
67
81
|
if normalized.startswith("sudo "):
|
|
68
82
|
normalized = normalized[5:].strip()
|
|
69
83
|
|
|
84
|
+
# Special case: diagnostic tools used for cleanup/repair
|
|
85
|
+
if normalized.startswith("journalctl"):
|
|
86
|
+
if "--vacuum-" in normalized or "--flush" in normalized or "--rotate" in normalized:
|
|
87
|
+
return False
|
|
88
|
+
|
|
70
89
|
diagnostic_prefixes = (
|
|
71
90
|
"df ",
|
|
72
91
|
"free ",
|
|
@@ -13,7 +13,7 @@ from ..constants import (
|
|
|
13
13
|
LONG_COMMAND_TIMEOUT,
|
|
14
14
|
)
|
|
15
15
|
from ..platform_utils import (
|
|
16
|
-
is_dangerous, elevate_cmd, run_command,
|
|
16
|
+
is_dangerous, is_interactive_blocker, elevate_cmd, run_command,
|
|
17
17
|
)
|
|
18
18
|
from ..utils.anonymizer import anonymize
|
|
19
19
|
from ..utils.web_search import search_all, format_results_for_llm
|
|
@@ -84,6 +84,34 @@ def handle_describe_problem(messages: list, ask_fn) -> bool:
|
|
|
84
84
|
return True
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def _sort_fixes_by_priority(fixes: list) -> list:
|
|
88
|
+
"""Move cleanup commands before disk-consuming operations."""
|
|
89
|
+
cleanup_patterns = (
|
|
90
|
+
r"journalctl.*--vacuum",
|
|
91
|
+
r"dnf\s+(remove|autoremove|clean)",
|
|
92
|
+
r"apt\s+(autoremove|clean)",
|
|
93
|
+
r"pacman\s+-Sc",
|
|
94
|
+
r"rm\s+-[rf]",
|
|
95
|
+
r"swapoff",
|
|
96
|
+
)
|
|
97
|
+
disk_hungry_patterns = (
|
|
98
|
+
r"\bdnf\s+(upgrade|update|distro-sync|install)\b",
|
|
99
|
+
r"\bapt(-get)?\s+(upgrade|install|full-upgrade)\b",
|
|
100
|
+
r"\bpacman\s+-S[yuy]*\b",
|
|
101
|
+
r"\bflatpak\s+(update|install)\b",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def score(item):
|
|
105
|
+
cmd = item[0].lower()
|
|
106
|
+
if any(re.search(p, cmd) for p in cleanup_patterns):
|
|
107
|
+
return 0
|
|
108
|
+
if any(re.search(p, cmd) for p in disk_hungry_patterns):
|
|
109
|
+
return 2
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
return sorted(fixes, key=score)
|
|
113
|
+
|
|
114
|
+
|
|
87
115
|
def handle_execute_all(
|
|
88
116
|
fixes: list,
|
|
89
117
|
messages: list,
|
|
@@ -94,7 +122,8 @@ def handle_execute_all(
|
|
|
94
122
|
if not fixes:
|
|
95
123
|
io.print_no_commands()
|
|
96
124
|
return True
|
|
97
|
-
|
|
125
|
+
|
|
126
|
+
fixes = _sort_fixes_by_priority(fixes)
|
|
98
127
|
io.print_executing_all(len(fixes))
|
|
99
128
|
summary_lines = []
|
|
100
129
|
for cmd, comment in fixes:
|
|
@@ -192,12 +221,24 @@ def handle_free_text(user_in: str, messages: list) -> bool:
|
|
|
192
221
|
def run_single_command(cmd: str, comment: str) -> CmdResult:
|
|
193
222
|
"""Run a command with full transparency and safety checks."""
|
|
194
223
|
cmd = elevate_cmd(cmd)
|
|
224
|
+
|
|
225
|
+
# Check for dangerous commands
|
|
195
226
|
danger = is_dangerous(cmd)
|
|
196
227
|
if danger:
|
|
197
228
|
io.print_blocked_command(cmd, danger)
|
|
198
229
|
return CmdResult(cmd=cmd, comment=comment, ok=False,
|
|
199
230
|
stdout="", stderr=f"Zablokowano: {danger}", returncode=-99)
|
|
200
231
|
|
|
232
|
+
# Check for interactive blockers
|
|
233
|
+
blocker = is_interactive_blocker(cmd)
|
|
234
|
+
if blocker:
|
|
235
|
+
from rich.text import Text
|
|
236
|
+
io.console.print(f"\n [bold yellow]⚠️ OSTRZEŻENIE:[/bold yellow] {blocker}")
|
|
237
|
+
io.console.print(f" Ta komenda może zawiesić sesję w trybie nieinteraktywnym.")
|
|
238
|
+
if io.console.input(" Czy na pewno chcesz spróbować? [y/N]: ").lower() not in ("y", "yes", "tak"):
|
|
239
|
+
return CmdResult(cmd=cmd, comment=comment, ok=False,
|
|
240
|
+
stdout="", stderr="Anulowano przez użytkownika (interaktywna).", returncode=-1, skipped=True)
|
|
241
|
+
|
|
201
242
|
io.print_cmd_preview(cmd, comment)
|
|
202
243
|
ans = io.ask_execute_prompt()
|
|
203
244
|
if ans in ("n", "no", "nie"):
|
|
@@ -206,7 +247,11 @@ def run_single_command(cmd: str, comment: str) -> CmdResult:
|
|
|
206
247
|
|
|
207
248
|
timeout = _resolve_command_timeout(cmd)
|
|
208
249
|
io.console.print(" [dim]⏳ Wykonuję...[/dim]", end="")
|
|
209
|
-
|
|
250
|
+
|
|
251
|
+
# Suspend session timeout during command execution
|
|
252
|
+
with io.suspend_timeout():
|
|
253
|
+
ok, stdout, stderr, rc = run_command(cmd, timeout=timeout)
|
|
254
|
+
|
|
210
255
|
io.console.print("\r" + " " * 30 + "\r", end="")
|
|
211
256
|
result = CmdResult(cmd=cmd, comment=comment, ok=ok,
|
|
212
257
|
stdout=stdout, stderr=stderr, returncode=rc)
|
|
@@ -30,7 +30,7 @@ _session_ref = None
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@contextmanager
|
|
33
|
-
def
|
|
33
|
+
def suspend_timeout():
|
|
34
34
|
"""Context manager to temporarily suspend session timeout during user input."""
|
|
35
35
|
global _timeout_handler, _timeout_seconds, _session_ref
|
|
36
36
|
try:
|
|
@@ -118,7 +118,7 @@ def ask_user_problem() -> str:
|
|
|
118
118
|
console.print()
|
|
119
119
|
console.print(Panel(body, title="[bold cyan]💬 OPISZ SWÓJ PROBLEM[/bold cyan]", border_style="cyan"))
|
|
120
120
|
try:
|
|
121
|
-
with
|
|
121
|
+
with suspend_timeout():
|
|
122
122
|
return console.input(" [bold cyan]Twój problem:[/bold cyan] ").strip()
|
|
123
123
|
except (EOFError, KeyboardInterrupt):
|
|
124
124
|
return ""
|
|
@@ -230,13 +230,13 @@ def print_searching() -> None:
|
|
|
230
230
|
|
|
231
231
|
def ask_execute_prompt() -> str:
|
|
232
232
|
"""Ask user if they want to execute a command."""
|
|
233
|
-
with
|
|
233
|
+
with suspend_timeout():
|
|
234
234
|
return console.input(" [bold]Wykonać?[/bold] \\[Y/n]: ").strip().lower()
|
|
235
235
|
|
|
236
236
|
|
|
237
237
|
def ask_low_confidence_search() -> bool:
|
|
238
238
|
"""Ask user if they want to search when LLM is uncertain."""
|
|
239
|
-
with
|
|
239
|
+
with suspend_timeout():
|
|
240
240
|
return console.input(
|
|
241
241
|
"\n [dim]💡 LLM niepewny – szukać zewnętrznie? [y/N]:[/dim] "
|
|
242
242
|
).strip().lower() in ("y", "yes", "tak")
|
|
@@ -244,7 +244,7 @@ def ask_low_confidence_search() -> bool:
|
|
|
244
244
|
|
|
245
245
|
def ask_send_data() -> bool:
|
|
246
246
|
"""Ask user if they want to send data to LLM."""
|
|
247
|
-
with
|
|
247
|
+
with suspend_timeout():
|
|
248
248
|
ans = console.input("\n Czy wysłać te dane do LLM? \\[Y/n]: ").strip().lower()
|
|
249
249
|
return ans not in ("n", "no", "nie")
|
|
250
250
|
|
|
@@ -252,7 +252,7 @@ def ask_send_data() -> bool:
|
|
|
252
252
|
def get_user_input(remaining: int) -> str:
|
|
253
253
|
"""Get user input with prompt."""
|
|
254
254
|
try:
|
|
255
|
-
with
|
|
255
|
+
with suspend_timeout():
|
|
256
256
|
return console.input(f"\n [bold cyan]fixos [{fmt_time(remaining)}] ❯[/bold cyan] ").strip()
|
|
257
257
|
except (EOFError, KeyboardInterrupt):
|
|
258
258
|
return ""
|
|
@@ -36,7 +36,7 @@ _OBJECT_KEYWORDS: list[tuple[list[str], tuple]] = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def _object_based_match(prompt_lower: str) -> object:
|
|
39
|
+
def _object_based_match(prompt_lower: str) -> object | None:
|
|
40
40
|
"""Fallback object-based matching when no action keyword is found."""
|
|
41
41
|
for keywords, cmd in _OBJECT_KEYWORDS:
|
|
42
42
|
if any(kw in prompt_lower for kw in keywords):
|
|
@@ -44,7 +44,7 @@ def _object_based_match(prompt_lower: str) -> object:
|
|
|
44
44
|
return None
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def _match_heuristic_command(prompt_lower: str) -> object:
|
|
47
|
+
def _match_heuristic_command(prompt_lower: str) -> object | None:
|
|
48
48
|
"""
|
|
49
49
|
Match user prompt against heuristic keyword mappings.
|
|
50
50
|
|
|
@@ -62,14 +62,15 @@ def _match_heuristic_command(prompt_lower: str) -> object:
|
|
|
62
62
|
return _object_based_match(prompt_lower)
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def _format_command(matched_cmd) -> str:
|
|
65
|
+
def _format_command(matched_cmd: object) -> str:
|
|
66
66
|
"""Convert matched command to string format."""
|
|
67
67
|
if isinstance(matched_cmd, str):
|
|
68
68
|
return matched_cmd
|
|
69
|
-
|
|
69
|
+
elif isinstance(matched_cmd, (list, tuple)):
|
|
70
70
|
cmd_program = matched_cmd[0]
|
|
71
71
|
cmd_args = matched_cmd[1] if len(matched_cmd) > 1 else []
|
|
72
|
-
return " ".join([cmd_program] + cmd_args)
|
|
72
|
+
return " ".join([str(cmd_program)] + [str(a) for a in cmd_args])
|
|
73
|
+
return str(matched_cmd)
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def _build_output_dict(
|
|
@@ -4,7 +4,16 @@ Cleanup command for fixOS CLI - service data cleanup with detailed flatpak suppo
|
|
|
4
4
|
import click
|
|
5
5
|
import subprocess
|
|
6
6
|
from fixos.diagnostics.service_scanner import ServiceDataScanner
|
|
7
|
-
|
|
7
|
+
from fixos.constants import (
|
|
8
|
+
DEFAULT_COMMAND_TIMEOUT,
|
|
9
|
+
FAST_COMMAND_TIMEOUT,
|
|
10
|
+
MAX_SEARCH_QUERY_LENGTH,
|
|
11
|
+
HOSTNAME_DISPLAY_LENGTH,
|
|
12
|
+
CONFIG_DISPLAY_LENGTH,
|
|
13
|
+
MAX_OUTPUT_LINES,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Local constants for internal logic
|
|
8
17
|
CONSTANT_3 = 3
|
|
9
18
|
CONSTANT_4 = 4
|
|
10
19
|
CONSTANT_5 = 5
|
|
@@ -16,7 +25,7 @@ CONSTANT_50 = 50
|
|
|
16
25
|
CONSTANT_60 = 60
|
|
17
26
|
CONSTANT_90 = 90
|
|
18
27
|
CONSTANT_120 = 120
|
|
19
|
-
CONSTANT_300 =
|
|
28
|
+
CONSTANT_300 = DEFAULT_COMMAND_TIMEOUT
|
|
20
29
|
CONSTANT_500 = 500
|
|
21
30
|
CONSTANT_1024 = 1024
|
|
22
31
|
|
|
@@ -104,7 +113,7 @@ def cleanup_services(threshold, services, json_output, cleanup, dry_run, list_on
|
|
|
104
113
|
def _display_cleanup_summary(plan: dict, threshold: int) -> None:
|
|
105
114
|
"""Display cleanup plan summary header."""
|
|
106
115
|
click.echo(click.style(f"\nSkanowanie usług (próg: {threshold} MB)...", fg="cyan"))
|
|
107
|
-
click.echo(click.style("═
|
|
116
|
+
click.echo(click.style(f"{'═' * CONSTANT_60}", fg="cyan"))
|
|
108
117
|
|
|
109
118
|
if plan["services_found"] == 0:
|
|
110
119
|
click.echo(click.style("\nNie znaleziono usług powyżej progu.", fg="green"))
|
|
@@ -281,9 +290,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None
|
|
|
281
290
|
return
|
|
282
291
|
|
|
283
292
|
# Wyświetl menu z opcjami
|
|
284
|
-
click.echo("\n
|
|
293
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
285
294
|
click.echo(click.style("📋 WYBIERZ OPCJE DO WYKONANIA", fg="cyan", bold=True))
|
|
286
|
-
click.echo(click.style("=
|
|
295
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
287
296
|
|
|
288
297
|
if dry_run:
|
|
289
298
|
click.echo(click.style("\n[TRYB DRY-RUN] - brak faktycznych zmian\n", fg="yellow"))
|
|
@@ -309,9 +318,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None
|
|
|
309
318
|
click.echo(f" {click.style(f'Elementów: {len(rec["items"])}', fg='white', dim=True)}")
|
|
310
319
|
|
|
311
320
|
# Podsumowanie potencjalnych korzyści
|
|
312
|
-
click.echo("\n
|
|
321
|
+
click.echo(f"\n{click.style('-'*CONSTANT_60, fg='cyan')}")
|
|
313
322
|
click.echo(f"💰 {click.style('ŁĄCZNA POTENCJALNA KORZYŚĆ:', fg='green', bold=True)} ~{_format_bytes(total_potential_savings)}")
|
|
314
|
-
click.echo(click.style("-
|
|
323
|
+
click.echo(click.style(f"{'-'*CONSTANT_60}", fg="cyan"))
|
|
315
324
|
|
|
316
325
|
# Menu wyboru
|
|
317
326
|
click.echo(f"\n{click.style('Dostępne opcje:', fg='white', bold=True)}")
|
|
@@ -353,9 +362,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None
|
|
|
353
362
|
"space_reclaimed": 0,
|
|
354
363
|
}
|
|
355
364
|
|
|
356
|
-
click.echo("\n
|
|
365
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
357
366
|
click.echo(click.style("🚀 WYKONYWANIE WYBRANYCH AKCJI", fg="cyan", bold=True))
|
|
358
|
-
click.echo(click.style(
|
|
367
|
+
click.echo(f"{click.style('='*CONSTANT_60, fg='cyan')}\n")
|
|
359
368
|
|
|
360
369
|
for idx in selected_indices:
|
|
361
370
|
rec = recommendations[idx]
|
|
@@ -385,9 +394,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None
|
|
|
385
394
|
click.echo(click.style(f" ❌ Błąd: {result.get('error', 'Unknown error')}", fg="red"))
|
|
386
395
|
|
|
387
396
|
# Podsumowanie końcowe
|
|
388
|
-
click.echo("\n
|
|
397
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
389
398
|
click.echo(click.style("📊 PODSUMOWANIE", fg="cyan", bold=True))
|
|
390
|
-
click.echo(click.style("=
|
|
399
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
391
400
|
click.echo(f" ✅ Wykonano: {len(results['executed'])}")
|
|
392
401
|
click.echo(f" ⏭️ Pominięto: {len(results['skipped'])}")
|
|
393
402
|
click.echo(f" ❌ Błędy: {len(results['failed'])}")
|
|
@@ -403,9 +412,9 @@ def _cleanup_flatpak_detailed(scanner, json_output: bool, dry_run: bool) -> None
|
|
|
403
412
|
|
|
404
413
|
def _display_flatpak_status(analysis: dict) -> None:
|
|
405
414
|
"""Wyświetl status Flatpak z rzeczywistymi danymi"""
|
|
406
|
-
click.echo("\n
|
|
415
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
407
416
|
click.echo(click.style("📊 STATUS FLATPAK", fg="cyan", bold=True))
|
|
408
|
-
click.echo(click.style("=
|
|
417
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
409
418
|
|
|
410
419
|
# Aplikacje
|
|
411
420
|
apps_count = len(analysis.get('installed_apps', []))
|
|
@@ -449,13 +458,13 @@ def _display_flatpak_status(analysis: dict) -> None:
|
|
|
449
458
|
|
|
450
459
|
def _display_detailed_recommendations(recommendations: list) -> None:
|
|
451
460
|
"""Wyświetl szczegółowe informacje o każdej rekomendacji"""
|
|
452
|
-
click.echo("\n
|
|
461
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
453
462
|
click.echo(click.style("📖 SZCZEGÓŁY REKOMENDACJI", fg="cyan", bold=True))
|
|
454
|
-
click.echo(click.style("=
|
|
463
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
455
464
|
|
|
456
465
|
for i, rec in enumerate(recommendations, 1):
|
|
457
466
|
click.echo(f"\n{click.style(f'[{i}]', fg='cyan', bold=True)} {rec['description']}")
|
|
458
|
-
click.echo(click.style("-
|
|
467
|
+
click.echo(click.style(f"{'-'*CONSTANT_50}", fg="white", dim=True))
|
|
459
468
|
click.echo(f"\n{rec['explanation']}")
|
|
460
469
|
|
|
461
470
|
if rec.get('items'):
|
|
@@ -544,9 +553,9 @@ def _build_dep_types(items: list) -> dict:
|
|
|
544
553
|
|
|
545
554
|
def _display_full_system_menu(analyzer, analysis: dict, safe_items: list, medium_items: list, dry_run: bool) -> str:
|
|
546
555
|
"""Display recommendations and menu for full system cleanup. Returns user selection."""
|
|
547
|
-
click.echo("\n
|
|
556
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
548
557
|
click.echo(click.style("📋 REKOMENDACJE", fg="cyan", bold=True))
|
|
549
|
-
click.echo(click.style("=
|
|
558
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
550
559
|
|
|
551
560
|
if dry_run:
|
|
552
561
|
click.echo(click.style("\n[TRYB DRY-RUN] - brak faktycznych zmian\n", fg="yellow"))
|
|
@@ -567,9 +576,9 @@ def _display_full_system_menu(analyzer, analysis: dict, safe_items: list, medium
|
|
|
567
576
|
click.echo(f" → {click.style(item.cleanup_command, fg='cyan', dim=True)}")
|
|
568
577
|
click.echo(f"\n 💰 Łącznie: {click.style(_format_bytes(total_medium), fg='yellow')}")
|
|
569
578
|
|
|
570
|
-
click.echo("\n
|
|
579
|
+
click.echo(f"\n{click.style('-'*CONSTANT_60, fg='cyan')}")
|
|
571
580
|
click.echo(f"💰 {click.style('ŁĄCZNIE DO ODZYSKANIA:', fg='green', bold=True)} {analysis['total_reclaimable_human']}")
|
|
572
|
-
click.echo(click.style("-
|
|
581
|
+
click.echo(click.style(f"{'-'*CONSTANT_60}", fg="cyan"))
|
|
573
582
|
|
|
574
583
|
dev_items = [item for item in analyzer.items if item.category == 'dev_projects']
|
|
575
584
|
if dev_items:
|
|
@@ -1094,9 +1103,9 @@ def _select_cleanup_items_by_filter(selection: str, analyzer, safe_items: list)
|
|
|
1094
1103
|
|
|
1095
1104
|
def _execute_full_cleanup(items_to_clean: list, dry_run: bool) -> None:
|
|
1096
1105
|
"""Execute cleanup commands for a list of StorageItems."""
|
|
1097
|
-
click.echo("\n
|
|
1106
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
1098
1107
|
click.echo(click.style("🚀 WYKONYWANIE CZYSZCZENIA", fg="cyan", bold=True))
|
|
1099
|
-
click.echo(click.style(
|
|
1108
|
+
click.echo(f"{click.style('='*CONSTANT_60, fg='cyan')}\n")
|
|
1100
1109
|
|
|
1101
1110
|
results = {"success": 0, "failed": 0, "space_reclaimed": 0}
|
|
1102
1111
|
for item in items_to_clean:
|
|
@@ -1127,9 +1136,9 @@ def _execute_full_cleanup(items_to_clean: list, dry_run: bool) -> None:
|
|
|
1127
1136
|
click.echo(click.style(f" ❌ Błąd: {e}", fg="red"))
|
|
1128
1137
|
results['failed'] += 1
|
|
1129
1138
|
|
|
1130
|
-
click.echo("\n
|
|
1139
|
+
click.echo(f"\n{click.style('='*CONSTANT_60, fg='cyan')}")
|
|
1131
1140
|
click.echo(click.style("📊 PODSUMOWANIE", fg="cyan", bold=True))
|
|
1132
|
-
click.echo(click.style("=
|
|
1141
|
+
click.echo(click.style(f"{'='*CONSTANT_60}", fg="cyan"))
|
|
1133
1142
|
click.echo(f" ✅ Sukces: {results['success']}")
|
|
1134
1143
|
click.echo(f" ❌ Błędy: {results['failed']}")
|
|
1135
1144
|
if dry_run:
|
|
@@ -88,6 +88,24 @@ def is_dangerous(cmd: str) -> Optional[str]:
|
|
|
88
88
|
return None
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def is_interactive_blocker(cmd: str) -> Optional[str]:
|
|
92
|
+
"""Returns reason string if command is likely to hang in non-interactive session."""
|
|
93
|
+
import re
|
|
94
|
+
patterns = [
|
|
95
|
+
(r"\bnewgrp\b", "newgrp replaces the shell and waits for input"),
|
|
96
|
+
(r"\bsu\s+-(\s+|$)", "su - starts a new login shell"),
|
|
97
|
+
(r"\bexec\s+bash\b", "exec replaces the process"),
|
|
98
|
+
(r"\btop\b(?!.*\b-b\b)", "top is interactive unless run in batch mode (-b)"),
|
|
99
|
+
(r"\bvim?\b", "editors require terminal interaction"),
|
|
100
|
+
(r"\bnano\b", "editors require terminal interaction"),
|
|
101
|
+
(r"\bless\b", "pagers require terminal interaction"),
|
|
102
|
+
]
|
|
103
|
+
for pat, reason in patterns:
|
|
104
|
+
if re.search(pat, cmd, re.IGNORECASE):
|
|
105
|
+
return reason
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
91
109
|
def run_command(
|
|
92
110
|
cmd: str,
|
|
93
111
|
timeout: int = 120,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fixos
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.22
|
|
4
4
|
Summary: AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues
|
|
5
5
|
Home-page: https://github.com/wronai/fixos
|
|
6
6
|
Author: fixos contributors
|
|
@@ -63,11 +63,11 @@ AI-powered OS Diagnostics
|
|
|
63
63
|
|
|
64
64
|
## AI Cost Tracking
|
|
65
65
|
|
|
66
|
-
     
|
|
67
|
+
  
|
|
68
68
|
|
|
69
|
-
- 🤖 **LLM usage:** $7.5000 (
|
|
70
|
-
- 👤 **Human dev:** ~$
|
|
69
|
+
- 🤖 **LLM usage:** $7.5000 (121 commits)
|
|
70
|
+
- 👤 **Human dev:** ~$2280 (22.8h @ $100/h, 30min dedup)
|
|
71
71
|
|
|
72
72
|
Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
73
73
|
|
|
@@ -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.
|
|
8
|
+
version="2.2.21",
|
|
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",
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|