Skip to content

Commit 477add7

Browse files
committed
feat: render LLM responses as markdown in terminal
Use rich to render markdown formatting (headings, code blocks, bold, lists, etc.) for all LLM responses. /copy still copies the raw LLM response text. - Add rich>=13.0.0 dependency - New iclaw/renderer.py with print_markdown() using rich Markdown - Main chat, /search, and /paste all render via print_markdown() - Streaming paths collect silently then render at end
1 parent 364961d commit 477add7

3 files changed

Lines changed: 31 additions & 6 deletions

File tree

iclaw/main.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from iclaw.commands.read import handle_read_command
2323
from iclaw.commands.search_provider import handle_search_provider_command
2424
from iclaw.commands.utils import handle_copy_command
25+
from iclaw.renderer import print_markdown
2526
from iclaw.completer import IclawCompleter
2627
from iclaw.config import (
2728
CONFIG_PATH,
@@ -244,9 +245,9 @@ async def _main():
244245
print()
245246
reply = ""
246247
for chunk in chunks:
247-
print(chunk, end="", flush=True)
248248
reply += chunk
249-
print("\n")
249+
print_markdown(reply)
250+
print()
250251
messages.append({"role": "assistant", "content": reply})
251252
last_reply = reply
252253
except UnsupportedModelError as e:
@@ -402,9 +403,9 @@ async def _main():
402403
print()
403404
reply = ""
404405
for chunk in chunks:
405-
print(chunk, end="", flush=True)
406406
reply += chunk
407-
print("\n")
407+
print_markdown(reply)
408+
print()
408409
messages.append({"role": "assistant", "content": reply})
409410
last_reply = reply
410411
except UnsupportedModelError as e:
@@ -554,7 +555,9 @@ async def _main():
554555
reply = response_message["content"]
555556
messages.append({"role": "assistant", "content": reply})
556557
last_reply = reply
557-
log.log_info(f"\n{reply}\n")
558+
print()
559+
print_markdown(reply)
560+
print()
558561
except UnsupportedModelError as e:
559562
print(f"Error: {e}", file=sys.stderr)
560563
print("Please select a different model with /model", file=sys.stderr)

iclaw/renderer.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Markdown rendering for terminal output using rich."""
2+
3+
from rich.console import Console
4+
from rich.markdown import Markdown
5+
6+
7+
def print_markdown(text: str) -> None:
8+
"""Render markdown text to the terminal with formatting.
9+
10+
Uses rich to render headings, code blocks, lists, bold/italic,
11+
links, tables, etc. Falls back to plain print if rendering fails.
12+
"""
13+
if not text or not text.strip():
14+
print(text or "")
15+
return
16+
17+
try:
18+
console = Console(soft_wrap=True)
19+
console.print(Markdown(text))
20+
except Exception:
21+
# Fallback to plain text if rich rendering fails
22+
print(text)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ version = "0.1.0"
88
description = "Interactive CLI REPL for chatting with GitHub Copilot"
99
readme = "README.md"
1010
requires-python = ">=3.8"
11-
dependencies = ["requests>=2.32.0", "pyperclip>=1.8.0", "beautifulsoup4>=4.12.0", "readability-lxml>=0.8.1", "lxml>=5.0.0", "tavily-python>=0.3.0", "prompt-toolkit>=3.0.0", "playwright>=1.40.0"]
11+
dependencies = ["requests>=2.32.0", "pyperclip>=1.8.0", "beautifulsoup4>=4.12.0", "readability-lxml>=0.8.1", "lxml>=5.0.0", "tavily-python>=0.3.0", "prompt-toolkit>=3.0.0", "playwright>=1.40.0", "rich>=13.0.0"]
1212

1313
[project.scripts]
1414
iclaw = "iclaw.main:main"

0 commit comments

Comments
 (0)