termrender 0.1.0__tar.gz → 0.2.1__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 (36) hide show
  1. termrender-0.2.1/.github/workflows/publish.yml +48 -0
  2. termrender-0.2.1/CHANGELOG.md +97 -0
  3. termrender-0.2.1/PKG-INFO +473 -0
  4. termrender-0.2.1/README.md +445 -0
  5. termrender-0.2.1/design.json +408 -0
  6. {termrender-0.1.0 → termrender-0.2.1}/pyproject.toml +20 -2
  7. termrender-0.2.1/requirements.json +590 -0
  8. termrender-0.2.1/src/termrender/CLAUDE.md +59 -0
  9. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/__init__.py +6 -1
  10. termrender-0.2.1/src/termrender/__main__.py +251 -0
  11. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/blocks.py +1 -0
  12. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/emit.py +4 -1
  13. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/layout.py +21 -9
  14. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/parser.py +63 -8
  15. termrender-0.2.1/src/termrender/renderers/CLAUDE.md +40 -0
  16. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/borders.py +18 -7
  17. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/divider.py +5 -2
  18. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/mermaid.py +15 -1
  19. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/quote.py +3 -3
  20. termrender-0.2.1/src/termrender/renderers/table.py +83 -0
  21. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/tree.py +22 -5
  22. termrender-0.2.1/src/termrender/style.py +253 -0
  23. termrender-0.2.1/tests/__init__.py +0 -0
  24. termrender-0.2.1/tests/test_column_alignment.py +138 -0
  25. termrender-0.1.0/.github/workflows/publish.yml +0 -23
  26. termrender-0.1.0/PKG-INFO +0 -26
  27. termrender-0.1.0/src/termrender/__main__.py +0 -46
  28. termrender-0.1.0/src/termrender/style.py +0 -147
  29. {termrender-0.1.0 → termrender-0.2.1}/.gitignore +0 -0
  30. {termrender-0.1.0 → termrender-0.2.1}/LICENSE +0 -0
  31. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/py.typed +0 -0
  32. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/__init__.py +0 -0
  33. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/code.py +0 -0
  34. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/columns.py +0 -0
  35. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/panel.py +0 -0
  36. {termrender-0.1.0 → termrender-0.2.1}/src/termrender/renderers/text.py +0 -0
@@ -0,0 +1,48 @@
1
+ name: Release & Publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ released: ${{ steps.semrel.outputs.released }}
16
+ tag: ${{ steps.semrel.outputs.tag }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+ - uses: python-semantic-release/python-semantic-release@v9
25
+ id: semrel
26
+ with:
27
+ github_token: ${{ secrets.GITHUB_TOKEN }}
28
+ git_committer_name: "github-actions[bot]"
29
+ git_committer_email: "github-actions[bot]@users.noreply.github.com"
30
+
31
+ publish:
32
+ needs: release
33
+ if: needs.release.outputs.released == 'true'
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ permissions:
37
+ id-token: write
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ with:
41
+ fetch-depth: 0
42
+ ref: ${{ needs.release.outputs.tag }}
43
+ - uses: actions/setup-python@v5
44
+ with:
45
+ python-version: "3.12"
46
+ - run: pip install build
47
+ - run: python -m build
48
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,97 @@
1
+ # CHANGELOG
2
+
3
+
4
+ ## v0.2.1 (2026-04-05)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **mermaid**: Undo double-encoded UTF-8 from mermaid-ascii output
9
+ ([`9e0560c`](https://github.com/CaptainCrouton89/termrender/commit/9e0560ce46b6dc3f90d2d716a97780713e5e5e53))
10
+
11
+ mermaid-ascii misinterprets UTF-8 bytes as Latin-1 and re-encodes, corrupting multi-byte characters
12
+ (e.g. → renders as â<U+0086><U+0092>). Apply latin-1 round-trip to recover original UTF-8 in both
13
+ layout and renderer subprocess call sites.
14
+
15
+ ### Documentation
16
+
17
+ - Add tmux pane lifecycle and --check interaction notes to CLAUDE.md
18
+ ([`9400092`](https://github.com/CaptainCrouton89/termrender/commit/9400092e507d470acb97ac5a17b66fcf0e9aa2f6))
19
+
20
+
21
+ ## v0.2.0 (2026-04-05)
22
+
23
+ ### Bug Fixes
24
+
25
+ - Handle zero-width and emoji presentation chars in visual width calculation
26
+ ([`d0bb8dc`](https://github.com/CaptainCrouton89/termrender/commit/d0bb8dcfa5ca0d2c16d78a1d7f81825231b9cb59))
27
+
28
+ _char_width now returns 0 for combining marks and format characters (ZWJ, variation selectors).
29
+ visual_len handles VS16 emoji presentation sequences by promoting the preceding character to width
30
+ 2. Fixes panel border misalignment when content contains emoji or special Unicode.
31
+
32
+ - **docs**: Update README output examples to match actual rendered output
33
+ ([`de6d0cc`](https://github.com/CaptainCrouton89/termrender/commit/de6d0ccfd8a60aca20f2b2659a313f8d8c87d853))
34
+
35
+ ### Chores
36
+
37
+ - Add README, design specs, and project CLAUDE.md files
38
+ ([`93ac358`](https://github.com/CaptainCrouton89/termrender/commit/93ac35857981c549797a9359573cacea1478b3ad))
39
+
40
+ - Derive version from git tags via hatch-vcs
41
+ ([`33595a0`](https://github.com/CaptainCrouton89/termrender/commit/33595a0b64363e445b90c9df135a50a4652e2bae))
42
+
43
+ ### Continuous Integration
44
+
45
+ - Auto-release and publish via conventional commits
46
+ ([`80a456b`](https://github.com/CaptainCrouton89/termrender/commit/80a456b7301c57f2fd2b0cd30622b78f2d4b931e))
47
+
48
+ Replace manual GitHub release trigger with python-semantic-release. On push to main, conventional
49
+ commits are analyzed to determine version bumps (feat→minor, fix→patch) and publish to PyPI
50
+ automatically.
51
+
52
+ ### Documentation
53
+
54
+ - Update README token count and expand CLAUDE.md implementation notes
55
+ ([`1f70a53`](https://github.com/CaptainCrouton89/termrender/commit/1f70a5352cbced30219012dbede7040c6ac97457))
56
+
57
+ ### Features
58
+
59
+ - Add CJK ambiguous-width support, strict directive parsing, and rendering fixes
60
+ ([`c000883`](https://github.com/CaptainCrouton89/termrender/commit/c0008835d66b721b0a09c7a34dde11d08b3d3d94))
61
+
62
+ - Add emoji presentation and East Asian ambiguous-width character handling with --cjk flag and
63
+ TERMRENDER_CJK env var - All renderers (borders, divider, quote, tree) now compute box-drawing
64
+ character widths dynamically via visual_len - Parser raises DirectiveError on unclosed or stray
65
+ ::: directives instead of silently degrading - Fix column width distribution to correctly account
66
+ for inter-column gaps - Support 'author' as alias for 'by' attribute on quote blocks
67
+
68
+ - Add GFM table rendering with box-drawing borders
69
+ ([`c3b61cd`](https://github.com/CaptainCrouton89/termrender/commit/c3b61cdd659fdb782089cbca2fd3f74b18486605))
70
+
71
+ Enable mistune table plugin, parse table AST into TABLE blocks, and render with box-drawing
72
+ characters. Supports left/center/right column alignment, bold headers, auto-sized columns, and
73
+ proportional overflow distribution.
74
+
75
+ - **cli**: Add --tmux pane output, --check validation, and structured error handling
76
+ ([`36b52ee`](https://github.com/CaptainCrouton89/termrender/commit/36b52eed9701cd0acd363db2d0fa3d277244c8b0))
77
+
78
+ - --tmux renders in a new tmux side pane via split-window, piped through less -R - --check validates
79
+ directive syntax without rendering (exit 0/2) - Structured _error() helper with fix/hint guidance
80
+ on stderr - Named exit codes (EXIT_OK, EXIT_INPUT, EXIT_SYNTAX, EXIT_TERMINAL) - Expanded epilog
81
+ with full directive reference, nesting examples, and env docs - Dynamic version from
82
+ importlib.metadata (hatch-vcs) - Updated CLAUDE.md to document --check behavior and fix recursion
83
+ depth note
84
+
85
+ - **cli**: Improve help output with examples, version flag, and tty detection
86
+ ([`cb3e7e2`](https://github.com/CaptainCrouton89/termrender/commit/cb3e7e2752ae860e5c3cbd4c4f1627e925a9c431))
87
+
88
+ ### Testing
89
+
90
+ - Add column alignment and visual width tests
91
+ ([`f8b6099`](https://github.com/CaptainCrouton89/termrender/commit/f8b60998625977b10dd4697f8e772d80125cb9ce))
92
+
93
+ Covers showpiece rendering, column line width consistency, status marker visual widths (text vs
94
+ emoji presentation), and panel border alignment.
95
+
96
+
97
+ ## v0.1.0 (2026-04-04)
@@ -0,0 +1,473 @@
1
+ Metadata-Version: 2.4
2
+ Name: termrender
3
+ Version: 0.2.1
4
+ Summary: Rich terminal rendering of directive-flavored markdown
5
+ Project-URL: Homepage, https://github.com/CaptainCrouton89/termrender
6
+ Project-URL: Repository, https://github.com/CaptainCrouton89/termrender
7
+ Project-URL: Issues, https://github.com/CaptainCrouton89/termrender/issues
8
+ Author: Silas Rhyneer
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ansi,cli,markdown,rendering,terminal
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Terminals
22
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: mermaid-ascii>=1.0
25
+ Requires-Dist: mistune>=3.0
26
+ Requires-Dist: pygments>=2.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # termrender
30
+
31
+ Rich terminal rendering of directive-flavored markdown.
32
+
33
+ ## What this is
34
+
35
+ termrender takes markdown with lightweight directives and renders it as formatted terminal output with borders, colors, tree views, side-by-side columns, syntax highlighting, and mermaid diagrams.
36
+
37
+ ## Why this exists
38
+
39
+ LLM agents are terrible at formatting terminal output. Not because they can't — because the cost is absurd.
40
+
41
+ Ask an agent to produce a bordered status panel and watch what happens. It manually draws every `━` character, pads every line to the right width, counts columns, aligns content. A simple box with three bullet points runs 200+ output tokens, and almost all of them are decorative characters the agent computed one by one.
42
+
43
+ To draw a panel the traditional way, the agent has to generate something like this:
44
+
45
+ ```
46
+ ╭─ Deploy Status ───────────────────╮
47
+ │ │
48
+ │ ✔ Unit tests passed │
49
+ │ ✔ Lint clean │
50
+ │ ✖ Integration: 2 failures │
51
+ │ │
52
+ ╰───────────────────────────────────╯
53
+ ```
54
+
55
+ Every box-drawing glyph, every padding space, every repeated dash. All tokens.
56
+
57
+ With termrender, the agent writes this instead:
58
+
59
+ ```markdown
60
+ :::panel{title="Deploy Status" color="green"}
61
+ - ✔ Unit tests passed
62
+ - ✔ Lint clean
63
+ - ✖ Integration: 2 failures
64
+ :::
65
+ ```
66
+
67
+ Same visual result. About 40 tokens of directive syntax versus 200+ tokens of manual box drawing. The agent writes structure. termrender handles pixels.
68
+
69
+ Output tokens are the bottleneck in LLM-powered tools. They're slower to produce than input tokens and they eat context window. Cutting 95% of the formatting overhead means faster responses and more room for the content that actually matters.
70
+
71
+ There's a second thing. Agents are bad at visual layout. Counting characters, padding to exact widths, aligning columns across lines — LLMs get this wrong all the time. Off-by-one padding, broken box corners, widths that don't match. With directives, the agent can't screw up the formatting because it never touches it.
72
+
73
+ ## What it looks like
74
+
75
+ Here's a realistic example — the kind of thing an LLM agent might produce after a deployment. It uses most of termrender's features at once: nested panels, columns, trees with status markers, callouts, a code block, a divider, and a quote.
76
+
77
+ The agent writes this:
78
+
79
+ ```markdown
80
+ :::panel{title="Deploy — api-gateway v3.2.0" color="cyan"}
81
+
82
+ Completed at **14:32 UTC** on `prod-us-east-1`. Health checks passing.
83
+
84
+ :::columns
85
+ :::col{width="55%"}
86
+ :::panel{title="Services" color="green"}
87
+ :::tree
88
+ api-gateway/ [x]
89
+ auth/ [x]
90
+ rate-limiter/ [x]
91
+ cache/ [x]
92
+ worker-pool/
93
+ job-runner/ [x]
94
+ scheduler/ [!]
95
+ dead-letter/ [x]
96
+ :::
97
+ :::
98
+ :::
99
+ :::col{width="45%"}
100
+ :::callout{type="success"}
101
+ 6 of 7 services healthy
102
+ :::
103
+
104
+ :::callout{type="warning"}
105
+ scheduler: 83% memory
106
+ GC tuning shipping next release
107
+ :::
108
+
109
+ - **p99 latency**: 34ms
110
+ - **error rate**: 0.02%
111
+ - **throughput**: 12.4k req/s
112
+ :::
113
+ :::
114
+
115
+ :::divider{label="rollback"}
116
+ :::
117
+
118
+ :::code{lang="bash"}
119
+ # If p99 exceeds 200ms:
120
+ kubectl rollout undo deployment/api-gateway -n prod
121
+ kubectl rollout status deployment/api-gateway -n prod
122
+ :::
123
+
124
+ :::quote{author="deploy-bot"}
125
+ Previous stable: v3.1.4 (deployed 2025-03-28)
126
+ :::
127
+ :::
128
+ ```
129
+
130
+ And termrender produces this:
131
+
132
+ ```
133
+ ┌─ Deploy — api-gateway v3.2.0 ──────────────────────────────────────────────┐
134
+ │ Completed at 14:32 UTC on prod-us-east-1. Health checks passing. │
135
+ │ ┌─ Services ───────────────────────────┐ ┌─ ✔ Success ──────────────────┐ │
136
+ │ │ api-gateway/ ✔ │ │ 6 of 7 services healthy │ │
137
+ │ │ ├── auth/ ✔ │ └──────────────────────────────┘ │
138
+ │ │ ├── rate-limiter/ ✔ │ ┌─ ⚠ Warning ──────────────────┐ │
139
+ │ │ └── cache/ ✔ │ │ scheduler: 83% memory GC │ │
140
+ │ │ worker-pool/ │ │ tuning shipping next release │ │
141
+ │ │ ├── job-runner/ ✔ │ └──────────────────────────────┘ │
142
+ │ │ ├── scheduler/ ⚠ │ • p99 latency: 34ms │
143
+ │ │ └── dead-letter/ ✔ │ • error rate: 0.02% │
144
+ │ └──────────────────────────────────────┘ • throughput: 12.4k req/s │
145
+ │ ──────────────────────────────── rollback ──────────────────────────────── │
146
+ │ ┌─ bash ─────────────────────────────────────────────────────────────────┐ │
147
+ │ │ # If p99 exceeds 200ms: │ │
148
+ │ │ kubectl rollout undo deployment/api-gateway -n prod │ │
149
+ │ │ kubectl rollout status deployment/api-gateway -n prod │ │
150
+ │ └────────────────────────────────────────────────────────────────────────┘ │
151
+ │ │ Previous stable: v3.1.4 (deployed 2025-03-28) │
152
+ │ │ — deploy-bot │
153
+ └────────────────────────────────────────────────────────────────────────────┘
154
+ ```
155
+
156
+ That entire output — the nested borders, the tree guide lines, the side-by-side columns, the colored callouts, the syntax-highlighted code block — came from about 40 lines of directive markdown. An agent drawing that manually would burn through north of 2,000 tokens just on box-drawing characters and padding spaces. The directive input is around 80.
157
+
158
+
159
+ ## Install
160
+
161
+ ```bash
162
+ pip install termrender
163
+ ```
164
+
165
+ Python 3.10+. Three dependencies: [mistune](https://github.com/lepture/mistune) (markdown parsing), [pygments](https://pygments.org/) (syntax highlighting), [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii) (diagram rendering).
166
+
167
+
168
+ ## Usage
169
+
170
+ ### CLI
171
+
172
+ ```bash
173
+ termrender doc.md # render a file
174
+ termrender doc.md --width 100 # fixed width
175
+ termrender doc.md --no-color # strip ANSI codes
176
+ cat doc.md | termrender # from stdin
177
+ echo '# Hello' | termrender # inline
178
+ ```
179
+
180
+ Reads from a file or stdin. Auto-detects terminal width unless you specify `--width`. Respects `NO_COLOR`.
181
+
182
+ ### Python
183
+
184
+ ```python
185
+ from termrender import render
186
+
187
+ output = render(source, width=80, color=True)
188
+ print(output)
189
+ ```
190
+
191
+ One function. Source in, ANSI string out.
192
+
193
+
194
+ ## Directives
195
+
196
+ Triple-colon syntax with optional attributes in curly braces. They nest arbitrarily. Standard markdown works everywhere inside them.
197
+
198
+ ```
199
+ :::name{key="value" key2="value2"}
200
+ content goes here
201
+ :::
202
+ ```
203
+
204
+ ### Panels
205
+
206
+ Bordered box with optional title and color.
207
+
208
+ **Input:**
209
+ ```markdown
210
+ :::panel{title="Migration" color="cyan"}
211
+ **Database**: migrated (v42 → v43)
212
+ - Added `users.email_verified` column
213
+ - Backfilled 2.3M rows in 14s
214
+ :::
215
+ ```
216
+
217
+ **Output:**
218
+ ```
219
+ ┌─ Migration ──────────────────────────┐
220
+ │ Database: migrated (v42 → v43) │
221
+ │ • Added users.email_verified column │
222
+ │ • Backfilled 2.3M rows in 14s │
223
+ └──────────────────────────────────────┘
224
+ ```
225
+
226
+ Colors: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`.
227
+
228
+ ### Columns
229
+
230
+ Side-by-side layout. Each `:::col` takes a width as a percentage.
231
+
232
+ **Input:**
233
+ ```markdown
234
+ :::columns
235
+ :::col{width="50%"}
236
+ **Before**
237
+ - Manual deploys
238
+ - 45 min rollbacks
239
+ - No staging env
240
+ :::
241
+ :::col{width="50%"}
242
+ **After**
243
+ - CI/CD pipeline
244
+ - 2 min rollbacks
245
+ - Full staging env
246
+ :::
247
+ :::
248
+ ```
249
+
250
+ **Output:**
251
+ ```
252
+ Before After
253
+ • Manual deploys • CI/CD pipeline
254
+ • 45 min rollbacks • 2 min rollbacks
255
+ • No staging env • Full staging env
256
+ ```
257
+
258
+ Good for comparisons, dashboards, anything where you'd otherwise scroll vertically through content that reads better side by side.
259
+
260
+ ### Trees
261
+
262
+ Indentation defines nesting. termrender auto-detects whether you're using 2 or 4 space indents and draws Unicode guide lines. Supports `**bold**` and `*italic*` labels, plus `[x]` (done) and `[!]` (warning) status markers.
263
+
264
+ **Input:**
265
+ ```markdown
266
+ :::tree{color="blue"}
267
+ src/
268
+ api/
269
+ routes.py [x]
270
+ middleware.py [x]
271
+ core/
272
+ models.py [x]
273
+ utils.py [!]
274
+ tests/
275
+ test_routes.py
276
+ :::
277
+ ```
278
+
279
+ **Output:**
280
+ ```
281
+ src/
282
+ ├── api/
283
+ │ ├── routes.py ✔
284
+ │ └── middleware.py ✔
285
+ ├── core/
286
+ │ ├── models.py ✔
287
+ │ └── utils.py ⚠
288
+ └── tests/
289
+ └── test_routes.py
290
+ ```
291
+
292
+ This is one of the best examples of why directive syntax wins. Manually drawing a tree with `├──`, `│`, and `└──` in the right places is genuinely painful for an LLM. Getting the continuation lines right when branches end at different depths? Agents mess this up constantly. With the tree directive, the agent just indents lines. termrender figures out the guide characters.
293
+
294
+ ### Callouts
295
+
296
+ Styled boxes. Four types: `info`, `warning`, `error`, `success`. Each gets its own icon and color.
297
+
298
+ **Input:**
299
+ ```markdown
300
+ :::callout{type="warning"}
301
+ Rate limiting is enabled on prod. Requests over 100/min per IP return 429.
302
+ :::
303
+
304
+ :::callout{type="error"}
305
+ The v2 endpoint is deprecated and will be removed May 1st.
306
+ :::
307
+
308
+ :::callout{type="success"}
309
+ All 847 tests passing. Coverage at 94%.
310
+ :::
311
+ ```
312
+
313
+ **Output:**
314
+ ```
315
+ ┌─ ⚠ Warning ──────────────────────────┐
316
+ │ Rate limiting is enabled on prod. │
317
+ │ Requests over 100/min per IP return │
318
+ │ 429. │
319
+ └──────────────────────────────────────┘
320
+ ┌─ ✖ Error ────────────────────────────┐
321
+ │ The v2 endpoint is deprecated and │
322
+ │ will be removed May 1st. │
323
+ └──────────────────────────────────────┘
324
+ ┌─ ✔ Success ──────────────────────────┐
325
+ │ All 847 tests passing. Coverage at │
326
+ │ 94%. │
327
+ └──────────────────────────────────────┘
328
+ ```
329
+
330
+ ### Quotes
331
+
332
+ Block quote with optional attribution. Rendered with a left border bar.
333
+
334
+ **Input:**
335
+ ```markdown
336
+ :::quote{author="Rob Pike"}
337
+ Simplicity is complicated.
338
+ :::
339
+ ```
340
+
341
+ **Output:**
342
+ ```
343
+ │ Simplicity is complicated.
344
+ │ — Rob Pike
345
+ ```
346
+
347
+ ### Code blocks
348
+
349
+ Syntax highlighted via Pygments. Supports 500+ languages. Displayed in a bordered box with the language label.
350
+
351
+ **Input:**
352
+ ```markdown
353
+ :::code{lang="python"}
354
+ def retry(fn, attempts=3):
355
+ for i in range(attempts):
356
+ try:
357
+ return fn()
358
+ except Exception:
359
+ if i == attempts - 1:
360
+ raise
361
+ :::
362
+ ```
363
+
364
+ **Output:**
365
+ ```
366
+ ┌─ python ─────────────────────────────┐
367
+ │ def retry(fn, attempts=3): │
368
+ │ for i in range(attempts): │
369
+ │ try: │
370
+ │ return fn() │
371
+ │ except Exception: │
372
+ │ if i == attempts - 1: │
373
+ │ raise │
374
+ └──────────────────────────────────────┘
375
+ ```
376
+
377
+ Standard fenced code blocks (` ```python `) also work and get the same treatment.
378
+
379
+ ### Dividers
380
+
381
+ Horizontal rules with optional centered labels.
382
+
383
+ **Input:**
384
+ ```markdown
385
+ :::divider{label="Phase 2"}
386
+ :::
387
+ ```
388
+
389
+ **Output:**
390
+ ```
391
+ ─────────────── Phase 2 ────────────────
392
+ ```
393
+
394
+ ### Mermaid diagrams
395
+
396
+ Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii). Use standard mermaid fenced code blocks.
397
+
398
+ **Input:**
399
+ ````markdown
400
+ ```mermaid
401
+ graph LR
402
+ A[Request] --> B{Auth?}
403
+ B -->|Yes| C[Handler]
404
+ B -->|No| D[401]
405
+ ```
406
+ ````
407
+
408
+ The diagram gets rendered as ASCII art inline in the terminal output. Requires `mermaid-ascii` to be installed (it's a dependency, so it should be).
409
+
410
+
411
+ ### Nesting
412
+
413
+ Directives compose. Put a tree inside a panel, panels inside columns, callouts next to trees with a divider between sections.
414
+
415
+ **Input:**
416
+ ```markdown
417
+ :::panel{title="Deployment" color="green"}
418
+
419
+ :::columns
420
+ :::col{width="60%"}
421
+ :::tree
422
+ services/
423
+ api/ [x]
424
+ worker/ [x]
425
+ scheduler/ [!]
426
+ :::
427
+ :::
428
+ :::col{width="40%"}
429
+ :::callout{type="success"}
430
+ 2 of 3 services deployed
431
+ :::
432
+ :::
433
+ :::
434
+
435
+ :::divider{label="logs"}
436
+ :::
437
+
438
+ Last deploy: `api@v2.4.1` at 14:32 UTC
439
+ :::
440
+ ```
441
+
442
+ A bordered panel containing two columns (file tree on the left, status callout on the right), a labeled divider, and a status line. All from nested directives.
443
+
444
+
445
+ ## Standard markdown
446
+
447
+ Headings (`#` through `####`), **bold**, *italic*, `inline code`, bullet lists with nesting, numbered lists, fenced code blocks with language detection. All styled for the terminal. Works inside directives too.
448
+
449
+
450
+ ## Environment
451
+
452
+ | Variable | Effect |
453
+ |----------|--------|
454
+ | `NO_COLOR` | Disables color output ([no-color.org](https://no-color.org/)) |
455
+ | `TERM=dumb` | Raises an error (requires Unicode support) |
456
+
457
+
458
+ ## How it works
459
+
460
+ Three-stage pipeline: parse, layout, emit.
461
+
462
+ The parser splits source into directive blocks and plain markdown using a two-pass approach. Directives get extracted first, then markdown segments run through mistune v3 in AST mode. The result is a tree of typed `Block` nodes.
463
+
464
+ Layout runs two passes over the block tree. First pass propagates available width top-down — columns divide space by percentage, panels and callouts reserve 4 characters for borders and padding. Second pass calculates heights bottom-up.
465
+
466
+ Emit walks the sized tree and dispatches each block to its renderer. Each block type lives in its own module under `renderers/`, so adding a new directive means adding one file and a case in the dispatcher. Renderers produce lists of ANSI-styled lines that get joined into the final output string.
467
+
468
+ About 1,400 lines across 12 modules. No magic.
469
+
470
+
471
+ ## License
472
+
473
+ MIT