Add QR code generation to CLI and API

CLI generate command:
- --qr <file.png|jpg> to save RSA key as QR image
- --qr-ascii to print ASCII QR code to terminal

API endpoints:
- POST /generate-key-qr - generate QR from key_pem
  - Supports png, jpg, and ascii output formats
  - Uses zstd compression by default
- Added has_qrcode_write to /capabilities

Core:
- generate_qr_code() now supports jpg/jpeg output format
- New generate_qr_ascii() for terminal display

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-09 23:38:51 -05:00
parent 32842f6b73
commit 2d7fbd1e0d
3 changed files with 201 additions and 5 deletions

View File

@@ -252,17 +252,23 @@ def needs_compression(data: str) -> bool:
return not can_fit_in_qr(data, compress=False) and can_fit_in_qr(data, compress=True)
def generate_qr_code(data: str, compress: bool = False, error_correction=None) -> bytes:
def generate_qr_code(
data: str,
compress: bool = False,
error_correction=None,
output_format: str = "png",
) -> bytes:
"""
Generate a QR code PNG from string data.
Generate a QR code image from string data.
Args:
data: String data to encode
compress: Whether to compress data first
error_correction: QR error correction level (default: auto)
output_format: Image format - 'png' or 'jpg'/'jpeg'
Returns:
PNG image bytes
Image bytes in requested format
Raises:
RuntimeError: If qrcode library not available
@@ -299,11 +305,79 @@ def generate_qr_code(data: str, compress: bool = False, error_correction=None) -
img = qr.make_image(fill_color="black", back_color="white")
buf = io.BytesIO()
img.save(buf, format="PNG")
fmt = output_format.lower()
if fmt in ("jpg", "jpeg"):
# Convert to RGB for JPEG (no alpha channel)
img = img.convert("RGB")
img.save(buf, format="JPEG", quality=95)
else:
img.save(buf, format="PNG")
buf.seek(0)
return buf.getvalue()
def generate_qr_ascii(
data: str,
compress: bool = False,
invert: bool = False,
) -> str:
"""
Generate an ASCII representation of a QR code.
Uses Unicode block characters for compact display.
Args:
data: String data to encode
compress: Whether to compress data first
invert: Invert colors (white on black for dark terminals)
Returns:
ASCII string representation of QR code
Raises:
RuntimeError: If qrcode library not available
ValueError: If data too large for QR code
"""
if not HAS_QRCODE_WRITE:
raise RuntimeError("qrcode library not installed. Run: pip install qrcode[pil]")
qr_data = data
# Compress if requested
if compress:
qr_data = compress_data(data)
# Check size
if len(qr_data.encode("utf-8")) > QR_MAX_BINARY:
raise ValueError(
f"Data too large for QR code ({len(qr_data)} bytes). " f"Maximum: {QR_MAX_BINARY} bytes"
)
qr = qrcode.QRCode(
version=None,
error_correction=ERROR_CORRECT_L,
box_size=1,
border=2,
)
qr.add_data(qr_data)
qr.make(fit=True)
# Get the QR matrix
# Use print_ascii to a StringIO to capture output
import sys
from io import StringIO
old_stdout = sys.stdout
sys.stdout = StringIO()
try:
qr.print_ascii(invert=invert)
ascii_qr = sys.stdout.getvalue()
finally:
sys.stdout = old_stdout
return ascii_qr
def read_qr_code(image_data: bytes) -> str | None:
"""
Read QR code from image data.