From 6719e04bd7417de7e021948f6e196921e223ccd5 Mon Sep 17 00:00:00 2001 From: Pedro Guina Saltareli Date: Tue, 7 Apr 2026 21:50:38 -0300 Subject: [PATCH 1/2] feat(cli): render markdown in responses with auto-detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add _rich_markdown_from_text() using rich.markdown.Markdown for proper rendering of headers, code blocks, tables, lists, and syntax highlighting. Add _smart_render() that auto-detects markdown syntax and routes to appropriate renderer (Markdown vs ANSI text). Updated 3 response rendering points: - Main agent responses (line ~6670) - Background tasks /plan (line ~4770) - /btw command (line ~4893) No config flag needed — markdown detection is automatic based on syntax presence. Falls back to ANSI rendering for plain text responses. --- cli.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/cli.py b/cli.py index 2dce0827c73e..8356a82d8a1b 100644 --- a/cli.py +++ b/cli.py @@ -903,6 +903,28 @@ def _rich_text_from_ansi(text: str) -> _RichText: return _RichText.from_ansi(text or "") +def _rich_markdown_from_text(text: str): + """Render markdown content using Rich's Markdown parser. + + Properly renders headers, code blocks, tables, lists, and other + markdown syntax with correct formatting and syntax highlighting. + """ + from rich.markdown import Markdown + return Markdown(text or "") + + +def _smart_render(text: str): + """Auto-detect markdown in text and render accordingly. + + Returns rich.markdown.Markdown if markdown syntax is detected, + otherwise falls back to RichText.from_ansi for ANSI-colored output. + """ + _MD_MARKERS = ('```', '## ', '### ', '- ', '1. ', '|--', '| ', '**') + if any(marker in (text or '') for marker in _MD_MARKERS): + return _rich_markdown_from_text(text) + return _rich_text_from_ansi(text) + + def _cprint(text: str): """Print ANSI-colored text through prompt_toolkit's native renderer. @@ -4745,7 +4767,7 @@ def _bg_thinking(text: str) -> None: _chat_console = ChatConsole() _chat_console.print(Panel( - _rich_text_from_ansi(response), + _smart_render(response), title=f"[{_resp_color} bold]{label} (background #{task_num})[/]", title_align="left", border_style=_resp_color, @@ -4868,7 +4890,7 @@ def run_btw(): _resp_color = "#4F6D4A" ChatConsole().print(Panel( - _rich_text_from_ansi(response), + _smart_render(response), title=f"[{_resp_color} bold]⚕ /btw[/]", title_align="left", border_style=_resp_color, @@ -6645,7 +6667,7 @@ def run_agent(): else: _chat_console = ChatConsole() _chat_console.print(Panel( - _rich_text_from_ansi(response), + _smart_render(response), title=f"[{_resp_color} bold]{label}[/]", title_align="left", border_style=_resp_color, From fe80102f600da1cf8c17d97388367e72a8d29d87 Mon Sep 17 00:00:00 2001 From: Pedro Guina Saltareli Date: Tue, 7 Apr 2026 21:51:02 -0300 Subject: [PATCH 2/2] test(cli): add markdown rendering tests Add 5 inspection tests verifying: - rich.markdown.Markdown import exists - _rich_markdown_from_text function defined - _smart_render auto-detection function defined - Markdown syntax markers detected - Fallback to ANSI rendering present - Response Panel rendering updated Add 4 integration tests verifying Rich Markdown actually renders: - Bold text (no raw asterisks) - Code blocks (no raw backticks) - Headers (no raw ##) - Lists (proper bullets) --- tests/cli/test_markdown_rendering.py | 120 +++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/cli/test_markdown_rendering.py diff --git a/tests/cli/test_markdown_rendering.py b/tests/cli/test_markdown_rendering.py new file mode 100644 index 000000000000..ea88fe837a46 --- /dev/null +++ b/tests/cli/test_markdown_rendering.py @@ -0,0 +1,120 @@ +"""Tests for markdown rendering in CLI responses.""" + +import pytest +from pathlib import Path + + +class TestMarkdownRendering: + """Test markdown rendering functions in cli.py.""" + + @pytest.fixture + def cli_source(self): + """Load cli.py source code for inspection.""" + cli_path = Path(__file__).parent.parent.parent / "cli.py" + return cli_path.read_text() + + def test_rich_markdown_import_exists(self, cli_source): + """Verify rich.markdown.Markdown is imported in cli.py.""" + assert "from rich.markdown import Markdown" in cli_source or \ + "from rich.markdown import Markdown as" in cli_source + + def test_rich_markdown_function_exists(self, cli_source): + """Verify _rich_markdown_from_text function is defined.""" + assert "def _rich_markdown_from_text" in cli_source + + def test_smart_render_function_exists(self, cli_source): + """Verify _smart_render auto-detection function is defined.""" + assert "def _smart_render" in cli_source + + def test_smart_render_uses_markdown_markers(self, cli_source): + """Verify _smart_render detects markdown syntax markers.""" + # Check for markdown detection markers + assert "```" in cli_source + assert "**" in cli_source or "## " in cli_source + + def test_smart_render_fallback_to_ansi(self, cli_source): + """Verify _smart_render falls back to _rich_text_from_ansi.""" + assert "_rich_text_from_ansi" in cli_source + # Should call _smart_render or _rich_markdown_from_text in response paths + assert "_smart_render(response)" in cli_source or \ + "_rich_markdown_from_text(response)" in cli_source + + def test_response_rendering_updated(self, cli_source): + """Verify response rendering points use markdown-aware rendering.""" + # At least one Panel should use smart_render or markdown renderer + # instead of plain _rich_text_from_ansi + lines = cli_source.split("\n") + panel_with_smart_render = False + + for i, line in enumerate(lines): + if "Panel(" in line: + # Check next few lines for smart_render or markdown call + context = "\n".join(lines[i:i+5]) + if "_smart_render" in context or "_rich_markdown_from_text" in context: + panel_with_smart_render = True + break + + assert panel_with_smart_render, \ + "No Panel found using _smart_render or _rich_markdown_from_text" + + +class TestMarkdownRenderingIntegration: + """Integration tests for markdown rendering (require Rich).""" + + def test_rich_markdown_renders_bold(self): + """Verify Rich Markdown renders bold text without raw asterisks.""" + from rich.markdown import Markdown + from rich.console import Console + from io import StringIO + + console = Console(file=StringIO(), force_terminal=True) + md = Markdown("**bold text**") + console.print(md) + + output = console.file.getvalue() + # Rich should render bold without raw asterisks + assert "**bold text**" not in output + + def test_rich_markdown_renders_code_block(self): + """Verify Rich Markdown renders code blocks properly.""" + from rich.markdown import Markdown + from rich.console import Console + from io import StringIO + + console = Console(file=StringIO(), force_terminal=True) + code_md = "```python\nprint('hello')\n```" + md = Markdown(code_md) + console.print(md) + + output = console.file.getvalue() + # Should not contain raw triple backticks + assert "```" not in output + + def test_rich_markdown_renders_header(self): + """Verify Rich Markdown renders headers without raw ##.""" + from rich.markdown import Markdown + from rich.console import Console + from io import StringIO + + console = Console(file=StringIO(), force_terminal=True) + md = Markdown("## Header") + console.print(md) + + output = console.file.getvalue() + # Should not contain raw ## + assert "## " not in output + + def test_rich_markdown_renders_list(self): + """Verify Rich Markdown renders lists without raw dashes.""" + from rich.markdown import Markdown + from rich.console import Console + from io import StringIO + + console = Console(file=StringIO(), force_terminal=True) + md = Markdown("- Item 1\n- Item 2") + console.print(md) + + output = console.file.getvalue() + # Rich may still show some bullet chars, but not raw "- " pattern + # This is a softer check since Rich uses unicode bullets + assert "- Item 1" not in output or "•" in output or "─" in output