fixos 2.2.9__tar.gz → 2.2.11__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 (132) hide show
  1. {fixos-2.2.9 → fixos-2.2.11}/CHANGELOG.md +29 -0
  2. {fixos-2.2.9 → fixos-2.2.11}/PKG-INFO +1 -1
  3. {fixos-2.2.9 → fixos-2.2.11}/fixos/__init__.py +1 -1
  4. fixos-2.2.11/fixos/agent/session_core.py +92 -0
  5. fixos-2.2.11/fixos/agent/session_io.py +219 -0
  6. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/cleanup_cmd.py +84 -71
  7. {fixos-2.2.9 → fixos-2.2.11}/fixos/config.py +10 -4
  8. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/flatpak_analyzer.py +45 -33
  9. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/storage_analyzer.py +73 -64
  10. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/PKG-INFO +1 -1
  11. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/SOURCES.txt +2 -1
  12. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/top_level.txt +0 -2
  13. {fixos-2.2.9 → fixos-2.2.11}/pyproject.toml +1 -1
  14. fixos-2.2.9/No changes needed for /home/tom/github/wronai/fixOS/agent/__init__.py +0 -8
  15. {fixos-2.2.9 → fixos-2.2.11}/.env.example +0 -0
  16. {fixos-2.2.9 → fixos-2.2.11}/LICENSE +0 -0
  17. {fixos-2.2.9 → fixos-2.2.11}/MANIFEST.in +0 -0
  18. {fixos-2.2.9 → fixos-2.2.11}/README.md +0 -0
  19. {fixos-2.2.9 → fixos-2.2.11}/docker/README.md +0 -0
  20. {fixos-2.2.9 → fixos-2.2.11}/docker/TEST_RESULTS.md +0 -0
  21. {fixos-2.2.9 → fixos-2.2.11}/docker/TEST_RESULTS_V2.md +0 -0
  22. {fixos-2.2.9 → fixos-2.2.11}/docker/alpine/Dockerfile +0 -0
  23. {fixos-2.2.9 → fixos-2.2.11}/docker/arch/Dockerfile +0 -0
  24. {fixos-2.2.9 → fixos-2.2.11}/docker/base/Dockerfile +0 -0
  25. {fixos-2.2.9 → fixos-2.2.11}/docker/broken-audio/Dockerfile +0 -0
  26. {fixos-2.2.9 → fixos-2.2.11}/docker/broken-full/Dockerfile +0 -0
  27. {fixos-2.2.9 → fixos-2.2.11}/docker/broken-network/Dockerfile +0 -0
  28. {fixos-2.2.9 → fixos-2.2.11}/docker/broken-thumbnails/Dockerfile +0 -0
  29. {fixos-2.2.9 → fixos-2.2.11}/docker/debian/Dockerfile +0 -0
  30. {fixos-2.2.9 → fixos-2.2.11}/docker/docker-compose.multi-system.yml +0 -0
  31. {fixos-2.2.9 → fixos-2.2.11}/docker/docker-compose.yml +0 -0
  32. {fixos-2.2.9 → fixos-2.2.11}/docker/fedora/Dockerfile +0 -0
  33. {fixos-2.2.9 → fixos-2.2.11}/docker/test-multi-system.sh +0 -0
  34. {fixos-2.2.9 → fixos-2.2.11}/docker/ubuntu/Dockerfile +0 -0
  35. {fixos-2.2.9 → fixos-2.2.11}/docs/examples/advanced_usage.py +0 -0
  36. {fixos-2.2.9 → fixos-2.2.11}/docs/examples/quickstart.py +0 -0
  37. {fixos-2.2.9 → fixos-2.2.11}/fixos/agent/__init__.py +0 -0
  38. {fixos-2.2.9 → fixos-2.2.11}/fixos/agent/autonomous.py +0 -0
  39. {fixos-2.2.9 → fixos-2.2.11}/fixos/agent/autonomous_session.py +0 -0
  40. {fixos-2.2.9 → fixos-2.2.11}/fixos/agent/hitl.py +0 -0
  41. {fixos-2.2.9 → fixos-2.2.11}/fixos/agent/hitl_session.py +0 -0
  42. {fixos-2.2.9 → fixos-2.2.11}/fixos/anonymizer.py +0 -0
  43. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/__init__.py +0 -0
  44. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/ask_cmd.py +0 -0
  45. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/config_cmd.py +0 -0
  46. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/features_cmd.py +0 -0
  47. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/fix_cmd.py +0 -0
  48. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/history_cmd.py +0 -0
  49. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/main.py +0 -0
  50. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/orchestrate_cmd.py +0 -0
  51. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/profile_cmd.py +0 -0
  52. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/provider_cmd.py +0 -0
  53. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/quickfix_cmd.py +0 -0
  54. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/report_cmd.py +0 -0
  55. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/rollback_cmd.py +0 -0
  56. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/scan_cmd.py +0 -0
  57. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/shared.py +0 -0
  58. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/token_cmd.py +0 -0
  59. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli/watch_cmd.py +0 -0
  60. {fixos-2.2.9 → fixos-2.2.11}/fixos/cli.py +0 -0
  61. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/__init__.py +0 -0
  62. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/dev_project_analyzer.py +0 -0
  63. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/disk_analyzer.py +0 -0
  64. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/service_cleanup.py +0 -0
  65. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/service_details.py +0 -0
  66. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/service_scanner.py +0 -0
  67. {fixos-2.2.9 → fixos-2.2.11}/fixos/diagnostics/system_checks.py +0 -0
  68. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/__init__.py +0 -0
  69. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/auditor.py +0 -0
  70. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/catalog.py +0 -0
  71. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/installer.py +0 -0
  72. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/profiles.py +0 -0
  73. {fixos-2.2.9 → fixos-2.2.11}/fixos/features/renderer.py +0 -0
  74. {fixos-2.2.9 → fixos-2.2.11}/fixos/fixes/__init__.py +0 -0
  75. {fixos-2.2.9 → fixos-2.2.11}/fixos/interactive/__init__.py +0 -0
  76. {fixos-2.2.9 → fixos-2.2.11}/fixos/interactive/cleanup_planner.py +0 -0
  77. {fixos-2.2.9 → fixos-2.2.11}/fixos/llm_shell.py +0 -0
  78. {fixos-2.2.9 → fixos-2.2.11}/fixos/orchestrator/__init__.py +0 -0
  79. {fixos-2.2.9 → fixos-2.2.11}/fixos/orchestrator/executor.py +0 -0
  80. {fixos-2.2.9 → fixos-2.2.11}/fixos/orchestrator/graph.py +0 -0
  81. {fixos-2.2.9 → fixos-2.2.11}/fixos/orchestrator/orchestrator.py +0 -0
  82. {fixos-2.2.9 → fixos-2.2.11}/fixos/orchestrator/rollback.py +0 -0
  83. {fixos-2.2.9 → fixos-2.2.11}/fixos/platform_utils.py +0 -0
  84. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/__init__.py +0 -0
  85. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/base.py +0 -0
  86. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/__init__.py +0 -0
  87. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/audio.py +0 -0
  88. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/disk.py +0 -0
  89. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/hardware.py +0 -0
  90. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/resources.py +0 -0
  91. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/security.py +0 -0
  92. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/builtin/thumbnails.py +0 -0
  93. {fixos-2.2.9 → fixos-2.2.11}/fixos/plugins/registry.py +0 -0
  94. {fixos-2.2.9 → fixos-2.2.11}/fixos/profiles/__init__.py +0 -0
  95. {fixos-2.2.9 → fixos-2.2.11}/fixos/providers/__init__.py +0 -0
  96. {fixos-2.2.9 → fixos-2.2.11}/fixos/providers/llm.py +0 -0
  97. {fixos-2.2.9 → fixos-2.2.11}/fixos/providers/llm_analyzer.py +0 -0
  98. {fixos-2.2.9 → fixos-2.2.11}/fixos/providers/schemas.py +0 -0
  99. {fixos-2.2.9 → fixos-2.2.11}/fixos/system_checks.py +0 -0
  100. {fixos-2.2.9 → fixos-2.2.11}/fixos/utils/__init__.py +0 -0
  101. {fixos-2.2.9 → fixos-2.2.11}/fixos/utils/anonymizer.py +0 -0
  102. {fixos-2.2.9 → fixos-2.2.11}/fixos/utils/terminal.py +0 -0
  103. {fixos-2.2.9 → fixos-2.2.11}/fixos/utils/timeout.py +0 -0
  104. {fixos-2.2.9 → fixos-2.2.11}/fixos/utils/web_search.py +0 -0
  105. {fixos-2.2.9 → fixos-2.2.11}/fixos/watch.py +0 -0
  106. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/dependency_links.txt +0 -0
  107. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/entry_points.txt +0 -0
  108. {fixos-2.2.9 → fixos-2.2.11}/fixos.egg-info/requires.txt +0 -0
  109. {fixos-2.2.9 → fixos-2.2.11}/pytest.ini +0 -0
  110. {fixos-2.2.9 → fixos-2.2.11}/requirements-dev.txt +0 -0
  111. {fixos-2.2.9 → fixos-2.2.11}/requirements.txt +0 -0
  112. {fixos-2.2.9 → fixos-2.2.11}/scripts/pyqual-calibrate.py +0 -0
  113. {fixos-2.2.9 → fixos-2.2.11}/setup.cfg +0 -0
  114. {fixos-2.2.9 → fixos-2.2.11}/setup.py +0 -0
  115. {fixos-2.2.9 → fixos-2.2.11}/tests/__init__.py +0 -0
  116. {fixos-2.2.9 → fixos-2.2.11}/tests/conftest.py +0 -0
  117. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/__init__.py +0 -0
  118. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_anonymization_layers.py +0 -0
  119. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_audio_broken.py +0 -0
  120. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_cli.py +0 -0
  121. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_executor.py +0 -0
  122. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_multi_system.py +0 -0
  123. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_network_broken.py +0 -0
  124. {fixos-2.2.9 → fixos-2.2.11}/tests/e2e/test_thumbnails_broken.py +0 -0
  125. {fixos-2.2.9 → fixos-2.2.11}/tests/test_fixos.py +0 -0
  126. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/__init__.py +0 -0
  127. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/test_anonymizer.py +0 -0
  128. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/test_core.py +0 -0
  129. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/test_executor.py +0 -0
  130. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/test_orchestrator.py +0 -0
  131. {fixos-2.2.9 → fixos-2.2.11}/tests/unit/test_service_cleanup.py +0 -0
  132. {fixos-2.2.9 → fixos-2.2.11}/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.11] - 2026-04-09
154
+
155
+ ### Docs
156
+ - Update docs/README.md
157
+ - Update project/README.md
158
+ - Update project/context.md
159
+
160
+ ### Other
161
+ - Update project/analysis.toon.yaml
162
+ - Update project/calls.mmd
163
+ - Update project/calls.png
164
+ - Update project/compact_flow.mmd
165
+ - Update project/compact_flow.png
166
+ - Update project/duplication.toon.yaml
167
+ - Update project/evolution.toon.yaml
168
+ - Update project/flow.mmd
169
+ - Update project/flow.png
170
+ - Update project/index.html
171
+ - ... and 4 more files
172
+
173
+ ## [2.2.10] - 2026-04-09
174
+
175
+ ### Other
176
+ - Update No changes needed for /home/tom/github/wronai/fixOS/agent/__init__.py
177
+ - Update fixos/cli/cleanup_cmd.py
178
+ - Update fixos/config.py
179
+ - Update fixos/diagnostics/flatpak_analyzer.py
180
+ - Update fixos/diagnostics/storage_analyzer.py
181
+
153
182
  ## [2.2.9] - 2026-04-04
154
183
 
155
184
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixos
3
- Version: 2.2.9
3
+ Version: 2.2.11
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
@@ -1,2 +1,2 @@
1
1
  """fixos – AI-powered Linux/Windows diagnostics and repair."""
2
- __version__ = "2.2.9"
2
+ __version__ = "2.2.11"
@@ -0,0 +1,92 @@
1
+ """
2
+ Core session types and constants for HITL agent.
3
+ """
4
+
5
+ import re
6
+ import time
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Tuple
9
+
10
+ # Constants for UI formatting and timeouts
11
+ MAX_SUMMARY_LENGTH = 80
12
+ MAX_STDOUT_LINES = 40
13
+ DEFAULT_COMMAND_TIMEOUT = 120
14
+
15
+
16
+ @dataclass
17
+ class CmdResult:
18
+ """Result of executed command."""
19
+ cmd: str
20
+ comment: str
21
+ ok: bool
22
+ stdout: str
23
+ stderr: str
24
+ returncode: int
25
+ skipped: bool = False
26
+ timestamp: float = field(default_factory=time.time)
27
+
28
+
29
+ SYSTEM_PROMPT = """You are an expert Linux/Windows/macOS system diagnostics assistant.
30
+
31
+ You receive anonymized diagnostic data OR a user-described problem. Your tasks:
32
+
33
+ 1. DIAGNOSE – identify ALL problems (🔴 critical → 🟡 important → 🟢 minor)
34
+ 2. SOLUTIONS – for each problem provide a CONCRETE command with a brief explanation
35
+ 3. FORMAT – always use this exact format:
36
+
37
+ ━━━ DIAGNOZA ━━━
38
+ 🔴 Problem 1: [description]
39
+ **Komenda:** `command to run`
40
+ **Co robi:** one-sentence explanation
41
+
42
+ 🟡 Problem 2: [description]
43
+ **Komenda:** `command`
44
+ **Co robi:** explanation
45
+
46
+ Always end with:
47
+ ━━━ DOSTĘPNE AKCJE ━━━
48
+ [1] Fix problem 1 – `command`
49
+ [2] Fix problem 2 – `command`
50
+ [A] Fix all automatically
51
+ [S] Skip all
52
+ [D] Describe a new problem
53
+ [Q] End session
54
+
55
+ IMPORTANT: Adapt commands to the detected OS (Linux/Windows/macOS).
56
+ """
57
+
58
+
59
+ def extract_fixes(reply: str) -> List[Tuple[str, str]]:
60
+ """Extract (command, comment) pairs from LLM reply."""
61
+ fixes: List[Tuple[str, str]] = []
62
+ for m in re.finditer(
63
+ r"\*\*Komenda:\*\*\s*`([^`]+)`(?:[^\n]*?\*\*Co robi:\*\*\s*(.+?))?(?=\n|$)",
64
+ reply, re.IGNORECASE,
65
+ ):
66
+ cmd = m.group(1).strip()
67
+ if cmd:
68
+ fixes.append((cmd, (m.group(2) or "").strip()))
69
+ if not fixes:
70
+ for m in re.finditer(r"→\s*Fix:\s*`([^`]+)`", reply, re.IGNORECASE):
71
+ fixes.append((m.group(1).strip(), ""))
72
+ if not fixes:
73
+ for m in re.finditer(r"\[(\d+)\][^`\n]+`([^`]+)`", reply):
74
+ fixes.append((m.group(2).strip(), f"Fix #{m.group(1)}"))
75
+ if not fixes:
76
+ for m in re.finditer(r"EXEC:\s*`([^`]+)`", reply, re.IGNORECASE):
77
+ fixes.append((m.group(1).strip(), ""))
78
+ return fixes
79
+
80
+
81
+ def extract_search_topic(llm_reply: str) -> str:
82
+ """Extract search keywords from LLM reply."""
83
+ tech_terms = re.findall(
84
+ r"\b(sof-firmware|pipewire|alsa|thumbnails?|nautilus|"
85
+ r"dnf|apt|systemctl|journalctl|codec|driver|nvidia|amd|"
86
+ r"snd_hda|intel_sst|avs|wireplumber|pulseaudio|bluetooth|wifi)\b",
87
+ llm_reply, re.IGNORECASE
88
+ )
89
+ if tech_terms:
90
+ return " ".join(dict.fromkeys(tech_terms[:4]))
91
+ first_sentence = llm_reply.split(".")[0][:MAX_SUMMARY_LENGTH]
92
+ return first_sentence or "linux system diagnostics"
@@ -0,0 +1,219 @@
1
+ """
2
+ UI/IO operations for HITL session.
3
+ All terminal output and input handling.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from rich.panel import Panel
9
+ from rich.rule import Rule
10
+ from rich.syntax import Syntax
11
+ from rich.text import Text
12
+
13
+ from ..utils.terminal import (
14
+ _C, render_md as _render_md, console,
15
+ print_cmd_block as _print_cmd_block_rich,
16
+ print_stdout_box, print_stderr_box,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from .session_core import CmdResult
21
+
22
+
23
+ def print_session_header(os_info: dict, pkg_manager: str, model: str, timeout: int, remaining_fn) -> None:
24
+ """Print session header with system info."""
25
+ from rich.panel import Panel
26
+ header = Text()
27
+ header.append(f"👤 HUMAN-IN-THE-LOOP | Model: {model}\n", style="bold cyan")
28
+ header.append(f"🖥️ OS: {os_info['system']} {os_info['release']} | PM: {pkg_manager}\n", style="cyan")
29
+ header.append(f"⏰ Sesja: max {fmt_time(timeout)}", style="dim")
30
+ console.print()
31
+ console.print(Panel(header, border_style="cyan"))
32
+
33
+
34
+ def fmt_time(s: int) -> str:
35
+ """Format seconds as HH:MM:SS."""
36
+ return f"{s//3600:02d}:{(s%3600)//60:02d}:{s%60:02d}"
37
+
38
+
39
+ def print_action_menu(fixes: list, remaining: int, total_tokens: int) -> None:
40
+ """Print the interactive numbered action menu."""
41
+ console.print()
42
+ console.print(Rule(
43
+ f"[bold cyan]📋 DOSTĘPNE AKCJE[/bold cyan] [dim]⏰ {fmt_time(remaining)} ~{total_tokens} tokenów[/dim]",
44
+ style="cyan",
45
+ ))
46
+ if fixes:
47
+ for i, (cmd, comment) in enumerate(fixes, 1):
48
+ label = comment if comment else (cmd[:55] + "..." if len(cmd) > 55 else cmd)
49
+ console.print(f" [bold yellow][{i}][/bold yellow] {label}")
50
+ console.print(Panel(
51
+ Syntax(cmd, "bash", theme="monokai", word_wrap=True),
52
+ border_style="dim cyan",
53
+ padding=(0, 1),
54
+ ))
55
+ console.print()
56
+ console.print(f" [bold yellow][A][/bold yellow] Wykonaj wszystkie ({len(fixes)} komend)")
57
+ console.print(f" [bold yellow][S][/bold yellow] Pomiń wszystkie")
58
+ else:
59
+ console.print(" [dim](brak zaproponowanych komend)[/dim]")
60
+ console.print()
61
+ console.print(f" [bold yellow][D][/bold yellow] Opisz własny problem / co chcesz zmienić")
62
+ console.print(f" [bold yellow][!cmd][/bold yellow] Wykonaj własną komendę")
63
+ console.print(f" [bold yellow][search <q>][/bold yellow] Szukaj zewnętrznie")
64
+ console.print(f" [bold yellow][?][/bold yellow] Zapytaj o więcej szczegółów")
65
+ console.print(f" [bold yellow][Q][/bold yellow] Zakończ sesję")
66
+ console.print(Rule(style="cyan"))
67
+
68
+
69
+ def ask_user_problem() -> str:
70
+ """Interactively asks the user to describe their problem."""
71
+ body = Text()
72
+ body.append("Napisz co chcesz naprawić, zmienić lub co nie działa.\n", style="white")
73
+ body.append("Możesz pisać po polsku lub angielsku.\n\n", style="dim")
74
+ body.append("Przykłady:\n", style="bold cyan")
75
+ body.append(
76
+ " - 'brak dźwięku po aktualizacji'\n"
77
+ " - 'chcę przyspieszyć uruchamianie systemu'\n"
78
+ " - 'wifi nie działa po uśpieniu'\n"
79
+ " - 'chcę zainstalować sterowniki NVIDIA'\n"
80
+ " - 'dysk jest prawie pełny, co usunąć'\n"
81
+ " - 'jak skonfigurować firewall'",
82
+ style="dim",
83
+ )
84
+ console.print()
85
+ console.print(Panel(body, title="[bold cyan]💬 OPISZ SWÓJ PROBLEM[/bold cyan]", border_style="cyan"))
86
+ try:
87
+ return console.input(" [bold cyan]Twój problem:[/bold cyan] ").strip()
88
+ except (EOFError, KeyboardInterrupt):
89
+ return ""
90
+
91
+
92
+ def print_cmd_preview(cmd: str, comment: str = "") -> None:
93
+ """Shows command in a clear block before execution."""
94
+ _print_cmd_block_rich(cmd, comment=comment)
95
+
96
+
97
+ def print_cmd_result(result: "CmdResult") -> None:
98
+ """Shows command result with colorized markdown."""
99
+ from rich.text import Text
100
+ if result.skipped:
101
+ console.print(Text(f"⏭️ Pominięto: `{result.cmd}`", style="dim"))
102
+ return
103
+
104
+ if result.ok:
105
+ console.print(Text(f"✅ {result.cmd}", style="bold green"))
106
+ else:
107
+ console.print(Text(f"❌ (kod {result.returncode}) {result.cmd}", style="bold red"))
108
+
109
+ if result.stdout.strip():
110
+ print_stdout_box(result.stdout, max_lines=40)
111
+ elif not result.ok and not result.stderr.strip():
112
+ console.print("[dim](brak stdout)[/dim]")
113
+
114
+ if result.stderr.strip() and not result.ok:
115
+ print_stderr_box(result.stderr)
116
+ console.print()
117
+
118
+
119
+ def print_session_summary(messages_count: int, elapsed: int, total_tokens: int, executed: list) -> None:
120
+ """Print session summary."""
121
+ ok_count = sum(1 for r in executed if r.ok)
122
+ console.print(
123
+ f"\n [bold cyan]📊 Sesja:[/bold cyan] {messages_count} tur | {fmt_time(elapsed)} | "
124
+ f"~{total_tokens} tokenów | [green]{ok_count}[/green]/[red]{len(executed)}[/red] komend OK"
125
+ )
126
+
127
+
128
+ def print_thinking() -> None:
129
+ """Print 'Analyzing...' indicator."""
130
+ console.print("\n [dim]🧠 Analizuję...[/dim]", end="")
131
+
132
+
133
+ def clear_thinking() -> None:
134
+ """Clear the 'Analyzing...' indicator."""
135
+ console.print("\r" + " " * 30 + "\r", end="")
136
+
137
+
138
+ def print_llm_reply(reply: str) -> None:
139
+ """Render LLM reply with markdown formatting."""
140
+ console.print(Rule(style="dim cyan"))
141
+ _render_md(reply)
142
+ console.print(Rule(style="dim cyan"))
143
+
144
+
145
+ def print_llm_error(e: Exception) -> None:
146
+ """Print LLM error message."""
147
+ console.print(f"\n [bold red]❌ Błąd LLM:[/bold red] {e}")
148
+
149
+
150
+ def print_blocked_command(cmd: str, reason: str) -> None:
151
+ """Print blocked dangerous command warning."""
152
+ console.print(f"\n [bold red]⛔ ZABLOKOWANO:[/bold red] {reason}")
153
+ console.print(f" Komenda: [cyan]`{cmd}`[/cyan]")
154
+
155
+
156
+ def print_timeout() -> None:
157
+ """Print session timeout message."""
158
+ console.print("\n\n [bold yellow]⏰ Sesja wygasła.[/bold yellow]")
159
+
160
+
161
+ def print_session_ended() -> None:
162
+ """Print session ended message."""
163
+ console.print("\n [bold green]✅ Sesja zakończona.[/bold green]")
164
+
165
+
166
+ def print_session_interrupted() -> None:
167
+ """Print session interrupted message."""
168
+ console.print("\n Sesja przerwana.")
169
+
170
+
171
+ def print_executing_all(count: int) -> None:
172
+ """Print executing all commands message."""
173
+ console.print(f"\n [bold cyan]▶️ Wykonuję wszystkie {count} komend...[/bold cyan]\n")
174
+
175
+
176
+ def print_no_commands() -> None:
177
+ """Print no commands available message."""
178
+ console.print(" [dim]Brak komend do wykonania.[/dim]")
179
+
180
+
181
+ def print_invalid_option(user_in: str, max_option: int) -> None:
182
+ """Print invalid option message."""
183
+ console.print(f" [yellow]Brak opcji [{user_in}]. Dostępne: 1–{max_option}[/yellow]")
184
+
185
+
186
+ def print_no_results() -> None:
187
+ """Print no search results message."""
188
+ console.print(" [dim]Brak wyników.[/dim]")
189
+
190
+
191
+ def print_searching() -> None:
192
+ """Print searching message."""
193
+ console.print(" [yellow]🔎 Szukam zewnętrznie...[/yellow]")
194
+
195
+
196
+ def ask_execute_prompt() -> str:
197
+ """Ask user if they want to execute a command."""
198
+ return console.input(" [bold]Wykonać?[/bold] \\[Y/n]: ").strip().lower()
199
+
200
+
201
+ def ask_low_confidence_search() -> bool:
202
+ """Ask user if they want to search when LLM is uncertain."""
203
+ return console.input(
204
+ "\n [dim]💡 LLM niepewny – szukać zewnętrznie? [y/N]:[/dim] "
205
+ ).strip().lower() in ("y", "yes", "tak")
206
+
207
+
208
+ def ask_send_data() -> bool:
209
+ """Ask user if they want to send data to LLM."""
210
+ ans = console.input("\n Czy wysłać te dane do LLM? \\[Y/n]: ").strip().lower()
211
+ return ans not in ("n", "no", "nie")
212
+
213
+
214
+ def get_user_input(remaining: int) -> str:
215
+ """Get user input with prompt."""
216
+ try:
217
+ return console.input(f"\n [bold cyan]fixos [{fmt_time(remaining)}] ❯[/bold cyan] ").strip()
218
+ except (EOFError, KeyboardInterrupt):
219
+ return ""