{"id":28511707,"url":"https://github.com/statico/md2term","last_synced_at":"2025-07-03T20:31:55.397Z","repository":{"id":295499005,"uuid":"990261252","full_name":"statico/md2term","owner":"statico","description":"📕💻  A streaming markdown-to-terminal converter that renders markdown with rich formatting and syntax highlighting. Great for LLMs.","archived":false,"fork":false,"pushed_at":"2025-05-26T04:39:13.000Z","size":337,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-09T00:07:49.121Z","etag":null,"topics":["ansi-colors","cli","command-line-tool","console","llm","llms","markdown","markdown-parser","markdown-to-terminal","ollama","python","rich","streaming","syntax-highlighting","terminal","terminal-colors"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/md2term/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/statico.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-25T20:27:15.000Z","updated_at":"2025-05-30T18:52:12.000Z","dependencies_parsed_at":"2025-05-25T22:27:40.781Z","dependency_job_id":"f43653f1-afdd-4214-ae9c-ad4ef5a7a197","html_url":"https://github.com/statico/md2term","commit_stats":null,"previous_names":["statico/md2term"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/statico/md2term","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statico%2Fmd2term","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statico%2Fmd2term/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statico%2Fmd2term/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statico%2Fmd2term/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/statico","download_url":"https://codeload.github.com/statico/md2term/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/statico%2Fmd2term/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263399376,"owners_count":23460692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansi-colors","cli","command-line-tool","console","llm","llms","markdown","markdown-parser","markdown-to-terminal","ollama","python","rich","streaming","syntax-highlighting","terminal","terminal-colors"],"created_at":"2025-06-09T00:07:52.493Z","updated_at":"2025-07-03T20:31:55.240Z","avatar_url":"https://github.com/statico.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# md2term\n\n[![GitHub repo](https://img.shields.io/badge/github-repo-green)](https://github.com/statico/md2term) [![PyPI](https://img.shields.io/pypi/v/md2term.svg)](https://pypi.org/project/md2term/) [![Changelog](https://img.shields.io/github/v/release/statico/md2term?include_prereleases\u0026label=changelog)](https://github.com/statico/md2term/releases) [![Tests](https://github.com/statico/md2term/workflows/Test/badge.svg)](https://github.com/statico/md2term/actions?query=workflow%3ATest) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/statico/md2term/blob/main/LICENSE)\n\nA streaming markdown-to-terminal converter that renders markdown with rich formatting and syntax highlighting. Streaming is supported so you can pipe your favorite LLM CLI tool to it, like [llm](https://github.com/simonw/llm) or [Ollama](https://ollama.com/), like this:\n\n![demo](https://github.com/user-attachments/assets/b64d5f92-4ecd-49fd-b733-0ee81955013b)\n\n\u003e [!NOTE]\n\u003e This software was created almost entirely by AI with [Cursor](https://www.cursor.com/) and [Claude 4 Sonnet](https://www.anthropic.com/).\n\n## Installation\n\nInstall md2term using `uv`:\n\n```bash\nuv tool install md2term\n```\n\nOr with `pip` or `pipx`:\n\n```bash\npip install md2term\n# or\npipx install md2term\n```\n\nVerify the installation:\n\n```bash\nmd2term --version\n```\n\n## Usage\n\n### Command Line\n\n```bash\n# Convert a markdown file\nmd2term README.md\n\n# Read from stdin\ncat README.md | md2term\n\n# Pipe from other commands\ncurl -s https://raw.githubusercontent.com/user/repo/main/README.md | md2term\n\n# Or commands with slow output\nllm 'tell me long a story about cheesecakes using markdown formatting' | md2term\n\n# Override terminal width\nmd2term --width 100 README.md\n\n# Show version\nmd2term --version\n\n# Show help\nmd2term --help\n```\n\n### Python Library\n\nYou can also use md2term as a Python library for integrating markdown rendering into your applications:\n\n````python\nfrom rich.console import Console\nfrom md2term import convert, StreamingRenderer\n\n# Simple conversion\nmarkdown_text = \"# Hello\\n\\nThis is **bold** text.\"\nconvert(markdown_text)\n\n# Streaming usage (great for LLM applications)\nconsole = Console(force_terminal=True)\nrenderer = StreamingRenderer(console)\n\ntry:\n    # Add content incrementally\n    renderer.add_text(\"# Streaming Example\\n\\n\")\n    renderer.add_text(\"This content appears **immediately** as it's added.\\n\")\n    renderer.add_text(\"\\n```python\\nprint('Hello, World!')\\n```\\n\")\nfinally:\n    # Always finalize to ensure complete rendering\n    renderer.finalize()\n````\n\nThe streaming functionality is particularly useful for:\n\n- LLM/AI applications that generate content in real-time\n- Processing large files with immediate visual feedback\n- Building interactive CLI tools with progressive output\n\nSee `example_streaming.py` for more detailed examples and patterns.\n\n## Examples\n\nFor a comprehensive example of markdown features, see `example.md` in this repository.\n\n## Design Decisions\n\n### Streaming and Timing Strategy\n\nThe program uses a sophisticated streaming approach designed to provide responsive real-time rendering while minimizing visual flickering and corruption:\n\n#### Smart Update Frequency Control\n\nThe streaming renderer uses balanced update conditions to determine when to re-render content:\n\n1. **Content-based triggers**: Updates when 80+ characters are added AND at least 50ms have passed\n2. **Paragraph breaks**: Immediate updates on double newlines (`\\n\\n`) for clear content boundaries\n3. **Time-based updates**: Every 100ms if at least 20 characters have been added\n4. **Completion detection**: Updates when content appears complete (headings, list items, blockquotes) AND 80ms have passed\n\nThis approach balances responsiveness with performance, avoiding excessive re-rendering while ensuring users see content as it streams in.\n\n#### Backtracking and Re-rendering\n\n- **Minimal re-rendering**: Only re-renders when buffer content actually changes\n- **Accurate line counting**: Uses a temporary console to count output lines before clearing previous content\n- **ANSI escape sequences**: Clears previous output using `\\033[1A\\033[2K` (move up, clear line) for each rendered line\n- **Fallback handling**: Gracefully falls back to plain text if markdown parsing fails during streaming\n\n#### Input Processing Strategies\n\nThe program adapts its reading strategy based on input type:\n\n- **File input**: Reads entire content at once for optimal performance\n- **Stdin streaming**: Uses character-by-character reading with `select()` optimization when available\n- **Chunk optimization**: Reads 64-character chunks when data is readily available, falls back to single characters otherwise\n- **Cross-platform compatibility**: Gracefully handles systems where `select()` or `fileno()` are not available\n\n#### Completion Detection\n\nThe renderer intelligently detects when markdown elements appear complete:\n\n- **Double newlines**: Clear paragraph boundaries\n- **Structural elements**: Headings (`#`), list items (`- `), blockquotes (`\u003e `), horizontal rules (`---`)\n- **Empty lines**: Natural content breaks\n\nThis allows for responsive updates without waiting for arbitrary timeouts.\n\n### Code Block Handling\n\nThe program uses a smart approach to handle multi-line code blocks:\n\n1. **Streaming Processing**: For stdin input, the program processes content in chunks, buffering until it encounters blank lines (when not in a code block)\n2. **Code Fence Detection**: Detects triple backticks (`\\`\\`\\``) to track when we're inside code blocks\n3. **No Backtracking**: Instead of clearing previous lines, the program assumes that triple backticks always indicate the start/end of code blocks\n\nThis approach is efficient and works well for typical markdown usage patterns.\n\n### Color Scheme\n\n- **H1**: Bright cyan with rules above and below, centered\n- **H2**: Bright blue with rule below\n- **H3**: Bright magenta\n- **H4**: Bright yellow\n- **H5**: Bright green\n- **H6**: Bright white\n- **Code spans**: Red text on dark gray background\n- **Links**: Blue underlined text with URL in parentheses\n- **Lists**: Yellow bullets (•) for unordered, cyan numbers for ordered\n- **Blockquotes**: Blue italic text in a panel\n\n### Terminal Width Handling\n\nThe program automatically detects terminal width and wraps text accordingly. You can override this with the `--width` option for testing or specific formatting needs.\n\n## Development\n\nTo install for development:\n\n```bash\n# Clone the repository\ngit clone https://github.com/statico/md2term\ncd md2term\n\n# Install in development mode\nuv tool install --editable .\n\n# Or install dependencies for local development\nuv sync\nuv run md2term README.md\n```\n\n### Running Tests\n\nThe project uses pytest with snapshot testing via syrupy to ensure consistent output formatting:\n\n```bash\n# Install test dependencies\nuv sync --group test\n\n# Run all tests\nuv run pytest\n\n# Run tests with coverage\nuv run pytest --cov=md2term\n\n# Run specific test file\nuv run pytest tests/test_md2term.py\n\n# Run specific test\nuv run pytest tests/test_md2term.py::TestMarkdownFeatures::test_headings_all_levels\n```\n\n### Snapshot Testing\n\nThe tests use snapshot testing to verify that markdown rendering produces consistent terminal output. Snapshots capture the exact ANSI escape sequences and formatting that would appear in the terminal.\n\n```bash\n# Update snapshots when output changes (after verifying changes are correct)\nuv run pytest --snapshot-update\n\n# Review snapshot differences\nuv run pytest --snapshot-details\n```\n\n**Important**: When modifying rendering logic, always:\n\n1. Run tests to see what changed\n2. Manually verify the output looks correct with `uv run md2term example.md`\n3. Update snapshots only if the changes are intentional and correct\n\nThe snapshot file is located at `tests/__snapshots__/test_md2term.ambr` and contains the expected terminal output for various markdown inputs.\n\n## License\n\nThis project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatico%2Fmd2term","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstatico%2Fmd2term","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstatico%2Fmd2term/lists"}