Coverage for src/ai_shell/qr.py: 100%
16 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-03 01:05 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-03 01:05 +0000
1"""Terminal QR rendering shared by the pairing/tunnel integrations.
3Both the T3 pairing URL and the Expo tunnel URL have to be scannable from a
4phone before the terminal is handed over to an agent, so the code is drawn
5with half-block characters (two matrix rows per text row) — the same way t3's
6own CLI draws its codes, so the two outputs look identical in a terminal.
7"""
9from __future__ import annotations
12def render_qr(data: str, border: int = 2) -> str | None:
13 """Render *data* as a half-block QR code, or None if segno is missing."""
14 try:
15 import segno
16 except ImportError: # pragma: no cover - segno is a declared dependency
17 return None
19 matrix = [bytearray(row) for row in segno.make(data, error="m").matrix]
20 size = len(matrix)
22 def dark(x: int, y: int) -> bool:
23 return 0 <= x < size and 0 <= y < size and bool(matrix[y][x])
25 rows: list[str] = []
26 for y in range(-border, size + border, 2):
27 row = ""
28 for x in range(-border, size + border):
29 top, bottom = dark(x, y), dark(x, y + 1)
30 row += "█" if top and bottom else "▀" if top else "▄" if bottom else " "
31 rows.append(row)
32 return "\n".join(rows)