fixos 2.2.24__tar.gz → 2.2.26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. {fixos-2.2.24 → fixos-2.2.26}/CHANGELOG.md +29 -0
  2. {fixos-2.2.24 → fixos-2.2.26}/PKG-INFO +5 -5
  3. {fixos-2.2.24 → fixos-2.2.26}/README.md +4 -4
  4. {fixos-2.2.24 → fixos-2.2.26}/fixos/__init__.py +1 -1
  5. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/autonomous_session.py +3 -2
  6. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/hitl_session.py +3 -2
  7. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/session_handlers.py +2 -1
  8. {fixos-2.2.24 → fixos-2.2.26}/fixos/platform_utils.py +1 -1
  9. fixos-2.2.26/fixos/utils/__init__.py +3 -0
  10. {fixos-2.2.24 → fixos-2.2.26}/fixos/utils/anonymizer.py +27 -0
  11. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/PKG-INFO +5 -5
  12. {fixos-2.2.24 → fixos-2.2.26}/pyproject.toml +1 -1
  13. {fixos-2.2.24 → fixos-2.2.26}/setup.py +1 -1
  14. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_anonymization_layers.py +110 -0
  15. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_core.py +85 -0
  16. fixos-2.2.24/fixos/utils/__init__.py +0 -3
  17. {fixos-2.2.24 → fixos-2.2.26}/.env.example +0 -0
  18. {fixos-2.2.24 → fixos-2.2.26}/LICENSE +0 -0
  19. {fixos-2.2.24 → fixos-2.2.26}/MANIFEST.in +0 -0
  20. {fixos-2.2.24 → fixos-2.2.26}/docker/README.md +0 -0
  21. {fixos-2.2.24 → fixos-2.2.26}/docker/TEST_RESULTS.md +0 -0
  22. {fixos-2.2.24 → fixos-2.2.26}/docker/TEST_RESULTS_V2.md +0 -0
  23. {fixos-2.2.24 → fixos-2.2.26}/docker/alpine/Dockerfile +0 -0
  24. {fixos-2.2.24 → fixos-2.2.26}/docker/arch/Dockerfile +0 -0
  25. {fixos-2.2.24 → fixos-2.2.26}/docker/base/Dockerfile +0 -0
  26. {fixos-2.2.24 → fixos-2.2.26}/docker/broken-audio/Dockerfile +0 -0
  27. {fixos-2.2.24 → fixos-2.2.26}/docker/broken-full/Dockerfile +0 -0
  28. {fixos-2.2.24 → fixos-2.2.26}/docker/broken-network/Dockerfile +0 -0
  29. {fixos-2.2.24 → fixos-2.2.26}/docker/broken-thumbnails/Dockerfile +0 -0
  30. {fixos-2.2.24 → fixos-2.2.26}/docker/debian/Dockerfile +0 -0
  31. {fixos-2.2.24 → fixos-2.2.26}/docker/docker-compose.multi-system.yml +0 -0
  32. {fixos-2.2.24 → fixos-2.2.26}/docker/docker-compose.yml +0 -0
  33. {fixos-2.2.24 → fixos-2.2.26}/docker/fedora/Dockerfile +0 -0
  34. {fixos-2.2.24 → fixos-2.2.26}/docker/test-multi-system.sh +0 -0
  35. {fixos-2.2.24 → fixos-2.2.26}/docker/ubuntu/Dockerfile +0 -0
  36. {fixos-2.2.24 → fixos-2.2.26}/docs/examples/advanced_usage.py +0 -0
  37. {fixos-2.2.24 → fixos-2.2.26}/docs/examples/quickstart.py +0 -0
  38. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/__init__.py +0 -0
  39. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/autonomous.py +0 -0
  40. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/hitl.py +0 -0
  41. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/session_core.py +0 -0
  42. {fixos-2.2.24 → fixos-2.2.26}/fixos/agent/session_io.py +0 -0
  43. {fixos-2.2.24 → fixos-2.2.26}/fixos/anonymizer.py +0 -0
  44. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/__init__.py +0 -0
  45. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/ask_cmd.py +0 -0
  46. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/cleanup_cmd.py +0 -0
  47. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/config_cmd.py +0 -0
  48. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/features_cmd.py +0 -0
  49. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/fix_cmd.py +0 -0
  50. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/history_cmd.py +0 -0
  51. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/main.py +0 -0
  52. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/orchestrate_cmd.py +0 -0
  53. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/profile_cmd.py +0 -0
  54. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/provider_cmd.py +0 -0
  55. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/quickfix_cmd.py +0 -0
  56. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/report_cmd.py +0 -0
  57. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/rollback_cmd.py +0 -0
  58. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/scan_cmd.py +0 -0
  59. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/shared.py +0 -0
  60. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/token_cmd.py +0 -0
  61. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli/watch_cmd.py +0 -0
  62. {fixos-2.2.24 → fixos-2.2.26}/fixos/cli.py +0 -0
  63. {fixos-2.2.24 → fixos-2.2.26}/fixos/config.py +0 -0
  64. {fixos-2.2.24 → fixos-2.2.26}/fixos/config_interactive.py +0 -0
  65. {fixos-2.2.24 → fixos-2.2.26}/fixos/constants.py +0 -0
  66. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/__init__.py +0 -0
  67. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/__init__.py +0 -0
  68. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/_shared.py +0 -0
  69. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/audio.py +0 -0
  70. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/hardware.py +0 -0
  71. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/resources.py +0 -0
  72. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/security.py +0 -0
  73. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/system_core.py +0 -0
  74. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/checks/thumbnails.py +0 -0
  75. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  76. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/disk_analyzer.py +0 -0
  77. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/flatpak_analyzer.py +0 -0
  78. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/service_cleanup.py +0 -0
  79. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/service_details.py +0 -0
  80. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/service_scanner.py +0 -0
  81. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/storage_analyzer.py +0 -0
  82. {fixos-2.2.24 → fixos-2.2.26}/fixos/diagnostics/system_checks.py +0 -0
  83. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/__init__.py +0 -0
  84. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/auditor.py +0 -0
  85. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/catalog.py +0 -0
  86. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/installer.py +0 -0
  87. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/profiles.py +0 -0
  88. {fixos-2.2.24 → fixos-2.2.26}/fixos/features/renderer.py +0 -0
  89. {fixos-2.2.24 → fixos-2.2.26}/fixos/fixes/__init__.py +0 -0
  90. {fixos-2.2.24 → fixos-2.2.26}/fixos/interactive/__init__.py +0 -0
  91. {fixos-2.2.24 → fixos-2.2.26}/fixos/interactive/cleanup_planner.py +0 -0
  92. {fixos-2.2.24 → fixos-2.2.26}/fixos/llm_shell.py +0 -0
  93. {fixos-2.2.24 → fixos-2.2.26}/fixos/orchestrator/__init__.py +0 -0
  94. {fixos-2.2.24 → fixos-2.2.26}/fixos/orchestrator/executor.py +0 -0
  95. {fixos-2.2.24 → fixos-2.2.26}/fixos/orchestrator/graph.py +0 -0
  96. {fixos-2.2.24 → fixos-2.2.26}/fixos/orchestrator/orchestrator.py +0 -0
  97. {fixos-2.2.24 → fixos-2.2.26}/fixos/orchestrator/rollback.py +0 -0
  98. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/__init__.py +0 -0
  99. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/base.py +0 -0
  100. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/__init__.py +0 -0
  101. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/audio.py +0 -0
  102. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/disk.py +0 -0
  103. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/hardware.py +0 -0
  104. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/resources.py +0 -0
  105. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/security.py +0 -0
  106. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/builtin/thumbnails.py +0 -0
  107. {fixos-2.2.24 → fixos-2.2.26}/fixos/plugins/registry.py +0 -0
  108. {fixos-2.2.24 → fixos-2.2.26}/fixos/profiles/__init__.py +0 -0
  109. {fixos-2.2.24 → fixos-2.2.26}/fixos/providers/__init__.py +0 -0
  110. {fixos-2.2.24 → fixos-2.2.26}/fixos/providers/llm.py +0 -0
  111. {fixos-2.2.24 → fixos-2.2.26}/fixos/providers/llm_analyzer.py +0 -0
  112. {fixos-2.2.24 → fixos-2.2.26}/fixos/providers/schemas.py +0 -0
  113. {fixos-2.2.24 → fixos-2.2.26}/fixos/system_checks.py +0 -0
  114. {fixos-2.2.24 → fixos-2.2.26}/fixos/utils/terminal.py +0 -0
  115. {fixos-2.2.24 → fixos-2.2.26}/fixos/utils/timeout.py +0 -0
  116. {fixos-2.2.24 → fixos-2.2.26}/fixos/utils/web_search.py +0 -0
  117. {fixos-2.2.24 → fixos-2.2.26}/fixos/watch.py +0 -0
  118. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/SOURCES.txt +0 -0
  119. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/dependency_links.txt +0 -0
  120. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/entry_points.txt +0 -0
  121. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/requires.txt +0 -0
  122. {fixos-2.2.24 → fixos-2.2.26}/fixos.egg-info/top_level.txt +0 -0
  123. {fixos-2.2.24 → fixos-2.2.26}/pytest.ini +0 -0
  124. {fixos-2.2.24 → fixos-2.2.26}/requirements-dev.txt +0 -0
  125. {fixos-2.2.24 → fixos-2.2.26}/requirements.txt +0 -0
  126. {fixos-2.2.24 → fixos-2.2.26}/scripts/pyqual-calibrate.py +0 -0
  127. {fixos-2.2.24 → fixos-2.2.26}/setup.cfg +0 -0
  128. {fixos-2.2.24 → fixos-2.2.26}/tests/__init__.py +0 -0
  129. {fixos-2.2.24 → fixos-2.2.26}/tests/conftest.py +0 -0
  130. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/__init__.py +0 -0
  131. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_audio_broken.py +0 -0
  132. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_cli.py +0 -0
  133. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_executor.py +0 -0
  134. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_multi_system.py +0 -0
  135. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_network_broken.py +0 -0
  136. {fixos-2.2.24 → fixos-2.2.26}/tests/e2e/test_thumbnails_broken.py +0 -0
  137. {fixos-2.2.24 → fixos-2.2.26}/tests/test_fixos.py +0 -0
  138. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/__init__.py +0 -0
  139. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_anonymizer.py +0 -0
  140. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_executor.py +0 -0
  141. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_orchestrator.py +0 -0
  142. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_service_cleanup.py +0 -0
  143. {fixos-2.2.24 → fixos-2.2.26}/tests/unit/test_service_scanner.py +0 -0
@@ -150,6 +150,35 @@ 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.26] - 2026-05-04
154
+
155
+ ### Docs
156
+ - Update README.md
157
+
158
+ ### Test
159
+ - Update tests/e2e/test_anonymization_layers.py
160
+
161
+ ### Other
162
+ - Update uv.lock
163
+
164
+ ## [2.2.25] - 2026-05-04
165
+
166
+ ### Docs
167
+ - Update README.md
168
+ - Update REFACTORING_PROGRESS.md
169
+
170
+ ### Test
171
+ - Update tests/unit/test_core.py
172
+
173
+ ### Other
174
+ - Update fixos/agent/autonomous_session.py
175
+ - Update fixos/agent/hitl_session.py
176
+ - Update fixos/agent/session_handlers.py
177
+ - Update fixos/platform_utils.py
178
+ - Update fixos/utils/__init__.py
179
+ - Update fixos/utils/anonymizer.py
180
+ - Update uv.lock
181
+
153
182
  ## [2.2.24] - 2026-05-04
154
183
 
155
184
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.24
3
+ Version: 2.2.26
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.24-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-25.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (123 commits)
70
- - 👤 **Human dev:** ~$2539 (25.4h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (125 commits)
70
+ - 👤 **Human dev:** ~$2603 (26.0h @ $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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.24-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-25.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
22
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
23
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $7.5000 (123 commits)
26
- - 👤 **Human dev:** ~$2539 (25.4h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $7.5000 (125 commits)
26
+ - 👤 **Human dev:** ~$2603 (26.0h @ $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.24"
2
+ __version__ = "2.2.26"
@@ -11,7 +11,7 @@ from dataclasses import dataclass, field
11
11
  from typing import Optional, List, Dict, Any
12
12
 
13
13
  from ..providers.llm import LLMClient, LLMError
14
- from ..utils.anonymizer import anonymize, display_anonymized_preview
14
+ from ..utils.anonymizer import anonymize, deanonymize, display_anonymized_preview
15
15
  from ..utils.web_search import search_all, format_results_for_llm
16
16
  from ..config import FixOsConfig
17
17
  from ..utils.timeout import SessionTimeout
@@ -297,7 +297,8 @@ class AutonomousSession:
297
297
  })
298
298
  return False
299
299
 
300
- cmd = self._add_sudo(cmd_raw)
300
+ cmd = deanonymize(cmd_raw)
301
+ cmd = self._add_sudo(cmd)
301
302
  print(f" ▶️ Wykonuję: {cmd}")
302
303
 
303
304
  ok, out = self._execute_command(cmd)
@@ -7,7 +7,7 @@ import time
7
7
  from typing import Dict, Any, List, Tuple
8
8
 
9
9
  from ..providers.llm import LLMClient, LLMError
10
- from ..utils.anonymizer import anonymize, display_anonymized_preview
10
+ from ..utils.anonymizer import anonymize, deanonymize, display_anonymized_preview
11
11
  from ..utils.web_search import search_all, format_results_for_llm
12
12
  from ..config import FixOsConfig
13
13
  from ..constants import (
@@ -150,7 +150,8 @@ class HITLSession:
150
150
  io.clear_thinking()
151
151
 
152
152
  io.print_llm_reply(reply)
153
- self.last_fixes = extract_fixes(reply)
153
+ raw_fixes = extract_fixes(reply)
154
+ self.last_fixes = [(deanonymize(cmd), comment) for cmd, comment in raw_fixes]
154
155
 
155
156
  if self._check_low_confidence(reply):
156
157
  return True
@@ -18,7 +18,7 @@ from ..constants import (
18
18
  from ..platform_utils import (
19
19
  is_dangerous, is_interactive_blocker, elevate_cmd, run_command,
20
20
  )
21
- from ..utils.anonymizer import anonymize
21
+ from ..utils.anonymizer import anonymize, deanonymize
22
22
  from ..utils.web_search import search_all, format_results_for_llm
23
23
  from . import session_io as io
24
24
  from .session_core import CmdResult, extract_fixes
@@ -223,6 +223,7 @@ def handle_free_text(user_in: str, messages: list) -> bool:
223
223
 
224
224
  def run_single_command(cmd: str, comment: str) -> CmdResult:
225
225
  """Run a command with full transparency and safety checks."""
226
+ cmd = deanonymize(cmd)
226
227
  cmd = elevate_cmd(cmd)
227
228
 
228
229
  # Check for dangerous commands
@@ -95,7 +95,7 @@ def is_interactive_blocker(cmd: str) -> Optional[str]:
95
95
  (r"\bnewgrp\b", "newgrp replaces the shell and waits for input"),
96
96
  (r"\bsu\s+-(\s+|$)", "su - starts a new login shell"),
97
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)"),
98
+ (r"\btop\b(?!.*\s-b(?:\s|$))", "top is interactive unless run in batch mode (-b)"),
99
99
  (r"\bvim?\b", "editors require terminal interaction"),
100
100
  (r"\bnano\b", "editors require terminal interaction"),
101
101
  (r"\bless\b", "pagers require terminal interaction"),
@@ -0,0 +1,3 @@
1
+ from .anonymizer import anonymize, deanonymize, display_anonymized_preview, AnonymizationReport
2
+ from .web_search import search_all, format_results_for_llm
3
+ __all__ = ["anonymize", "deanonymize", "display_anonymized_preview", "AnonymizationReport", "search_all", "format_results_for_llm"]
@@ -116,6 +116,33 @@ def anonymize(data_str: str) -> tuple[str, AnonymizationReport]:
116
116
  return data_str, report
117
117
 
118
118
 
119
+ def deanonymize(text: str) -> str:
120
+ """
121
+ Reverses anonymization placeholders back to real values for execution.
122
+ Handles [USER], [HOSTNAME], and [HOME].
123
+ """
124
+ if not isinstance(text, str):
125
+ return text
126
+
127
+ sensitive = _get_sensitive()
128
+
129
+ # 1. Hostname
130
+ if sensitive.get("hostname"):
131
+ text = text.replace("[HOSTNAME]", sensitive["hostname"])
132
+
133
+ # 2. Home directory
134
+ if sensitive.get("home"):
135
+ text = text.replace("[HOME]", sensitive["home"])
136
+ # Some LLMs might use /home/[USER] literally
137
+ # We replace [USER] next, which covers /home/[USER]
138
+
139
+ # 3. Username
140
+ if sensitive.get("username"):
141
+ text = text.replace("[USER]", sensitive["username"])
142
+
143
+ return text
144
+
145
+
119
146
  def display_anonymized_preview(data_str: str, report: AnonymizationReport, max_lines: int = 80):
120
147
  """
121
148
  Wyświetla użytkownikowi zanonimizowane dane przed wysłaniem do LLM.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.24
3
+ Version: 2.2.26
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.24-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-25.4h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
66
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.26-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
67
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-26.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (123 commits)
70
- - 👤 **Human dev:** ~$2539 (25.4h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $7.5000 (125 commits)
70
+ - 👤 **Human dev:** ~$2603 (26.0h @ $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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixos"
7
- version = "2.2.24"
7
+ version = "2.2.26"
8
8
  description = "AI-powered Linux/Windows diagnostics and repair – audio, hardware, system issues"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -5,7 +5,7 @@ long_description = (Path(__file__).parent / "README.md").read_text(encoding="utf
5
5
 
6
6
  setup(
7
7
  name="fixos",
8
- version="2.2.23",
8
+ version="2.2.25",
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",
@@ -589,3 +589,113 @@ def _make_sensitive_string() -> str:
589
589
  "sk-abc123def456ghi789jkl012mno345pqr password=mysecretpass123 "
590
590
  "UUID=a1b2c3d4-e5f6-7890-abcd-ef1234567890"
591
591
  )
592
+
593
+ # ══════════════════════════════════════════════════════════
594
+ # WARSTWA 7: Deanonimizacja (LLM → wykonanie)
595
+ # ══════════════════════════════════════════════════════════
596
+
597
+ class TestDeanonymizationLayer:
598
+ """Testy odwracania anonimizacji dla komend z LLM."""
599
+
600
+ def test_deanonymize_base_function(self):
601
+ from fixos.utils.anonymizer import deanonymize
602
+
603
+ cmd = "ping [HOSTNAME]"
604
+ assert deanonymize(cmd) == f"ping {REAL_HOSTNAME}"
605
+
606
+ cmd = "ls /home/[USER]/.cache"
607
+ assert deanonymize(cmd) == f"ls /home/{REAL_USER}/.cache"
608
+
609
+ cmd = "chown [USER]:[USER] [HOME]/file"
610
+ res = deanonymize(cmd)
611
+ assert REAL_USER in res
612
+ assert REAL_HOME in res
613
+ assert "[USER]" not in res
614
+ assert "[HOME]" not in res
615
+
616
+ @patch("fixos.providers.llm.openai")
617
+ def test_hitl_deanonymize_extracted_fixes(self, mock_openai, mock_cfg):
618
+ from fixos.agent.hitl_session import HITLSession
619
+
620
+ def capture(**kwargs):
621
+ resp = MagicMock()
622
+ resp.choices[0].message.content = (
623
+ "**Komenda:** `rm -rf /home/[USER]/tmp`\n"
624
+ "**Co robi:** clean\n"
625
+ )
626
+ resp.usage.total_tokens = 10
627
+ return resp
628
+
629
+ mock_openai.OpenAI.return_value.chat.completions.create.side_effect = capture
630
+
631
+ session = HITLSession(diagnostics={}, config=mock_cfg, show_data=False)
632
+ # Mock _initialize_messages and inputs
633
+ session._initialize_messages = MagicMock(return_value=True)
634
+ session.web_search_count = 0
635
+
636
+ with patch("fixos.agent.session_io.get_user_input", return_value="q"), \
637
+ patch("fixos.agent.session_io.ask_execute_prompt", return_value="n"), \
638
+ patch("fixos.agent.session_handlers.run_single_command") as mock_run:
639
+
640
+ session.run()
641
+
642
+ assert len(session.last_fixes) == 1
643
+ cmd, comment = session.last_fixes[0]
644
+ assert cmd == f"rm -rf /home/{REAL_USER}/tmp"
645
+ assert "[USER]" not in cmd
646
+
647
+ @patch("fixos.providers.llm.openai")
648
+ @patch("fixos.agent.autonomous_session.subprocess.run")
649
+ def test_autonomous_deanonymize_exec(self, mock_subproc, mock_openai, mock_cfg):
650
+ from fixos.agent.autonomous import run_autonomous_session
651
+
652
+ def capture(**kwargs):
653
+ resp = MagicMock()
654
+ resp.choices[0].message.content = json.dumps({
655
+ "analysis": "test",
656
+ "severity": "low",
657
+ "action": "EXEC",
658
+ "command": "echo [HOSTNAME] [USER] [HOME]",
659
+ "reason": "test",
660
+ "next_step": "done",
661
+ })
662
+ resp.usage.total_tokens = 50
663
+ return resp
664
+
665
+ mock_openai.OpenAI.return_value.chat.completions.create.side_effect = capture
666
+
667
+ # Mock subprocess.run to avoid actual execution
668
+ mock_proc = MagicMock()
669
+ mock_proc.returncode = 0
670
+ mock_proc.stdout = "ok"
671
+ mock_proc.stderr = ""
672
+ mock_subproc.return_value = mock_proc
673
+
674
+ with patch("builtins.input", return_value="yes"):
675
+ report = run_autonomous_session(
676
+ diagnostics={},
677
+ config=mock_cfg,
678
+ show_data=False,
679
+ max_fixes=1,
680
+ )
681
+
682
+ assert len(report.fixes_applied) == 1
683
+ executed_cmd = report.fixes_applied[0].command
684
+ assert REAL_HOSTNAME in executed_cmd
685
+ assert REAL_USER in executed_cmd
686
+ assert "[HOSTNAME]" not in executed_cmd
687
+ assert "[USER]" not in executed_cmd
688
+
689
+ @patch("fixos.agent.session_handlers.run_command")
690
+ def test_run_single_command_deanonymizes(self, mock_run):
691
+ from fixos.agent.session_handlers import run_single_command
692
+
693
+ mock_run.return_value = (True, "ok", "", 0)
694
+
695
+ with patch("fixos.agent.session_io.ask_execute_prompt", return_value="y"):
696
+ run_single_command("ls /home/[USER]", "test comment")
697
+
698
+ # run_command is called with the deanonymized command
699
+ args, kwargs = mock_run.call_args
700
+ assert args[0] == f"ls /home/{REAL_USER}"
701
+ assert "[USER]" not in args[0]
@@ -122,3 +122,88 @@ class TestWebSearch:
122
122
  from fixos.utils.web_search import _http_get
123
123
  result = _http_get("http://240.0.0.1/nonexistent", timeout=1)
124
124
  assert result is None
125
+
126
+
127
+ class TestSortFixesByPriority:
128
+ def test_cleanup_before_upgrade(self):
129
+ from fixos.agent.session_handlers import _sort_fixes_by_priority
130
+ fixes = [
131
+ ("sudo dnf upgrade -y", "upgrade"),
132
+ ("sudo journalctl --vacuum-size=200M", "clean logs"),
133
+ ]
134
+ result = _sort_fixes_by_priority(fixes)
135
+ assert result[0][0] == "sudo journalctl --vacuum-size=200M"
136
+ assert result[1][0] == "sudo dnf upgrade -y"
137
+
138
+ def test_disk_hungry_sorted_to_end(self):
139
+ from fixos.agent.session_handlers import _sort_fixes_by_priority
140
+ fixes = [
141
+ ("sudo dnf upgrade -y", "upgrade"),
142
+ ("sudo apt full-upgrade -y", "upgrade"),
143
+ ("sudo dnf remove oldkernel", "remove"),
144
+ ("sudo rm -rf /var/cache", "clean cache"),
145
+ ]
146
+ result = _sort_fixes_by_priority(fixes)
147
+ # Both remove and rm are cleanup (score 0), upgrades are score 2.
148
+ # Stable sort preserves original order among equal scores.
149
+ assert result[0][0] == "sudo dnf remove oldkernel"
150
+ assert result[1][0] == "sudo rm -rf /var/cache"
151
+ assert result[2][0] == "sudo dnf upgrade -y"
152
+ assert result[3][0] == "sudo apt full-upgrade -y"
153
+
154
+ def test_unknown_commands_mid_priority(self):
155
+ from fixos.agent.session_handlers import _sort_fixes_by_priority
156
+ fixes = [
157
+ ("sudo dnf upgrade -y", "upgrade"),
158
+ ("echo 'restart service'", "info"),
159
+ ]
160
+ result = _sort_fixes_by_priority(fixes)
161
+ assert result[0][0] == "echo 'restart service'"
162
+ assert result[1][0] == "sudo dnf upgrade -y"
163
+
164
+
165
+ class TestDiagnosticOnlyCommand:
166
+ def test_simple_diagnostic_is_filtered(self):
167
+ from fixos.agent.session_core import _is_diagnostic_only_command
168
+ assert _is_diagnostic_only_command("df -h") is True
169
+ assert _is_diagnostic_only_command("free -h") is True
170
+ assert _is_diagnostic_only_command("systemctl status auditd") is True
171
+
172
+ def test_repair_command_is_not_diagnostic(self):
173
+ from fixos.agent.session_core import _is_diagnostic_only_command
174
+ assert _is_diagnostic_only_command("sudo systemctl restart auditd") is False
175
+ assert _is_diagnostic_only_command("dnf upgrade -y") is False
176
+ assert _is_diagnostic_only_command("rm -rf /var/cache") is False
177
+
178
+ def test_journalctl_vacuum_is_not_diagnostic(self):
179
+ from fixos.agent.session_core import _is_diagnostic_only_command
180
+ assert _is_diagnostic_only_command("journalctl --vacuum-size=200M") is False
181
+ assert _is_diagnostic_only_command("journalctl --flush") is False
182
+
183
+ def test_compound_command_any_repair_keeps_all(self):
184
+ from fixos.agent.session_core import _is_diagnostic_only_command
185
+ # Compound with both diagnostic and repair → not filtered
186
+ assert _is_diagnostic_only_command("df -h && sudo dnf remove kernel") is False
187
+ assert _is_diagnostic_only_command("cat /etc/fstab || systemctl restart auditd") is False
188
+
189
+ def test_compound_all_diagnostic_is_filtered(self):
190
+ from fixos.agent.session_core import _is_diagnostic_only_command
191
+ assert _is_diagnostic_only_command("df -h && free -h") is True
192
+
193
+
194
+ class TestInteractiveBlocker:
195
+ def test_newgrp_blocked(self):
196
+ from fixos.platform_utils import is_interactive_blocker
197
+ assert is_interactive_blocker("sudo usermod -aG video $USER && newgrp video") is not None
198
+
199
+ def test_su_dash_blocked(self):
200
+ from fixos.platform_utils import is_interactive_blocker
201
+ assert is_interactive_blocker("su - tom") is not None
202
+
203
+ def test_top_not_blocked_if_batch(self):
204
+ from fixos.platform_utils import is_interactive_blocker
205
+ assert is_interactive_blocker("top -b -n1") is None
206
+
207
+ def test_regular_command_not_blocked(self):
208
+ from fixos.platform_utils import is_interactive_blocker
209
+ assert is_interactive_blocker("dnf upgrade -y") is None
@@ -1,3 +0,0 @@
1
- from .anonymizer import anonymize, display_anonymized_preview, AnonymizationReport
2
- from .web_search import search_all, format_results_for_llm
3
- __all__ = ["anonymize", "display_anonymized_preview", "AnonymizationReport", "search_all", "format_results_for_llm"]
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