fixos 2.2.28__tar.gz → 2.2.30__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 (174) hide show
  1. {fixos-2.2.28 → fixos-2.2.30}/CHANGELOG.md +35 -0
  2. {fixos-2.2.28 → fixos-2.2.30}/PKG-INFO +7 -7
  3. {fixos-2.2.28 → fixos-2.2.30}/README.md +5 -5
  4. {fixos-2.2.28 → fixos-2.2.30}/docker/validate-scenario.py +16 -5
  5. {fixos-2.2.28 → fixos-2.2.30}/docs/examples/advanced_usage.py +0 -3
  6. fixos-2.2.30/docs/examples/quickstart.py +5 -0
  7. {fixos-2.2.28 → fixos-2.2.30}/fixos/__init__.py +2 -1
  8. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/__init__.py +2 -1
  9. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/autonomous_session.py +80 -48
  10. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/hitl.py +1 -1
  11. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/hitl_session.py +51 -26
  12. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/session_core.py +99 -38
  13. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/session_handlers.py +115 -94
  14. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/session_io.py +66 -29
  15. {fixos-2.2.28 → fixos-2.2.30}/fixos/anonymizer.py +19 -25
  16. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/__init__.py +1 -0
  17. fixos-2.2.30/fixos/cli/_cleanup_flatpak.py +288 -0
  18. fixos-2.2.30/fixos/cli/_cleanup_home.py +181 -0
  19. fixos-2.2.30/fixos/cli/_cleanup_snap.py +170 -0
  20. fixos-2.2.30/fixos/cli/_cleanup_system.py +603 -0
  21. fixos-2.2.30/fixos/cli/_cleanup_utils.py +115 -0
  22. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/ask_cmd.py +89 -68
  23. fixos-2.2.30/fixos/cli/cleanup_cmd.py +307 -0
  24. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/config_cmd.py +93 -39
  25. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/features_cmd.py +9 -4
  26. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/fix_cmd.py +152 -58
  27. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/history_cmd.py +6 -3
  28. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/main.py +57 -29
  29. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/orchestrate_cmd.py +55 -24
  30. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/output_formatter.py +1 -0
  31. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/profile_cmd.py +1 -1
  32. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/provider_cmd.py +30 -9
  33. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/quickfix_cmd.py +13 -7
  34. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/report_cmd.py +19 -5
  35. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/rollback_cmd.py +11 -4
  36. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/scan_cmd.py +119 -32
  37. fixos-2.2.30/fixos/cli/shared.py +105 -0
  38. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/token_cmd.py +25 -8
  39. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli/watch_cmd.py +27 -9
  40. {fixos-2.2.28 → fixos-2.2.30}/fixos/cli.py +17 -5
  41. {fixos-2.2.28 → fixos-2.2.30}/fixos/config.py +19 -19
  42. {fixos-2.2.28 → fixos-2.2.30}/fixos/config_interactive.py +10 -6
  43. {fixos-2.2.28 → fixos-2.2.30}/fixos/constants.py +13 -0
  44. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/__init__.py +1 -0
  45. fixos-2.2.30/fixos/diagnostics/_flatpak_analysis_mixin.py +319 -0
  46. fixos-2.2.30/fixos/diagnostics/_flatpak_execution_mixin.py +240 -0
  47. fixos-2.2.30/fixos/diagnostics/_flatpak_recommendations_mixin.py +299 -0
  48. fixos-2.2.30/fixos/diagnostics/_storage_container_mixin.py +176 -0
  49. fixos-2.2.30/fixos/diagnostics/_storage_system_mixin.py +286 -0
  50. fixos-2.2.30/fixos/diagnostics/_storage_user_mixin.py +395 -0
  51. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/checks/__init__.py +7 -1
  52. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/checks/_shared.py +6 -2
  53. fixos-2.2.30/fixos/diagnostics/checks/audio.py +77 -0
  54. fixos-2.2.30/fixos/diagnostics/checks/file_analysis.py +320 -0
  55. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/checks/hardware.py +21 -12
  56. fixos-2.2.30/fixos/diagnostics/checks/packages.py +203 -0
  57. fixos-2.2.30/fixos/diagnostics/checks/resources.py +163 -0
  58. fixos-2.2.30/fixos/diagnostics/checks/security.py +142 -0
  59. fixos-2.2.30/fixos/diagnostics/checks/storage_optimization.py +201 -0
  60. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/checks/system_core.py +43 -13
  61. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/checks/thumbnails.py +33 -17
  62. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/dev_project_analyzer.py +114 -79
  63. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/disk_analyzer.py +232 -166
  64. fixos-2.2.30/fixos/diagnostics/flatpak_analyzer.py +166 -0
  65. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/service_cleanup.py +102 -73
  66. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/service_details.py +43 -24
  67. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/service_scanner.py +26 -9
  68. fixos-2.2.30/fixos/diagnostics/storage_analyzer.py +287 -0
  69. {fixos-2.2.28 → fixos-2.2.30}/fixos/diagnostics/system_checks.py +10 -1
  70. fixos-2.2.30/fixos/diagnostics/utils.py +12 -0
  71. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/__init__.py +21 -8
  72. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/auditor.py +2 -1
  73. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/catalog.py +20 -11
  74. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/installer.py +6 -8
  75. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/profiles.py +15 -10
  76. {fixos-2.2.28 → fixos-2.2.30}/fixos/features/renderer.py +41 -23
  77. fixos-2.2.30/fixos/fixes/__init__.py +2 -0
  78. {fixos-2.2.28 → fixos-2.2.30}/fixos/interactive/cleanup_planner.py +160 -104
  79. {fixos-2.2.28 → fixos-2.2.30}/fixos/llm_shell.py +48 -24
  80. fixos-2.2.30/fixos/orchestrator/__init__.py +18 -0
  81. {fixos-2.2.28 → fixos-2.2.30}/fixos/orchestrator/executor.py +28 -7
  82. {fixos-2.2.28 → fixos-2.2.30}/fixos/orchestrator/graph.py +10 -8
  83. {fixos-2.2.28 → fixos-2.2.30}/fixos/orchestrator/orchestrator.py +71 -35
  84. {fixos-2.2.28 → fixos-2.2.30}/fixos/orchestrator/rollback.py +49 -36
  85. {fixos-2.2.28 → fixos-2.2.30}/fixos/platform_utils.py +62 -13
  86. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/__init__.py +1 -0
  87. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/base.py +4 -0
  88. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/audio.py +42 -32
  89. fixos-2.2.30/fixos/plugins/builtin/disk.py +133 -0
  90. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/hardware.py +41 -28
  91. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/resources.py +42 -31
  92. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/security.py +87 -59
  93. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/thumbnails.py +49 -33
  94. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/registry.py +20 -10
  95. {fixos-2.2.28 → fixos-2.2.30}/fixos/profiles/__init__.py +1 -0
  96. {fixos-2.2.28 → fixos-2.2.30}/fixos/providers/__init__.py +1 -0
  97. {fixos-2.2.28 → fixos-2.2.30}/fixos/providers/llm.py +24 -14
  98. {fixos-2.2.28 → fixos-2.2.30}/fixos/providers/llm_analyzer.py +93 -68
  99. {fixos-2.2.28 → fixos-2.2.30}/fixos/providers/schemas.py +12 -3
  100. fixos-2.2.30/fixos/system_checks.py +168 -0
  101. fixos-2.2.30/fixos/utils/__init__.py +16 -0
  102. {fixos-2.2.28 → fixos-2.2.30}/fixos/utils/anonymizer.py +75 -34
  103. {fixos-2.2.28 → fixos-2.2.30}/fixos/utils/terminal.py +54 -38
  104. {fixos-2.2.28 → fixos-2.2.30}/fixos/utils/timeout.py +1 -0
  105. {fixos-2.2.28 → fixos-2.2.30}/fixos/utils/web_search.py +68 -46
  106. {fixos-2.2.28 → fixos-2.2.30}/fixos/watch.py +9 -4
  107. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/PKG-INFO +7 -7
  108. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/SOURCES.txt +16 -0
  109. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/top_level.txt +1 -0
  110. {fixos-2.2.28 → fixos-2.2.30}/pyproject.toml +2 -4
  111. {fixos-2.2.28 → fixos-2.2.30}/pytest.ini +1 -1
  112. {fixos-2.2.28 → fixos-2.2.30}/scripts/pyqual-calibrate.py +68 -77
  113. {fixos-2.2.28 → fixos-2.2.30}/setup.py +1 -1
  114. {fixos-2.2.28 → fixos-2.2.30}/tests/conftest.py +8 -4
  115. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_anonymization_layers.py +101 -77
  116. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_audio_broken.py +23 -11
  117. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_cli.py +127 -50
  118. fixos-2.2.30/tests/e2e/test_diagnostics_integration.py +151 -0
  119. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_executor.py +27 -11
  120. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_multi_system.py +34 -34
  121. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_network_broken.py +18 -4
  122. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/test_thumbnails_broken.py +24 -7
  123. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_anonymizer.py +1 -1
  124. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_core.py +94 -5
  125. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_orchestrator.py +191 -37
  126. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_service_cleanup.py +6 -2
  127. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_service_scanner.py +6 -2
  128. fixos-2.2.28/docs/examples/quickstart.py +0 -14
  129. fixos-2.2.28/fixos/cli/cleanup_cmd.py +0 -1228
  130. fixos-2.2.28/fixos/cli/shared.py +0 -62
  131. fixos-2.2.28/fixos/diagnostics/checks/audio.py +0 -61
  132. fixos-2.2.28/fixos/diagnostics/checks/resources.py +0 -99
  133. fixos-2.2.28/fixos/diagnostics/checks/security.py +0 -92
  134. fixos-2.2.28/fixos/diagnostics/flatpak_analyzer.py +0 -937
  135. fixos-2.2.28/fixos/diagnostics/storage_analyzer.py +0 -1091
  136. fixos-2.2.28/fixos/fixes/__init__.py +0 -4
  137. fixos-2.2.28/fixos/orchestrator/__init__.py +0 -9
  138. fixos-2.2.28/fixos/plugins/builtin/disk.py +0 -113
  139. fixos-2.2.28/fixos/system_checks.py +0 -156
  140. fixos-2.2.28/fixos/utils/__init__.py +0 -3
  141. {fixos-2.2.28 → fixos-2.2.30}/.env.example +0 -0
  142. {fixos-2.2.28 → fixos-2.2.30}/LICENSE +0 -0
  143. {fixos-2.2.28 → fixos-2.2.30}/MANIFEST.in +0 -0
  144. {fixos-2.2.28 → fixos-2.2.30}/docker/README.md +0 -0
  145. {fixos-2.2.28 → fixos-2.2.30}/docker/TEST_RESULTS.md +0 -0
  146. {fixos-2.2.28 → fixos-2.2.30}/docker/TEST_RESULTS_V2.md +0 -0
  147. {fixos-2.2.28 → fixos-2.2.30}/docker/alpine/Dockerfile +0 -0
  148. {fixos-2.2.28 → fixos-2.2.30}/docker/arch/Dockerfile +0 -0
  149. {fixos-2.2.28 → fixos-2.2.30}/docker/base/Dockerfile +0 -0
  150. {fixos-2.2.28 → fixos-2.2.30}/docker/broken-audio/Dockerfile +0 -0
  151. {fixos-2.2.28 → fixos-2.2.30}/docker/broken-full/Dockerfile +0 -0
  152. {fixos-2.2.28 → fixos-2.2.30}/docker/broken-network/Dockerfile +0 -0
  153. {fixos-2.2.28 → fixos-2.2.30}/docker/broken-thumbnails/Dockerfile +0 -0
  154. {fixos-2.2.28 → fixos-2.2.30}/docker/debian/Dockerfile +0 -0
  155. {fixos-2.2.28 → fixos-2.2.30}/docker/docker-compose.multi-system.yml +0 -0
  156. {fixos-2.2.28 → fixos-2.2.30}/docker/docker-compose.yml +0 -0
  157. {fixos-2.2.28 → fixos-2.2.30}/docker/fedora/Dockerfile +0 -0
  158. {fixos-2.2.28 → fixos-2.2.30}/docker/test-multi-system.sh +0 -0
  159. {fixos-2.2.28 → fixos-2.2.30}/docker/test-scenarios.sh +0 -0
  160. {fixos-2.2.28 → fixos-2.2.30}/docker/ubuntu/Dockerfile +0 -0
  161. {fixos-2.2.28 → fixos-2.2.30}/fixos/agent/autonomous.py +0 -0
  162. {fixos-2.2.28 → fixos-2.2.30}/fixos/interactive/__init__.py +0 -0
  163. {fixos-2.2.28 → fixos-2.2.30}/fixos/plugins/builtin/__init__.py +0 -0
  164. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/dependency_links.txt +0 -0
  165. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/entry_points.txt +0 -0
  166. {fixos-2.2.28 → fixos-2.2.30}/fixos.egg-info/requires.txt +0 -0
  167. {fixos-2.2.28 → fixos-2.2.30}/requirements-dev.txt +0 -0
  168. {fixos-2.2.28 → fixos-2.2.30}/requirements.txt +0 -0
  169. {fixos-2.2.28 → fixos-2.2.30}/setup.cfg +0 -0
  170. {fixos-2.2.28 → fixos-2.2.30}/tests/__init__.py +0 -0
  171. {fixos-2.2.28 → fixos-2.2.30}/tests/e2e/__init__.py +0 -0
  172. {fixos-2.2.28 → fixos-2.2.30}/tests/test_fixos.py +0 -0
  173. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/__init__.py +0 -0
  174. {fixos-2.2.28 → fixos-2.2.30}/tests/unit/test_executor.py +0 -0
@@ -150,6 +150,41 @@ 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.30] - 2026-06-16
154
+
155
+ ### Docs
156
+ - Update README.md
157
+
158
+ ### Other
159
+ - Update nlp2uri.yaml
160
+ - Update uv.lock
161
+
162
+ ## [2.2.29] - 2026-05-08
163
+
164
+ ### Docs
165
+ - Update README.md
166
+ - Update SUMD.md
167
+ - Update SUMR.md
168
+ - Update project/README.md
169
+ - Update project/context.md
170
+
171
+ ### Test
172
+ - Update tests/e2e/test_diagnostics_integration.py
173
+ - Update tests/unit/test_core.py
174
+
175
+ ### Other
176
+ - Update app.doql.less
177
+ - Update fixos/agent/session_core.py
178
+ - Update fixos/cli/_cleanup_flatpak.py
179
+ - Update fixos/cli/_cleanup_home.py
180
+ - Update fixos/cli/_cleanup_snap.py
181
+ - Update fixos/cli/_cleanup_system.py
182
+ - Update fixos/cli/_cleanup_utils.py
183
+ - Update fixos/cli/cleanup_cmd.py
184
+ - Update fixos/cli/config_cmd.py
185
+ - Update fixos/cli/scan_cmd.py
186
+ - ... and 36 more files
187
+
153
188
  ## [2.2.28] - 2026-05-06
154
189
 
155
190
  ### Docs
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.28
3
+ Version: 2.2.30
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
7
7
  Author-email: Tom Sapletta <tom@sapletta.com>
8
- License: Apache-2.0
8
+ License-Expression: Apache-2.0
9
9
  Project-URL: Homepage, https://github.com/wronai/fixos
10
10
  Project-URL: Bug Tracker, https://github.com/wronai/fixos/issues
11
11
  Keywords: linux,windows,diagnostics,ai,llm,audio,system-repair,cross-platform
@@ -63,13 +63,13 @@ 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.28-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.5h-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.30-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-$3.66-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-30.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
68
68
 
69
- - 🤖 **LLM usage:** $7.5000 (127 commits)
70
- - 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
69
+ - 🤖 **LLM usage:** $3.6649 (131 commits)
70
+ - 👤 **Human dev:** ~$3047 (30.5h @ $100/h, 30min dedup)
71
71
 
72
- Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
72
+ Generated on 2026-06-16 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
73
73
 
74
74
  ---
75
75
 
@@ -19,13 +19,13 @@ AI-powered OS Diagnostics
19
19
 
20
20
  ## AI Cost Tracking
21
21
 
22
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-2.2.28-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.5h-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.30-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-$3.66-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-30.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
24
24
 
25
- - 🤖 **LLM usage:** $7.5000 (127 commits)
26
- - 👤 **Human dev:** ~$2647 (26.5h @ $100/h, 30min dedup)
25
+ - 🤖 **LLM usage:** $3.6649 (131 commits)
26
+ - 👤 **Human dev:** ~$3047 (30.5h @ $100/h, 30min dedup)
27
27
 
28
- Generated on 2026-05-06 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
28
+ Generated on 2026-06-16 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
29
29
 
30
30
  ---
31
31
 
@@ -85,7 +85,9 @@ def validate(data: dict, scenario: str) -> list[str]:
85
85
  """Validate data against scenario expectations. Returns list of failures."""
86
86
  expectations = SCENARIOS.get(scenario)
87
87
  if expectations is None:
88
- return [f"Nieznany scenariusz: '{scenario}'. Dostępne: {', '.join(SCENARIOS.keys())}"]
88
+ return [
89
+ f"Nieznany scenariusz: '{scenario}'. Dostępne: {', '.join(SCENARIOS.keys())}"
90
+ ]
89
91
 
90
92
  failures = []
91
93
  for exp in expectations:
@@ -101,14 +103,20 @@ def validate(data: dict, scenario: str) -> list[str]:
101
103
 
102
104
  if "equals" in exp:
103
105
  if value_str.strip() != str(exp["equals"]).strip():
104
- failures.append(f" ✗ {desc}: oczekiwano '{exp['equals']}', otrzymano '{value_str[:80]}'")
106
+ failures.append(
107
+ f" ✗ {desc}: oczekiwano '{exp['equals']}', otrzymano '{value_str[:80]}'"
108
+ )
105
109
 
106
110
  if "contains" in exp:
107
111
  if exp["contains"].lower() not in value_str.lower():
108
- failures.append(f" ✗ {desc}: brak '{exp['contains']}' w '{value_str[:80]}'")
112
+ failures.append(
113
+ f" ✗ {desc}: brak '{exp['contains']}' w '{value_str[:80]}'"
114
+ )
109
115
 
110
116
  if "contains_any" in exp:
111
- if not any(term.lower() in value_str.lower() for term in exp["contains_any"]):
117
+ if not any(
118
+ term.lower() in value_str.lower() for term in exp["contains_any"]
119
+ ):
112
120
  failures.append(
113
121
  f" ✗ {desc}: brak żadnego z {exp['contains_any']} w '{value_str[:80]}'"
114
122
  )
@@ -150,7 +158,10 @@ def main() -> None:
150
158
  sys.exit(1)
151
159
  else:
152
160
  expectations_count = len(SCENARIOS[scenario])
153
- print(f"✓ Scenariusz '{scenario}' — {expectations_count}/{expectations_count} OK", file=sys.stderr)
161
+ print(
162
+ f"✓ Scenariusz '{scenario}' — {expectations_count}/{expectations_count} OK",
163
+ file=sys.stderr,
164
+ )
154
165
  sys.exit(0)
155
166
 
156
167
 
@@ -4,6 +4,3 @@ Advanced usage — fixos
4
4
  Shows how to use individual generators, sync, and formatters.
5
5
  Run: python examples/advanced_usage.py
6
6
  """
7
-
8
- from pathlib import Path
9
-
@@ -0,0 +1,5 @@
1
+ from typing import Any
2
+
3
+
4
+ def run_autonomous_session(*args: Any, **kwargs: Any) -> Any:
5
+ pass
@@ -1,2 +1,3 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
- __version__ = "2.2.28"
2
+
3
+ __version__ = "2.2.30"
@@ -21,9 +21,10 @@ if TYPE_CHECKING:
21
21
 
22
22
  def get_remaining_time(session: "HITLSession | AutonomousSession") -> int:
23
23
  """Calculate remaining session time in seconds."""
24
- start_time = getattr(session, 'start_ts', None) or getattr(session, 'start_time')
24
+ start_time = getattr(session, "start_ts", None) or getattr(session, "start_time")
25
25
  return session.config.session_timeout - int(time.time() - start_time)
26
26
 
27
+
27
28
  __all__ = [
28
29
  "run_hitl_session",
29
30
  "HITLSession",
@@ -18,7 +18,6 @@ from ..utils.timeout import SessionTimeout
18
18
  from ..constants import (
19
19
  UI_BORDER_WIDTH,
20
20
  MAX_OUTPUT_PREVIEW_LENGTH,
21
- MAX_ANON_PREVIEW_LENGTH,
22
21
  DEFAULT_COMMAND_TIMEOUT,
23
22
  DEFAULT_TOKEN_LIMIT,
24
23
  MAX_COMMAND_LENGTH,
@@ -26,7 +25,6 @@ from ..constants import (
26
25
  )
27
26
 
28
27
 
29
-
30
28
  # Commands NEVER executed automatically
31
29
  FORBIDDEN_COMMANDS = [
32
30
  r"rm\s+-rf\s+/",
@@ -42,8 +40,16 @@ FORBIDDEN_COMMANDS = [
42
40
  ]
43
41
 
44
42
  SUDO_PREFIXES = [
45
- "dnf", "rpm", "systemctl", "firewall-cmd", "setenforce",
46
- "modprobe", "rmmod", "alsactl", "grub2-", "update-grub",
43
+ "dnf",
44
+ "rpm",
45
+ "systemctl",
46
+ "firewall-cmd",
47
+ "setenforce",
48
+ "modprobe",
49
+ "rmmod",
50
+ "alsactl",
51
+ "grub2-",
52
+ "update-grub",
47
53
  ]
48
54
 
49
55
  SYSTEM_PROMPT_AUTONOMOUS = """Jesteś autonomicznym agentem diagnostyki Linux, Windows, macOS.
@@ -130,8 +136,10 @@ class AutonomousSession:
130
136
 
131
137
  def _setup_timeout(self) -> None:
132
138
  """Setup session timeout handler."""
139
+
133
140
  def _timeout_handler(signum, frame) -> None:
134
141
  raise SessionTimeout()
142
+
135
143
  signal.signal(signal.SIGALRM, _timeout_handler)
136
144
  signal.alarm(self.config.session_timeout)
137
145
 
@@ -149,7 +157,9 @@ class AutonomousSession:
149
157
  print(f" Timeout sesji: {self.config.session_timeout}s")
150
158
  print(f" Model: {self.config.model}")
151
159
 
152
- confirm = input("\n Czy na pewno chcesz uruchomić tryb autonomiczny? (yes/N): ").strip()
160
+ confirm = input(
161
+ "\n Czy na pewno chcesz uruchomić tryb autonomiczny? (yes/N): "
162
+ ).strip()
153
163
  if confirm.lower() not in ("yes", "tak"):
154
164
  print(" Anulowano. Użyj --mode hitl dla trybu z potwierdzeniem.")
155
165
  return False
@@ -175,6 +185,7 @@ class AutonomousSession:
175
185
  def _get_remaining_time(self) -> int:
176
186
  """Get remaining session time in seconds."""
177
187
  from . import get_remaining_time
188
+
178
189
  return get_remaining_time(self)
179
190
 
180
191
  def _check_timeout(self) -> None:
@@ -185,7 +196,9 @@ class AutonomousSession:
185
196
  def _query_llm(self) -> Optional[str]:
186
197
  """Query LLM and return reply."""
187
198
  try:
188
- return self.llm.chat(self.messages, max_tokens=DEFAULT_TOKEN_LIMIT, temperature=0.1)
199
+ return self.llm.chat(
200
+ self.messages, max_tokens=DEFAULT_TOKEN_LIMIT, temperature=0.1
201
+ )
189
202
  except LLMError as e:
190
203
  print(f" ❌ LLM błąd: {e}")
191
204
  return None
@@ -195,10 +208,9 @@ class AutonomousSession:
195
208
  if self.config.enable_web_search and self.search_count < self.MAX_SEARCHES:
196
209
  results = search_all("fedora repair diagnostics", self.config.serpapi_key)
197
210
  if results:
198
- self.messages.append({
199
- "role": "user",
200
- "content": format_results_for_llm(results)
201
- })
211
+ self.messages.append(
212
+ {"role": "user", "content": format_results_for_llm(results)}
213
+ )
202
214
  self.search_count += 1
203
215
  return True
204
216
  return False
@@ -206,6 +218,7 @@ class AutonomousSession:
206
218
  def _parse_action(self, reply: str) -> Optional[Dict[str, Any]]:
207
219
  """Parse JSON action from LLM reply."""
208
220
  import json
221
+
209
222
  patterns = [
210
223
  r"```(?:json)?\s*(\{.*?\})\s*```",
211
224
  r"(\{[^{}]*\"action\"[^{}]*\})",
@@ -242,7 +255,11 @@ class AutonomousSession:
242
255
  """Execute command and return (success, output)."""
243
256
  try:
244
257
  proc = subprocess.run(
245
- cmd, shell=True, capture_output=True, text=True, timeout=DEFAULT_COMMAND_TIMEOUT
258
+ cmd,
259
+ shell=True,
260
+ capture_output=True,
261
+ text=True,
262
+ timeout=DEFAULT_COMMAND_TIMEOUT,
246
263
  )
247
264
  out = proc.stdout.strip() or proc.stderr.strip() or "(brak outputu)"
248
265
  return proc.returncode == 0, out[:MAX_OUTPUT_PREVIEW_LENGTH]
@@ -260,22 +277,28 @@ class AutonomousSession:
260
277
  self.report.searches_done.append(query)
261
278
  if results:
262
279
  web_ctx = format_results_for_llm(results)
263
- self.messages.append({
264
- "role": "user",
265
- "content": f"Wyniki dla '{query}':\n{web_ctx}\nKontynuuj naprawę."
266
- })
280
+ self.messages.append(
281
+ {
282
+ "role": "user",
283
+ "content": f"Wyniki dla '{query}':\n{web_ctx}\nKontynuuj naprawę.",
284
+ }
285
+ )
267
286
  else:
268
- self.messages.append({
269
- "role": "user",
270
- "content": f"Brak wyników dla '{query}'. Co innego możemy zrobić?"
271
- })
287
+ self.messages.append(
288
+ {
289
+ "role": "user",
290
+ "content": f"Brak wyników dla '{query}'. Co innego możemy zrobić?",
291
+ }
292
+ )
272
293
  return True
273
294
  else:
274
295
  print(" ⚠️ Limit wyszukiwań osiągnięty.")
275
- self.messages.append({
276
- "role": "user",
277
- "content": "Brak więcej wyszukiwań. Co możemy zrobić bez zewnętrznych źródeł?"
278
- })
296
+ self.messages.append(
297
+ {
298
+ "role": "user",
299
+ "content": "Brak więcej wyszukiwań. Co możemy zrobić bez zewnętrznych źródeł?",
300
+ }
301
+ )
279
302
  return False
280
303
 
281
304
  def _handle_exec(self, action_data: Dict[str, Any]) -> bool:
@@ -284,17 +307,21 @@ class AutonomousSession:
284
307
  reason = action_data.get("reason", "")
285
308
 
286
309
  if not cmd_raw:
287
- self.messages.append({"role": "user", "content": "Brak komendy. Podaj konkretną komendę."})
310
+ self.messages.append(
311
+ {"role": "user", "content": "Brak komendy. Podaj konkretną komendę."}
312
+ )
288
313
  return False
289
314
 
290
315
  # Security check
291
316
  danger = self._is_forbidden(cmd_raw)
292
317
  if danger:
293
318
  print(f" 🚫 ZABLOKOWANO: {danger}")
294
- self.messages.append({
295
- "role": "user",
296
- "content": f"Komenda `{cmd_raw}` jest zabroniona: {danger}. Zaproponuj bezpieczniejszą alternatywę."
297
- })
319
+ self.messages.append(
320
+ {
321
+ "role": "user",
322
+ "content": f"Komenda `{cmd_raw}` jest zabroniona: {danger}. Zaproponuj bezpieczniejszą alternatywę.",
323
+ }
324
+ )
298
325
  return False
299
326
 
300
327
  cmd = deanonymize(cmd_raw)
@@ -313,25 +340,26 @@ class AutonomousSession:
313
340
  anon_out, _ = anonymize(out)
314
341
  anon_cmd, _ = anonymize(cmd)
315
342
 
316
- self.messages.append({
317
- "role": "user",
318
- "content": (
319
- f"Wykonano: `{anon_cmd}`\n"
320
- f"Sukces: {ok}\n"
321
- f"Output: {anon_out[:MAX_SEARCH_QUERY_LENGTH]}\n"
322
- f"Zweryfikuj wynik i zaproponuj następną akcję."
323
- ),
324
- })
343
+ self.messages.append(
344
+ {
345
+ "role": "user",
346
+ "content": (
347
+ f"Wykonano: `{anon_cmd}`\n"
348
+ f"Sukces: {ok}\n"
349
+ f"Output: {anon_out[:MAX_SEARCH_QUERY_LENGTH]}\n"
350
+ f"Zweryfikuj wynik i zaproponuj następną akcję."
351
+ ),
352
+ }
353
+ )
325
354
  return True
326
355
 
327
356
  def _handle_skip(self, action_data: Dict[str, Any]) -> None:
328
357
  """Handle SKIP action."""
329
358
  reason = action_data.get("reason", "")
330
359
  print(f" ⏭️ Pomijam: {reason}")
331
- self.messages.append({
332
- "role": "user",
333
- "content": f"Pominięto: {reason}. Co dalej?"
334
- })
360
+ self.messages.append(
361
+ {"role": "user", "content": f"Pominięto: {reason}. Co dalej?"}
362
+ )
335
363
  self.fix_count += 1
336
364
 
337
365
  def _handle_done(self) -> None:
@@ -345,7 +373,9 @@ class AutonomousSession:
345
373
  """
346
374
  self._check_timeout()
347
375
  remaining = self._get_remaining_time()
348
- print(f" ⟳ Tura {self.fix_count + 1}/{self.max_fixes} | ⏰ {remaining}s pozostało")
376
+ print(
377
+ f" ⟳ Tura {self.fix_count + 1}/{self.max_fixes} | ⏰ {remaining}s pozostało"
378
+ )
349
379
 
350
380
  # Query LLM
351
381
  reply = self._query_llm()
@@ -360,10 +390,12 @@ class AutonomousSession:
360
390
  action_data = self._parse_action(reply)
361
391
  if not action_data:
362
392
  print(" ⚠️ Nieprawidłowy format JSON, kontynuuję...")
363
- self.messages.append({
364
- "role": "user",
365
- "content": "Odpowiedz TYLKO w formacie JSON jak w instrukcji."
366
- })
393
+ self.messages.append(
394
+ {
395
+ "role": "user",
396
+ "content": "Odpowiedz TYLKO w formacie JSON jak w instrukcji.",
397
+ }
398
+ )
367
399
  return True
368
400
 
369
401
  action = action_data.get("action", "SKIP")
@@ -407,9 +439,9 @@ class AutonomousSession:
407
439
  if not should_continue:
408
440
  break
409
441
  except SessionTimeout:
410
- print(f"\n ⏰ Timeout sesji.")
442
+ print("\n ⏰ Timeout sesji.")
411
443
  except KeyboardInterrupt:
412
- print(f"\n\n ⛔ Przerwano przez użytkownika (Ctrl+C).")
444
+ print("\n\n ⛔ Przerwano przez użytkownika (Ctrl+C).")
413
445
  finally:
414
446
  self._clear_timeout()
415
447
 
@@ -24,7 +24,7 @@ __all__ = [
24
24
  def run_hitl_session(diagnostics: dict, config, show_data: bool = True) -> None:
25
25
  """
26
26
  Run interactive HITL session with full transparency.
27
-
27
+
28
28
  This is a backward-compatible wrapper around HITLSession.
29
29
  For new code, use HITLSession directly.
30
30
  """
@@ -10,15 +10,11 @@ from ..providers.llm import LLMClient, LLMError
10
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
- from ..constants import (
14
- HITL_TIMEOUT_BUFFER,
15
- AUTONOMOU_TIMEOUT_BUFFER,
16
- CLEANUP_TIMEOUT_ESTIMATE,
17
- MAX_COMMAND_LENGTH,
18
- )
19
13
  from ..platform_utils import (
20
- setup_signal_timeout, cancel_signal_timeout,
21
- get_os_info, get_package_manager,
14
+ setup_signal_timeout,
15
+ cancel_signal_timeout,
16
+ get_os_info,
17
+ get_package_manager,
22
18
  )
23
19
  from ..utils.timeout import SessionTimeout
24
20
 
@@ -54,8 +50,10 @@ class HITLSession:
54
50
  def _setup_timeout(self) -> None:
55
51
  """Setup session timeout handler."""
56
52
  from . import session_io
53
+
57
54
  def _timeout(signum, frame) -> None:
58
55
  raise SessionTimeout()
56
+
59
57
  # Store reference in session_io for reinstatement during user input
60
58
  session_io._setup_timeout_ref(self, self.config.session_timeout, _timeout)
61
59
  setup_signal_timeout(self.config.session_timeout, _timeout)
@@ -67,6 +65,7 @@ class HITLSession:
67
65
  def remaining(self) -> int:
68
66
  """Get remaining session time in seconds."""
69
67
  from . import get_remaining_time
68
+
70
69
  return get_remaining_time(self)
71
70
 
72
71
  def _initialize_messages(self) -> bool:
@@ -96,17 +95,24 @@ class HITLSession:
96
95
  def _print_header(self) -> None:
97
96
  """Print session header with system info."""
98
97
  io.print_session_header(
99
- self.os_info, self.pkg_manager,
100
- self.config.model, self.config.session_timeout,
101
- self.remaining
98
+ self.os_info,
99
+ self.pkg_manager,
100
+ self.config.model,
101
+ self.config.session_timeout,
102
+ self.remaining,
102
103
  )
103
104
 
104
105
  def _handle_llm_error(self) -> bool:
105
106
  """Handle LLM error - try web search if enabled."""
106
- if self.config.enable_web_search and self.web_search_count < self.MAX_WEB_SEARCHES:
107
+ if (
108
+ self.config.enable_web_search
109
+ and self.web_search_count < self.MAX_WEB_SEARCHES
110
+ ):
107
111
  self.web_search_count += 1
108
112
  io.print_searching()
109
- results = search_all("linux system diagnostics repair", self.config.serpapi_key)
113
+ results = search_all(
114
+ "linux system diagnostics repair", self.config.serpapi_key
115
+ )
110
116
  if results:
111
117
  io.console.print(format_results_for_llm(results))
112
118
  return True
@@ -114,11 +120,21 @@ class HITLSession:
114
120
 
115
121
  def _check_low_confidence(self, reply: str) -> bool:
116
122
  """Check if LLM is uncertain and perform web search if enabled."""
117
- low_conf = any(p in reply.lower() for p in [
118
- "nie wiem", "nie jestem pewien", "i don't know",
119
- "not sure", "cannot determine",
120
- ])
121
- if low_conf and self.config.enable_web_search and self.web_search_count < self.MAX_WEB_SEARCHES:
123
+ low_conf = any(
124
+ p in reply.lower()
125
+ for p in [
126
+ "nie wiem",
127
+ "nie jestem pewien",
128
+ "i don't know",
129
+ "not sure",
130
+ "cannot determine",
131
+ ]
132
+ )
133
+ if (
134
+ low_conf
135
+ and self.config.enable_web_search
136
+ and self.web_search_count < self.MAX_WEB_SEARCHES
137
+ ):
122
138
  if io.ask_low_confidence_search():
123
139
  self.web_search_count += 1
124
140
  topic = extract_search_topic(reply)
@@ -126,8 +142,12 @@ class HITLSession:
126
142
  if results:
127
143
  web_ctx = format_results_for_llm(results)
128
144
  io.console.print(web_ctx)
129
- self.messages.append({"role": "user",
130
- "content": f"External sources:\n{web_ctx}\nUpdate analysis."})
145
+ self.messages.append(
146
+ {
147
+ "role": "user",
148
+ "content": f"External sources:\n{web_ctx}\nUpdate analysis.",
149
+ }
150
+ )
131
151
  return True
132
152
  return False
133
153
 
@@ -164,14 +184,17 @@ class HITLSession:
164
184
 
165
185
  # Handle all command types via handlers module
166
186
  should_continue, was_handled = handlers.parse_user_input(
167
- user_in, self.last_fixes, self.messages, self.executed,
168
- self.config.serpapi_key
187
+ user_in,
188
+ self.last_fixes,
189
+ self.messages,
190
+ self.executed,
191
+ self.config.serpapi_key,
169
192
  )
170
-
193
+
171
194
  if not was_handled:
172
195
  # Free text → send to LLM
173
196
  self.messages.append({"role": "user", "content": user_in})
174
-
197
+
175
198
  return should_continue
176
199
 
177
200
  def run(self) -> None:
@@ -195,7 +218,9 @@ class HITLSession:
195
218
  def _print_summary(self) -> None:
196
219
  """Print session summary."""
197
220
  elapsed = int(time.time() - self.start_ts)
198
- io.print_session_summary(len(self.messages)-2, elapsed, self.llm.total_tokens, self.executed)
221
+ io.print_session_summary(
222
+ len(self.messages) - 2, elapsed, self.llm.total_tokens, self.executed
223
+ )
199
224
 
200
225
 
201
226
  def run_hitl_session(
@@ -215,7 +240,7 @@ def run_hitl_session(
215
240
  # Backward compatibility exports
216
241
  __all__ = [
217
242
  "CmdResult",
218
- "HITLSession",
243
+ "HITLSession",
219
244
  "run_hitl_session",
220
245
  "SYSTEM_PROMPT",
221
246
  ]