Cooler brand palette, compact CST-agent home with tagline and framed composer, session topbar (model + chair chip), sidebar off by default. Screenshot pipeline in tools/tui_shot.py; docs/screenshots for before/after. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
4.1 KiB
Python
154 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""ANSI pane dump → HTML → PNG (chrome headless). Usage: tui_shot.py in.ansi out.png"""
|
|
from __future__ import annotations
|
|
|
|
import html
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SPECIAL = {
|
|
75: "#6ea8fe",
|
|
74: "#61b8c7",
|
|
216: "#faa06b",
|
|
233: "#0e0f12",
|
|
234: "#14161e",
|
|
235: "#1a1c24",
|
|
244: "#80848f",
|
|
250: "#bcbcbc",
|
|
252: "#d1d4d9",
|
|
255: "#ffffff",
|
|
242: "#6a6a6a",
|
|
114: "#66bf73",
|
|
167: "#d95252",
|
|
157: "#afff87",
|
|
22: "#005f00",
|
|
203: "#ff5f5f",
|
|
52: "#5f0000",
|
|
}
|
|
|
|
|
|
def ansi_to_html(text: str) -> str:
|
|
cmap = {
|
|
30: "#3c3c46",
|
|
31: "#e65a5a",
|
|
32: "#5ad278",
|
|
33: "#faa06b",
|
|
34: "#6ea8fe",
|
|
35: "#d282d2",
|
|
36: "#5ad2e6",
|
|
37: "#bcbcbc",
|
|
90: "#80848f",
|
|
91: "#ff8282",
|
|
92: "#82ff96",
|
|
93: "#ffeb82",
|
|
94: "#96beff",
|
|
95: "#eba0eb",
|
|
96: "#82ebff",
|
|
97: "#ffffff",
|
|
}
|
|
out: list[str] = []
|
|
i = 0
|
|
bold = dim = False
|
|
fg = "#d1d4d9"
|
|
open_span = False
|
|
|
|
def close() -> None:
|
|
nonlocal open_span
|
|
if open_span:
|
|
out.append("</span>")
|
|
open_span = False
|
|
|
|
def reopen() -> None:
|
|
nonlocal open_span
|
|
close()
|
|
styles = [f"color:{fg}"]
|
|
if bold:
|
|
styles.append("font-weight:700")
|
|
if dim:
|
|
styles.append("opacity:.65")
|
|
out.append(f'<span style="{";".join(styles)}">')
|
|
open_span = True
|
|
|
|
reopen()
|
|
while i < len(text):
|
|
if text[i] == "\x1b" and i + 1 < len(text) and text[i + 1] == "[":
|
|
j = i + 2
|
|
while j < len(text) and text[j] != "m":
|
|
j += 1
|
|
seq = text[i + 2 : j]
|
|
i = j + 1 if j < len(text) else len(text)
|
|
parts = [int(p) for p in seq.split(";") if p.isdigit()] if seq else [0]
|
|
k = 0
|
|
while k < len(parts):
|
|
c = parts[k]
|
|
if c == 0:
|
|
bold = dim = False
|
|
fg = "#d1d4d9"
|
|
elif c == 1:
|
|
bold = True
|
|
elif c == 2:
|
|
dim = True
|
|
elif c == 22:
|
|
bold = dim = False
|
|
elif c == 38 and k + 2 < len(parts) and parts[k + 1] == 5:
|
|
fg = SPECIAL.get(parts[k + 2], fg)
|
|
k += 2
|
|
elif c in cmap:
|
|
fg = cmap[c]
|
|
elif c == 39:
|
|
fg = "#d1d4d9"
|
|
k += 1
|
|
reopen()
|
|
continue
|
|
if text[i] == "\n":
|
|
out.append("\n")
|
|
i += 1
|
|
continue
|
|
if text[i] == "\r":
|
|
i += 1
|
|
continue
|
|
out.append(html.escape(text[i]))
|
|
i += 1
|
|
close()
|
|
body = "".join(out)
|
|
return f"""<!doctype html><meta charset=utf-8>
|
|
<style>
|
|
html,body{{margin:0;background:#07080a}}
|
|
pre{{margin:0;padding:18px 22px;background:#0e0f12;color:#d1d4d9;
|
|
font:14px/1.35 "JetBrains Mono","Cascadia Mono",Menlo,Consolas,monospace;
|
|
white-space:pre; min-height:100vh; letter-spacing:0.01em}}
|
|
</style><pre>{body}</pre>"""
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 3:
|
|
print("usage: tui_shot.py in.ansi out.png", file=sys.stderr)
|
|
return 2
|
|
ansi = Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace")
|
|
html_path = Path(sys.argv[2]).with_suffix(".html")
|
|
html_path.write_text(ansi_to_html(ansi), encoding="utf-8")
|
|
png = Path(sys.argv[2])
|
|
chrome = "google-chrome"
|
|
subprocess.run(
|
|
[
|
|
chrome,
|
|
"--headless=new",
|
|
"--disable-gpu",
|
|
"--no-sandbox",
|
|
"--hide-scrollbars",
|
|
"--window-size=1280,800",
|
|
f"--screenshot={png}",
|
|
html_path.resolve().as_uri(),
|
|
],
|
|
check=False,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
print(png, png.stat().st_size if png.exists() else 0)
|
|
return 0 if png.exists() else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|