simultex 0.1.0__py3-none-any.whl
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.
- simultex/__init__.py +3 -0
- simultex/assets/simultex.ansi +6 -0
- simultex/browser.py +329 -0
- simultex/browser_assets/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
- simultex/browser_assets/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
- simultex/browser_assets/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
- simultex/browser_assets/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
- simultex/browser_assets/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
- simultex/browser_assets/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
- simultex/browser_assets/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
- simultex/browser_assets/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- simultex/browser_assets/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
- simultex/browser_assets/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- simultex/browser_assets/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
- simultex/browser_assets/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
- simultex/browser_assets/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- simultex/browser_assets/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
- simultex/browser_assets/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- simultex/browser_assets/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
- simultex/browser_assets/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
- simultex/browser_assets/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
- simultex/browser_assets/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- simultex/browser_assets/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
- simultex/browser_assets/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- simultex/browser_assets/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
- simultex/browser_assets/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
- simultex/browser_assets/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- simultex/browser_assets/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
- simultex/browser_assets/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
- simultex/browser_assets/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- simultex/browser_assets/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
- simultex/browser_assets/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- simultex/browser_assets/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
- simultex/browser_assets/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
- simultex/browser_assets/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
- simultex/browser_assets/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- simultex/browser_assets/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
- simultex/browser_assets/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
- simultex/browser_assets/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- simultex/browser_assets/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
- simultex/browser_assets/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- simultex/browser_assets/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
- simultex/browser_assets/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
- simultex/browser_assets/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
- simultex/browser_assets/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- simultex/browser_assets/app.css +1 -0
- simultex/browser_assets/app.js +3683 -0
- simultex/browser_assets/index.html +22 -0
- simultex/cli.py +369 -0
- simultex/codex_history.py +330 -0
- simultex/model_proxy.py +961 -0
- simultex/protocol.py +218 -0
- simultex/pty_proxy.py +166 -0
- simultex/render.py +234 -0
- simultex/screen.py +333 -0
- simultex/stream.py +276 -0
- simultex-0.1.0.dist-info/METADATA +221 -0
- simultex-0.1.0.dist-info/RECORD +79 -0
- simultex-0.1.0.dist-info/WHEEL +4 -0
- simultex-0.1.0.dist-info/entry_points.txt +2 -0
- simultex-0.1.0.dist-info/licenses/LICENSE +21 -0
simultex/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[38;2;0;255;65m [38;2;0;253;64m█[38;2;0;252;64m█[38;2;0;250;63m█[38;2;0;249;62m█[38;2;0;247;62m█[38;2;0;246;61m█[38;2;0;244;60m█[38;2;0;242;60m╗[38;2;0;241;59m [38;2;0;239;58m█[38;2;0;238;58m█[38;2;0;236;57m╗[38;2;0;234;56m [38;2;0;233;56m█[38;2;0;231;55m█[38;2;0;230;54m█[38;2;0;228;54m╗[38;2;0;227;53m [38;2;0;225;52m [38;2;0;223;51m [38;2;0;222;51m█[38;2;0;220;50m█[38;2;0;219;49m█[38;2;0;217;49m╗[38;2;0;216;48m [38;2;0;214;47m█[38;2;0;212;47m█[38;2;0;211;46m╗[38;2;0;209;45m [38;2;0;208;45m [38;2;0;206;44m [38;2;0;205;43m█[38;2;0;203;43m█[38;2;0;201;42m╗[38;2;0;200;41m [38;2;0;198;41m█[38;2;0;197;40m█[38;2;0;195;39m╗[38;2;0;193;39m [38;2;0;192;38m [38;2;0;190;37m [38;2;0;189;37m [38;2;0;187;36m [38;2;0;186;35m [38;2;0;184;35m█[38;2;0;182;34m█[38;2;0;181;33m█[38;2;0;179;33m█[38;2;0;178;32m█[38;2;0;176;31m█[38;2;0;175;31m█[38;2;0;173;30m█[38;2;0;171;29m╗[38;2;0;170;28m [38;2;0;168;28m█[38;2;0;167;27m█[38;2;0;165;26m█[38;2;0;164;26m█[38;2;0;162;25m█[38;2;0;160;24m█[38;2;0;159;24m█[38;2;0;157;23m╗[38;2;0;156;22m [38;2;0;154;22m█[38;2;0;152;21m█[38;2;0;151;20m╗[38;2;0;149;20m [38;2;0;148;19m [38;2;0;146;18m█[38;2;0;145;18m█[38;2;0;143;17m╗[39m
|
|
2
|
+
[38;2;0;255;65m [38;2;0;253;64m█[38;2;0;252;64m█[38;2;0;250;63m╔[38;2;0;249;62m═[38;2;0;247;62m═[38;2;0;246;61m═[38;2;0;244;60m═[38;2;0;242;60m╝[38;2;0;241;59m [38;2;0;239;58m█[38;2;0;238;58m█[38;2;0;236;57m║[38;2;0;234;56m [38;2;0;233;56m█[38;2;0;231;55m█[38;2;0;230;54m█[38;2;0;228;54m█[38;2;0;227;53m╗[38;2;0;225;52m [38;2;0;223;51m█[38;2;0;222;51m█[38;2;0;220;50m█[38;2;0;219;49m█[38;2;0;217;49m║[38;2;0;216;48m [38;2;0;214;47m█[38;2;0;212;47m█[38;2;0;211;46m║[38;2;0;209;45m [38;2;0;208;45m [38;2;0;206;44m [38;2;0;205;43m█[38;2;0;203;43m█[38;2;0;201;42m║[38;2;0;200;41m [38;2;0;198;41m█[38;2;0;197;40m█[38;2;0;195;39m║[38;2;0;193;39m [38;2;0;192;38m [38;2;0;190;37m [38;2;0;189;37m [38;2;0;187;36m [38;2;0;186;35m [38;2;0;184;35m╚[38;2;0;182;34m═[38;2;0;181;33m═[38;2;0;179;33m█[38;2;0;178;32m█[38;2;0;176;31m╔[38;2;0;175;31m═[38;2;0;173;30m═[38;2;0;171;29m╝[38;2;0;170;28m [38;2;0;168;28m█[38;2;0;167;27m█[38;2;0;165;26m╔[38;2;0;164;26m═[38;2;0;162;25m═[38;2;0;160;24m═[38;2;0;159;24m═[38;2;0;157;23m╝[38;2;0;156;22m [38;2;0;154;22m╚[38;2;0;152;21m█[38;2;0;151;20m█[38;2;0;149;20m╗[38;2;0;148;19m█[38;2;0;146;18m█[38;2;0;145;18m╔[38;2;0;143;17m╝[39m
|
|
3
|
+
[38;2;0;255;65m [38;2;0;253;64m█[38;2;0;252;64m█[38;2;0;250;63m█[38;2;0;249;62m█[38;2;0;247;62m█[38;2;0;246;61m█[38;2;0;244;60m█[38;2;0;242;60m╗[38;2;0;241;59m [38;2;0;239;58m█[38;2;0;238;58m█[38;2;0;236;57m║[38;2;0;234;56m [38;2;0;233;56m█[38;2;0;231;55m█[38;2;0;230;54m╔[38;2;0;228;54m█[38;2;0;227;53m█[38;2;0;225;52m█[38;2;0;223;51m█[38;2;0;222;51m╔[38;2;0;220;50m█[38;2;0;219;49m█[38;2;0;217;49m║[38;2;0;216;48m [38;2;0;214;47m█[38;2;0;212;47m█[38;2;0;211;46m║[38;2;0;209;45m [38;2;0;208;45m [38;2;0;206;44m [38;2;0;205;43m█[38;2;0;203;43m█[38;2;0;201;42m║[38;2;0;200;41m [38;2;0;198;41m█[38;2;0;197;40m█[38;2;0;195;39m║[38;2;0;193;39m [38;2;0;192;38m [38;2;0;190;37m [38;2;0;189;37m [38;2;0;187;36m [38;2;0;186;35m [38;2;0;184;35m [38;2;0;182;34m [38;2;0;181;33m [38;2;0;179;33m█[38;2;0;178;32m█[38;2;0;176;31m║[38;2;0;175;31m [38;2;0;173;30m [38;2;0;171;29m [38;2;0;170;28m [38;2;0;168;28m█[38;2;0;167;27m█[38;2;0;165;26m█[38;2;0;164;26m█[38;2;0;162;25m█[38;2;0;160;24m╗[38;2;0;159;24m [38;2;0;157;23m [38;2;0;156;22m [38;2;0;154;22m [38;2;0;152;21m╚[38;2;0;151;20m█[38;2;0;149;20m█[38;2;0;148;19m█[38;2;0;146;18m╔[38;2;0;145;18m╝[38;2;0;143;17m [39m
|
|
4
|
+
[38;2;0;255;65m [38;2;0;253;64m╚[38;2;0;252;64m═[38;2;0;250;63m═[38;2;0;249;62m═[38;2;0;247;62m═[38;2;0;246;61m█[38;2;0;244;60m█[38;2;0;242;60m║[38;2;0;241;59m [38;2;0;239;58m█[38;2;0;238;58m█[38;2;0;236;57m║[38;2;0;234;56m [38;2;0;233;56m█[38;2;0;231;55m█[38;2;0;230;54m║[38;2;0;228;54m╚[38;2;0;227;53m█[38;2;0;225;52m█[38;2;0;223;51m╔[38;2;0;222;51m╝[38;2;0;220;50m█[38;2;0;219;49m█[38;2;0;217;49m║[38;2;0;216;48m [38;2;0;214;47m█[38;2;0;212;47m█[38;2;0;211;46m║[38;2;0;209;45m [38;2;0;208;45m [38;2;0;206;44m [38;2;0;205;43m█[38;2;0;203;43m█[38;2;0;201;42m║[38;2;0;200;41m [38;2;0;198;41m█[38;2;0;197;40m█[38;2;0;195;39m║[38;2;0;193;39m [38;2;0;192;38m [38;2;0;190;37m [38;2;0;189;37m [38;2;0;187;36m [38;2;0;186;35m [38;2;0;184;35m [38;2;0;182;34m [38;2;0;181;33m [38;2;0;179;33m█[38;2;0;178;32m█[38;2;0;176;31m║[38;2;0;175;31m [38;2;0;173;30m [38;2;0;171;29m [38;2;0;170;28m [38;2;0;168;28m█[38;2;0;167;27m█[38;2;0;165;26m╔[38;2;0;164;26m═[38;2;0;162;25m═[38;2;0;160;24m╝[38;2;0;159;24m [38;2;0;157;23m [38;2;0;156;22m [38;2;0;154;22m [38;2;0;152;21m█[38;2;0;151;20m█[38;2;0;149;20m╔[38;2;0;148;19m█[38;2;0;146;18m█[38;2;0;145;18m╗[38;2;0;143;17m [39m
|
|
5
|
+
[38;2;0;255;65m [38;2;0;253;64m█[38;2;0;252;64m█[38;2;0;250;63m█[38;2;0;249;62m█[38;2;0;247;62m█[38;2;0;246;61m█[38;2;0;244;60m█[38;2;0;242;60m║[38;2;0;241;59m [38;2;0;239;58m█[38;2;0;238;58m█[38;2;0;236;57m║[38;2;0;234;56m [38;2;0;233;56m█[38;2;0;231;55m█[38;2;0;230;54m║[38;2;0;228;54m [38;2;0;227;53m╚[38;2;0;225;52m═[38;2;0;223;51m╝[38;2;0;222;51m [38;2;0;220;50m█[38;2;0;219;49m█[38;2;0;217;49m║[38;2;0;216;48m [38;2;0;214;47m╚[38;2;0;212;47m█[38;2;0;211;46m█[38;2;0;209;45m█[38;2;0;208;45m█[38;2;0;206;44m█[38;2;0;205;43m█[38;2;0;203;43m╔[38;2;0;201;42m╝[38;2;0;200;41m [38;2;0;198;41m█[38;2;0;197;40m█[38;2;0;195;39m█[38;2;0;193;39m█[38;2;0;192;38m█[38;2;0;190;37m█[38;2;0;189;37m█[38;2;0;187;36m╗[38;2;0;186;35m [38;2;0;184;35m [38;2;0;182;34m [38;2;0;181;33m [38;2;0;179;33m█[38;2;0;178;32m█[38;2;0;176;31m║[38;2;0;175;31m [38;2;0;173;30m [38;2;0;171;29m [38;2;0;170;28m [38;2;0;168;28m█[38;2;0;167;27m█[38;2;0;165;26m█[38;2;0;164;26m█[38;2;0;162;25m█[38;2;0;160;24m█[38;2;0;159;24m█[38;2;0;157;23m╗[38;2;0;156;22m [38;2;0;154;22m█[38;2;0;152;21m█[38;2;0;151;20m╔[38;2;0;149;20m╝[38;2;0;148;19m [38;2;0;146;18m█[38;2;0;145;18m█[38;2;0;143;17m╗[39m
|
|
6
|
+
[38;2;0;255;65m [38;2;0;253;64m╚[38;2;0;252;64m═[38;2;0;250;63m═[38;2;0;249;62m═[38;2;0;247;62m═[38;2;0;246;61m═[38;2;0;244;60m═[38;2;0;242;60m╝[38;2;0;241;59m [38;2;0;239;58m╚[38;2;0;238;58m═[38;2;0;236;57m╝[38;2;0;234;56m [38;2;0;233;56m╚[38;2;0;231;55m═[38;2;0;230;54m╝[38;2;0;228;54m [38;2;0;227;53m [38;2;0;225;52m [38;2;0;223;51m [38;2;0;222;51m [38;2;0;220;50m╚[38;2;0;219;49m═[38;2;0;217;49m╝[38;2;0;216;48m [38;2;0;214;47m [38;2;0;212;47m╚[38;2;0;211;46m═[38;2;0;209;45m═[38;2;0;208;45m═[38;2;0;206;44m═[38;2;0;205;43m═[38;2;0;203;43m╝[38;2;0;201;42m [38;2;0;200;41m [38;2;0;198;41m╚[38;2;0;197;40m═[38;2;0;195;39m═[38;2;0;193;39m═[38;2;0;192;38m═[38;2;0;190;37m═[38;2;0;189;37m═[38;2;0;187;36m╝[38;2;0;186;35m [38;2;0;184;35m [38;2;0;182;34m [38;2;0;181;33m [38;2;0;179;33m╚[38;2;0;178;32m═[38;2;0;176;31m╝[38;2;0;175;31m [38;2;0;173;30m [38;2;0;171;29m [38;2;0;170;28m [38;2;0;168;28m╚[38;2;0;167;27m═[38;2;0;165;26m═[38;2;0;164;26m═[38;2;0;162;25m═[38;2;0;160;24m═[38;2;0;159;24m═[38;2;0;157;23m╝[38;2;0;156;22m [38;2;0;154;22m╚[38;2;0;152;21m═[38;2;0;151;20m╝[38;2;0;149;20m [38;2;0;148;19m [38;2;0;146;18m╚[38;2;0;145;18m═[38;2;0;143;17m╝[39m[0m
|
simultex/browser.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""Loopback-only browser companion for mirroring a terminal session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import html
|
|
7
|
+
import json
|
|
8
|
+
import secrets
|
|
9
|
+
import sys
|
|
10
|
+
import threading
|
|
11
|
+
from collections import deque
|
|
12
|
+
from dataclasses import dataclass, replace
|
|
13
|
+
from http import HTTPStatus
|
|
14
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from queue import Empty, Full, Queue
|
|
17
|
+
from urllib.parse import parse_qs, urlsplit
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_ASSET_ROOT = Path(__file__).with_name("browser_assets")
|
|
21
|
+
_MAX_HISTORY_BYTES = 8 * 1024 * 1024
|
|
22
|
+
_CLIENT_QUEUE_SIZE = 512
|
|
23
|
+
_MAX_SESSION_IMAGE_BYTES = 64 * 1024 * 1024
|
|
24
|
+
_SESSION_IMAGE_TYPES = {
|
|
25
|
+
".avif": "image/avif",
|
|
26
|
+
".bmp": "image/bmp",
|
|
27
|
+
".gif": "image/gif",
|
|
28
|
+
".jpeg": "image/jpeg",
|
|
29
|
+
".jpg": "image/jpeg",
|
|
30
|
+
".png": "image/png",
|
|
31
|
+
".webp": "image/webp",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _QuietThreadingHTTPServer(ThreadingHTTPServer):
|
|
36
|
+
def handle_error(self, request: object, client_address: object) -> None:
|
|
37
|
+
error = sys.exc_info()[1]
|
|
38
|
+
if isinstance(error, (BrokenPipeError, ConnectionResetError)):
|
|
39
|
+
return
|
|
40
|
+
super().handle_error(request, client_address)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class _Event:
|
|
45
|
+
name: str
|
|
46
|
+
data: str
|
|
47
|
+
size: int
|
|
48
|
+
sequence: int = 0
|
|
49
|
+
|
|
50
|
+
def encode(self) -> bytes:
|
|
51
|
+
return (
|
|
52
|
+
f"id: {self.sequence}\nevent: {self.name}\ndata: {self.data}\n\n"
|
|
53
|
+
).encode("utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class _EventBus:
|
|
57
|
+
def __init__(self, history_limit: int = _MAX_HISTORY_BYTES):
|
|
58
|
+
self._history_limit = history_limit
|
|
59
|
+
self._history: deque[_Event] = deque()
|
|
60
|
+
self._history_size = 0
|
|
61
|
+
self._clients: set[Queue[_Event | None]] = set()
|
|
62
|
+
self._lock = threading.Lock()
|
|
63
|
+
self._next_sequence = 1
|
|
64
|
+
|
|
65
|
+
def publish(self, event: _Event) -> None:
|
|
66
|
+
with self._lock:
|
|
67
|
+
event = replace(event, sequence=self._next_sequence)
|
|
68
|
+
self._next_sequence += 1
|
|
69
|
+
self._history.append(event)
|
|
70
|
+
self._history_size += event.size
|
|
71
|
+
while self._history and self._history_size > self._history_limit:
|
|
72
|
+
removed = self._history.popleft()
|
|
73
|
+
self._history_size -= removed.size
|
|
74
|
+
stale: list[Queue[_Event | None]] = []
|
|
75
|
+
for client in self._clients:
|
|
76
|
+
try:
|
|
77
|
+
client.put_nowait(event)
|
|
78
|
+
except Full:
|
|
79
|
+
stale.append(client)
|
|
80
|
+
for client in stale:
|
|
81
|
+
self._clients.discard(client)
|
|
82
|
+
try:
|
|
83
|
+
client.get_nowait()
|
|
84
|
+
client.put_nowait(None)
|
|
85
|
+
except (Empty, Full):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def subscribe(
|
|
89
|
+
self, after_sequence: int = 0
|
|
90
|
+
) -> tuple[list[_Event], Queue[_Event | None]]:
|
|
91
|
+
client: Queue[_Event | None] = Queue(maxsize=_CLIENT_QUEUE_SIZE)
|
|
92
|
+
with self._lock:
|
|
93
|
+
history = [event for event in self._history if event.sequence > after_sequence]
|
|
94
|
+
self._clients.add(client)
|
|
95
|
+
return history, client
|
|
96
|
+
|
|
97
|
+
def unsubscribe(self, client: Queue[_Event | None]) -> None:
|
|
98
|
+
with self._lock:
|
|
99
|
+
self._clients.discard(client)
|
|
100
|
+
|
|
101
|
+
def close(self) -> None:
|
|
102
|
+
with self._lock:
|
|
103
|
+
clients = list(self._clients)
|
|
104
|
+
self._clients.clear()
|
|
105
|
+
for client in clients:
|
|
106
|
+
try:
|
|
107
|
+
client.put_nowait(None)
|
|
108
|
+
except Full:
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BrowserCompanion:
|
|
113
|
+
"""Serve a read-only rich transcript derived from raw PTY output."""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
port: int = 0,
|
|
118
|
+
*,
|
|
119
|
+
parse_dollars: bool = True,
|
|
120
|
+
content_root: Path | None = None,
|
|
121
|
+
):
|
|
122
|
+
self.token = secrets.token_urlsafe(24)
|
|
123
|
+
self.parse_dollars = parse_dollars
|
|
124
|
+
self.content_root = (content_root or Path.cwd()).resolve()
|
|
125
|
+
self._events = _EventBus()
|
|
126
|
+
handler = self._handler_type()
|
|
127
|
+
self._server = _QuietThreadingHTTPServer(("127.0.0.1", port), handler)
|
|
128
|
+
self._server.daemon_threads = True
|
|
129
|
+
self._thread = threading.Thread(
|
|
130
|
+
target=self._server.serve_forever,
|
|
131
|
+
name="simultex-browser",
|
|
132
|
+
daemon=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def port(self) -> int:
|
|
137
|
+
return int(self._server.server_address[1])
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def url(self) -> str:
|
|
141
|
+
return f"http://127.0.0.1:{self.port}/?token={self.token}"
|
|
142
|
+
|
|
143
|
+
def start(self) -> None:
|
|
144
|
+
self._thread.start()
|
|
145
|
+
|
|
146
|
+
def feed(self, data: bytes) -> None:
|
|
147
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
148
|
+
self._events.publish(_Event("output", encoded, len(data)))
|
|
149
|
+
|
|
150
|
+
def resize(self, columns: int, rows: int) -> None:
|
|
151
|
+
payload = json.dumps({"columns": columns, "rows": rows}, separators=(",", ":"))
|
|
152
|
+
self._events.publish(_Event("resize", payload, len(payload)))
|
|
153
|
+
|
|
154
|
+
def api_event(self, payload: dict[str, object]) -> None:
|
|
155
|
+
"""Publish a normalized model event without changing PTY rendering."""
|
|
156
|
+
encoded = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
|
157
|
+
self._events.publish(_Event("api", encoded, len(encoded.encode("utf-8"))))
|
|
158
|
+
|
|
159
|
+
def close(self) -> None:
|
|
160
|
+
self._events.close()
|
|
161
|
+
self._server.shutdown()
|
|
162
|
+
self._server.server_close()
|
|
163
|
+
self._thread.join(timeout=2)
|
|
164
|
+
|
|
165
|
+
def __enter__(self) -> BrowserCompanion:
|
|
166
|
+
self.start()
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def __exit__(self, _type: object, _value: object, _traceback: object) -> None:
|
|
170
|
+
self.close()
|
|
171
|
+
|
|
172
|
+
def _handler_type(self) -> type[BaseHTTPRequestHandler]:
|
|
173
|
+
companion = self
|
|
174
|
+
|
|
175
|
+
class Handler(BaseHTTPRequestHandler):
|
|
176
|
+
server_version = "simultex-browser"
|
|
177
|
+
|
|
178
|
+
def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API
|
|
179
|
+
parsed = urlsplit(self.path)
|
|
180
|
+
if parsed.path == "/":
|
|
181
|
+
self._serve_index()
|
|
182
|
+
return
|
|
183
|
+
if parsed.path == "/events":
|
|
184
|
+
self._serve_events(parsed.query)
|
|
185
|
+
return
|
|
186
|
+
if parsed.path == "/session-image":
|
|
187
|
+
self._serve_session_image(parsed.query)
|
|
188
|
+
return
|
|
189
|
+
self._serve_asset(parsed.path)
|
|
190
|
+
|
|
191
|
+
def log_message(self, _format: str, *_args: object) -> None:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
def _headers(self, content_type: str, length: int | None = None) -> None:
|
|
195
|
+
self.send_header("Content-Type", content_type)
|
|
196
|
+
self.send_header("Cache-Control", "no-store")
|
|
197
|
+
self.send_header("X-Content-Type-Options", "nosniff")
|
|
198
|
+
self.send_header("Referrer-Policy", "no-referrer")
|
|
199
|
+
self.send_header("Cross-Origin-Resource-Policy", "same-origin")
|
|
200
|
+
self.send_header(
|
|
201
|
+
"Content-Security-Policy",
|
|
202
|
+
"default-src 'self'; connect-src 'self'; script-src 'self'; "
|
|
203
|
+
"style-src 'self' 'unsafe-inline'; font-src 'self' data:; "
|
|
204
|
+
"img-src 'self' data: blob: http: https:",
|
|
205
|
+
)
|
|
206
|
+
if length is not None:
|
|
207
|
+
self.send_header("Content-Length", str(length))
|
|
208
|
+
|
|
209
|
+
def _serve_index(self) -> None:
|
|
210
|
+
try:
|
|
211
|
+
template = (_ASSET_ROOT / "index.html").read_text(encoding="utf-8")
|
|
212
|
+
except OSError:
|
|
213
|
+
self.send_error(
|
|
214
|
+
HTTPStatus.SERVICE_UNAVAILABLE,
|
|
215
|
+
"browser assets are missing; run npm install && npm run build in browser-ui",
|
|
216
|
+
)
|
|
217
|
+
return
|
|
218
|
+
config = html.escape(
|
|
219
|
+
json.dumps({"parseDollars": companion.parse_dollars}),
|
|
220
|
+
quote=True,
|
|
221
|
+
)
|
|
222
|
+
body = template.replace("__SIMULTEX_CONFIG__", config).encode("utf-8")
|
|
223
|
+
self.send_response(HTTPStatus.OK)
|
|
224
|
+
self._headers("text/html; charset=utf-8", len(body))
|
|
225
|
+
self.end_headers()
|
|
226
|
+
self.wfile.write(body)
|
|
227
|
+
|
|
228
|
+
def _serve_session_image(self, query: str) -> None:
|
|
229
|
+
values = parse_qs(query)
|
|
230
|
+
supplied = values.get("token", [""])[0]
|
|
231
|
+
if not secrets.compare_digest(supplied, companion.token):
|
|
232
|
+
self.send_error(HTTPStatus.FORBIDDEN)
|
|
233
|
+
return
|
|
234
|
+
requested = values.get("path", [""])[0]
|
|
235
|
+
if not requested:
|
|
236
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
237
|
+
return
|
|
238
|
+
candidate = Path(requested)
|
|
239
|
+
if not candidate.is_absolute():
|
|
240
|
+
candidate = companion.content_root / candidate
|
|
241
|
+
try:
|
|
242
|
+
path = candidate.resolve(strict=True)
|
|
243
|
+
path.relative_to(companion.content_root)
|
|
244
|
+
size = path.stat().st_size
|
|
245
|
+
except (OSError, ValueError):
|
|
246
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
247
|
+
return
|
|
248
|
+
content_type = _SESSION_IMAGE_TYPES.get(path.suffix.lower())
|
|
249
|
+
if not path.is_file() or content_type is None or size > _MAX_SESSION_IMAGE_BYTES:
|
|
250
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
251
|
+
return
|
|
252
|
+
try:
|
|
253
|
+
body = path.read_bytes()
|
|
254
|
+
except OSError:
|
|
255
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
256
|
+
return
|
|
257
|
+
self.send_response(HTTPStatus.OK)
|
|
258
|
+
self._headers(content_type, len(body))
|
|
259
|
+
self.end_headers()
|
|
260
|
+
self.wfile.write(body)
|
|
261
|
+
|
|
262
|
+
def _serve_asset(self, request_path: str) -> None:
|
|
263
|
+
name = request_path.removeprefix("/")
|
|
264
|
+
if not name or Path(name).name != name:
|
|
265
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
266
|
+
return
|
|
267
|
+
path = _ASSET_ROOT / name
|
|
268
|
+
types = {
|
|
269
|
+
".js": "text/javascript; charset=utf-8",
|
|
270
|
+
".css": "text/css; charset=utf-8",
|
|
271
|
+
".woff2": "font/woff2",
|
|
272
|
+
".woff": "font/woff",
|
|
273
|
+
".ttf": "font/ttf",
|
|
274
|
+
}
|
|
275
|
+
content_type = types.get(path.suffix)
|
|
276
|
+
if content_type is None:
|
|
277
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
278
|
+
return
|
|
279
|
+
try:
|
|
280
|
+
body = path.read_bytes()
|
|
281
|
+
except OSError:
|
|
282
|
+
self.send_error(HTTPStatus.NOT_FOUND)
|
|
283
|
+
return
|
|
284
|
+
self.send_response(HTTPStatus.OK)
|
|
285
|
+
self._headers(content_type, len(body))
|
|
286
|
+
self.end_headers()
|
|
287
|
+
self.wfile.write(body)
|
|
288
|
+
|
|
289
|
+
def _serve_events(self, query: str) -> None:
|
|
290
|
+
supplied = parse_qs(query).get("token", [""])[0]
|
|
291
|
+
if not secrets.compare_digest(supplied, companion.token):
|
|
292
|
+
self.send_error(HTTPStatus.FORBIDDEN)
|
|
293
|
+
return
|
|
294
|
+
origin = self.headers.get("Origin")
|
|
295
|
+
expected = f"http://127.0.0.1:{companion.port}"
|
|
296
|
+
if origin not in {None, expected}:
|
|
297
|
+
self.send_error(HTTPStatus.FORBIDDEN)
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
after_sequence = max(int(self.headers.get("Last-Event-ID", "0")), 0)
|
|
302
|
+
except ValueError:
|
|
303
|
+
after_sequence = 0
|
|
304
|
+
history, events = companion._events.subscribe(after_sequence)
|
|
305
|
+
try:
|
|
306
|
+
self.send_response(HTTPStatus.OK)
|
|
307
|
+
self._headers("text/event-stream; charset=utf-8")
|
|
308
|
+
self.send_header("Connection", "keep-alive")
|
|
309
|
+
self.end_headers()
|
|
310
|
+
for event in history:
|
|
311
|
+
self.wfile.write(event.encode())
|
|
312
|
+
self.wfile.flush()
|
|
313
|
+
while True:
|
|
314
|
+
try:
|
|
315
|
+
event = events.get(timeout=15)
|
|
316
|
+
except Empty:
|
|
317
|
+
self.wfile.write(b": keepalive\n\n")
|
|
318
|
+
self.wfile.flush()
|
|
319
|
+
continue
|
|
320
|
+
if event is None:
|
|
321
|
+
return
|
|
322
|
+
self.wfile.write(event.encode())
|
|
323
|
+
self.wfile.flush()
|
|
324
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
325
|
+
return
|
|
326
|
+
finally:
|
|
327
|
+
companion._events.unsubscribe(events)
|
|
328
|
+
|
|
329
|
+
return Handler
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:KaTeX_AMS;src:url("./KaTeX_AMS-Regular-U6PRYMIZ.woff2") format("woff2"),url("./KaTeX_AMS-Regular-CYEKBG2K.woff") format("woff"),url("./KaTeX_AMS-Regular-JKX5W2C4.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Caligraphic;src:url("./KaTeX_Caligraphic-Bold-5QL5CMTE.woff2") format("woff2"),url("./KaTeX_Caligraphic-Bold-WZ3QSGD3.woff") format("woff"),url("./KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf") format("truetype");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Caligraphic;src:url("./KaTeX_Caligraphic-Regular-KX5MEWCF.woff2") format("woff2"),url("./KaTeX_Caligraphic-Regular-3LKEU76G.woff") format("woff"),url("./KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Fraktur;src:url("./KaTeX_Fraktur-Bold-2QVFK6NQ.woff2") format("woff2"),url("./KaTeX_Fraktur-Bold-T4SWXBMT.woff") format("woff"),url("./KaTeX_Fraktur-Bold-WGHVTYOR.ttf") format("truetype");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Fraktur;src:url("./KaTeX_Fraktur-Regular-2PEIFJSJ.woff2") format("woff2"),url("./KaTeX_Fraktur-Regular-PQMHCIK6.woff") format("woff"),url("./KaTeX_Fraktur-Regular-5U4OPH2X.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Main;src:url("./KaTeX_Main-Bold-YP5VVQRP.woff2") format("woff2"),url("./KaTeX_Main-Bold-2GA4IZIN.woff") format("woff"),url("./KaTeX_Main-Bold-W5FBVCZM.ttf") format("truetype");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Main;src:url("./KaTeX_Main-BoldItalic-N4V3DX7S.woff2") format("woff2"),url("./KaTeX_Main-BoldItalic-4P4C7HJH.woff") format("woff"),url("./KaTeX_Main-BoldItalic-ODMLBJJQ.ttf") format("truetype");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:KaTeX_Main;src:url("./KaTeX_Main-Italic-RELBIK7M.woff2") format("woff2"),url("./KaTeX_Main-Italic-SASNQFN2.woff") format("woff"),url("./KaTeX_Main-Italic-I43T2HSR.ttf") format("truetype");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:KaTeX_Main;src:url("./KaTeX_Main-Regular-ARRPAO67.woff2") format("woff2"),url("./KaTeX_Main-Regular-P5I74A2A.woff") format("woff"),url("./KaTeX_Main-Regular-W74P5G27.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Math;src:url("./KaTeX_Math-BoldItalic-K4WTGH3J.woff2") format("woff2"),url("./KaTeX_Math-BoldItalic-6EBV3DK5.woff") format("woff"),url("./KaTeX_Math-BoldItalic-VB447A4D.ttf") format("truetype");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:KaTeX_Math;src:url("./KaTeX_Math-Italic-6KGCHLFN.woff2") format("woff2"),url("./KaTeX_Math-Italic-KKK3USB2.woff") format("woff"),url("./KaTeX_Math-Italic-SON4MRCA.ttf") format("truetype");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:KaTeX_SansSerif;src:url("./KaTeX_SansSerif-Bold-RRNVJFFW.woff2") format("woff2"),url("./KaTeX_SansSerif-Bold-X5M5EMOD.woff") format("woff"),url("./KaTeX_SansSerif-Bold-STQ6RXC7.ttf") format("truetype");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:KaTeX_SansSerif;src:url("./KaTeX_SansSerif-Italic-HMPFTM52.woff2") format("woff2"),url("./KaTeX_SansSerif-Italic-PSN4QKYX.woff") format("woff"),url("./KaTeX_SansSerif-Italic-WTBAZBGY.ttf") format("truetype");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:KaTeX_SansSerif;src:url("./KaTeX_SansSerif-Regular-XIQ62X4E.woff2") format("woff2"),url("./KaTeX_SansSerif-Regular-OQCII6EP.woff") format("woff"),url("./KaTeX_SansSerif-Regular-2TL3USAE.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Script;src:url("./KaTeX_Script-Regular-APUWIHLP.woff2") format("woff2"),url("./KaTeX_Script-Regular-A5IFOEBS.woff") format("woff"),url("./KaTeX_Script-Regular-72OLXYNA.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Size1;src:url("./KaTeX_Size1-Regular-5LRUTBFT.woff2") format("woff2"),url("./KaTeX_Size1-Regular-4HRHTS65.woff") format("woff"),url("./KaTeX_Size1-Regular-7K6AASVL.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Size2;src:url("./KaTeX_Size2-Regular-LELKET5D.woff2") format("woff2"),url("./KaTeX_Size2-Regular-K5ZHAIS6.woff") format("woff"),url("./KaTeX_Size2-Regular-222HN3GT.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Size3;src:url("./KaTeX_Size3-Regular-WQRQ47UD.woff2") format("woff2"),url("./KaTeX_Size3-Regular-TLFPAHDE.woff") format("woff"),url("./KaTeX_Size3-Regular-UFCO6WCA.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Size4;src:url("./KaTeX_Size4-Regular-CDMV7U5C.woff2") format("woff2"),url("./KaTeX_Size4-Regular-PKMWZHNC.woff") format("woff"),url("./KaTeX_Size4-Regular-7PGNVPQK.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:KaTeX_Typewriter;src:url("./KaTeX_Typewriter-Regular-VBYJ4NRC.woff2") format("woff2"),url("./KaTeX_Typewriter-Regular-MJMFSK64.woff") format("woff"),url("./KaTeX_Typewriter-Regular-3F5K6SQ6.ttf") format("truetype");font-weight:400;font-style:normal;font-display:block}.katex{font: 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;position:relative;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important}.katex *{border-color:currentColor}.katex .katex-version:after{content:"0.16.47"}.katex .katex-mathml{position:absolute;-webkit-clip-path:inset(50%);clip-path:inset(50%);padding:0;border:0;height:1px;width:1px;overflow:hidden}.katex .katex-html>.newline{display:block}.katex .base{position:relative;display:inline-block;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-weight:700;font-style:italic}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathsfit,.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{display:inline-table;table-layout:fixed;border-collapse:collapse}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;vertical-align:bottom;position:relative}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;vertical-align:bottom;font-size:1px;width:2px;min-width:2px}.katex .vbox{display:inline-flex;flex-direction:column;align-items:baseline}.katex .hbox{display:inline-flex;flex-direction:row;width:100%}.katex .thinbox{display:inline-flex;flex-direction:row;width:0;max-width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{display:inline-block;width:100%;border-bottom-style:solid}.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .underline .underline-line,.katex .hline,.katex .hdashline,.katex .rule{min-height:1px}.katex .mspace{display:inline-block}.katex .smash{display:inline;line-height:0}.katex .llap,.katex .rlap,.katex .clap{width:0;position:relative}.katex .llap>.inner,.katex .rlap>.inner,.katex .clap>.inner{position:absolute}.katex .llap>.fix,.katex .rlap>.fix,.katex .clap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .rlap>.inner,.katex .clap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{display:inline-block;border:solid 0;position:relative}.katex .overline .overline-line,.katex .underline .underline-line,.katex .hline{display:inline-block;width:100%;border-bottom-style:solid}.katex .hdashline{display:inline-block;width:100%;border-bottom-style:dashed}.katex .sqrt>.root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .sizing.reset-size1.size1,.katex .fontsize-ensurer.reset-size1.size1{font-size:1em}.katex .sizing.reset-size1.size2,.katex .fontsize-ensurer.reset-size1.size2{font-size:1.2em}.katex .sizing.reset-size1.size3,.katex .fontsize-ensurer.reset-size1.size3{font-size:1.4em}.katex .sizing.reset-size1.size4,.katex .fontsize-ensurer.reset-size1.size4{font-size:1.6em}.katex .sizing.reset-size1.size5,.katex .fontsize-ensurer.reset-size1.size5{font-size:1.8em}.katex .sizing.reset-size1.size6,.katex .fontsize-ensurer.reset-size1.size6{font-size:2em}.katex .sizing.reset-size1.size7,.katex .fontsize-ensurer.reset-size1.size7{font-size:2.4em}.katex .sizing.reset-size1.size8,.katex .fontsize-ensurer.reset-size1.size8{font-size:2.88em}.katex .sizing.reset-size1.size9,.katex .fontsize-ensurer.reset-size1.size9{font-size:3.456em}.katex .sizing.reset-size1.size10,.katex .fontsize-ensurer.reset-size1.size10{font-size:4.148em}.katex .sizing.reset-size1.size11,.katex .fontsize-ensurer.reset-size1.size11{font-size:4.976em}.katex .sizing.reset-size2.size1,.katex .fontsize-ensurer.reset-size2.size1{font-size:.8333333333em}.katex .sizing.reset-size2.size2,.katex .fontsize-ensurer.reset-size2.size2{font-size:1em}.katex .sizing.reset-size2.size3,.katex .fontsize-ensurer.reset-size2.size3{font-size:1.1666666667em}.katex .sizing.reset-size2.size4,.katex .fontsize-ensurer.reset-size2.size4{font-size:1.3333333333em}.katex .sizing.reset-size2.size5,.katex .fontsize-ensurer.reset-size2.size5{font-size:1.5em}.katex .sizing.reset-size2.size6,.katex .fontsize-ensurer.reset-size2.size6{font-size:1.6666666667em}.katex .sizing.reset-size2.size7,.katex .fontsize-ensurer.reset-size2.size7{font-size:2em}.katex .sizing.reset-size2.size8,.katex .fontsize-ensurer.reset-size2.size8{font-size:2.4em}.katex .sizing.reset-size2.size9,.katex .fontsize-ensurer.reset-size2.size9{font-size:2.88em}.katex .sizing.reset-size2.size10,.katex .fontsize-ensurer.reset-size2.size10{font-size:3.4566666667em}.katex .sizing.reset-size2.size11,.katex .fontsize-ensurer.reset-size2.size11{font-size:4.1466666667em}.katex .sizing.reset-size3.size1,.katex .fontsize-ensurer.reset-size3.size1{font-size:.7142857143em}.katex .sizing.reset-size3.size2,.katex .fontsize-ensurer.reset-size3.size2{font-size:.8571428571em}.katex .sizing.reset-size3.size3,.katex .fontsize-ensurer.reset-size3.size3{font-size:1em}.katex .sizing.reset-size3.size4,.katex .fontsize-ensurer.reset-size3.size4{font-size:1.1428571429em}.katex .sizing.reset-size3.size5,.katex .fontsize-ensurer.reset-size3.size5{font-size:1.2857142857em}.katex .sizing.reset-size3.size6,.katex .fontsize-ensurer.reset-size3.size6{font-size:1.4285714286em}.katex .sizing.reset-size3.size7,.katex .fontsize-ensurer.reset-size3.size7{font-size:1.7142857143em}.katex .sizing.reset-size3.size8,.katex .fontsize-ensurer.reset-size3.size8{font-size:2.0571428571em}.katex .sizing.reset-size3.size9,.katex .fontsize-ensurer.reset-size3.size9{font-size:2.4685714286em}.katex .sizing.reset-size3.size10,.katex .fontsize-ensurer.reset-size3.size10{font-size:2.9628571429em}.katex .sizing.reset-size3.size11,.katex .fontsize-ensurer.reset-size3.size11{font-size:3.5542857143em}.katex .sizing.reset-size4.size1,.katex .fontsize-ensurer.reset-size4.size1{font-size:.625em}.katex .sizing.reset-size4.size2,.katex .fontsize-ensurer.reset-size4.size2{font-size:.75em}.katex .sizing.reset-size4.size3,.katex .fontsize-ensurer.reset-size4.size3{font-size:.875em}.katex .sizing.reset-size4.size4,.katex .fontsize-ensurer.reset-size4.size4{font-size:1em}.katex .sizing.reset-size4.size5,.katex .fontsize-ensurer.reset-size4.size5{font-size:1.125em}.katex .sizing.reset-size4.size6,.katex .fontsize-ensurer.reset-size4.size6{font-size:1.25em}.katex .sizing.reset-size4.size7,.katex .fontsize-ensurer.reset-size4.size7{font-size:1.5em}.katex .sizing.reset-size4.size8,.katex .fontsize-ensurer.reset-size4.size8{font-size:1.8em}.katex .sizing.reset-size4.size9,.katex .fontsize-ensurer.reset-size4.size9{font-size:2.16em}.katex .sizing.reset-size4.size10,.katex .fontsize-ensurer.reset-size4.size10{font-size:2.5925em}.katex .sizing.reset-size4.size11,.katex .fontsize-ensurer.reset-size4.size11{font-size:3.11em}.katex .sizing.reset-size5.size1,.katex .fontsize-ensurer.reset-size5.size1{font-size:.5555555556em}.katex .sizing.reset-size5.size2,.katex .fontsize-ensurer.reset-size5.size2{font-size:.6666666667em}.katex .sizing.reset-size5.size3,.katex .fontsize-ensurer.reset-size5.size3{font-size:.7777777778em}.katex .sizing.reset-size5.size4,.katex .fontsize-ensurer.reset-size5.size4{font-size:.8888888889em}.katex .sizing.reset-size5.size5,.katex .fontsize-ensurer.reset-size5.size5{font-size:1em}.katex .sizing.reset-size5.size6,.katex .fontsize-ensurer.reset-size5.size6{font-size:1.1111111111em}.katex .sizing.reset-size5.size7,.katex .fontsize-ensurer.reset-size5.size7{font-size:1.3333333333em}.katex .sizing.reset-size5.size8,.katex .fontsize-ensurer.reset-size5.size8{font-size:1.6em}.katex .sizing.reset-size5.size9,.katex .fontsize-ensurer.reset-size5.size9{font-size:1.92em}.katex .sizing.reset-size5.size10,.katex .fontsize-ensurer.reset-size5.size10{font-size:2.3044444444em}.katex .sizing.reset-size5.size11,.katex .fontsize-ensurer.reset-size5.size11{font-size:2.7644444444em}.katex .sizing.reset-size6.size1,.katex .fontsize-ensurer.reset-size6.size1{font-size:.5em}.katex .sizing.reset-size6.size2,.katex .fontsize-ensurer.reset-size6.size2{font-size:.6em}.katex .sizing.reset-size6.size3,.katex .fontsize-ensurer.reset-size6.size3{font-size:.7em}.katex .sizing.reset-size6.size4,.katex .fontsize-ensurer.reset-size6.size4{font-size:.8em}.katex .sizing.reset-size6.size5,.katex .fontsize-ensurer.reset-size6.size5{font-size:.9em}.katex .sizing.reset-size6.size6,.katex .fontsize-ensurer.reset-size6.size6{font-size:1em}.katex .sizing.reset-size6.size7,.katex .fontsize-ensurer.reset-size6.size7{font-size:1.2em}.katex .sizing.reset-size6.size8,.katex .fontsize-ensurer.reset-size6.size8{font-size:1.44em}.katex .sizing.reset-size6.size9,.katex .fontsize-ensurer.reset-size6.size9{font-size:1.728em}.katex .sizing.reset-size6.size10,.katex .fontsize-ensurer.reset-size6.size10{font-size:2.074em}.katex .sizing.reset-size6.size11,.katex .fontsize-ensurer.reset-size6.size11{font-size:2.488em}.katex .sizing.reset-size7.size1,.katex .fontsize-ensurer.reset-size7.size1{font-size:.4166666667em}.katex .sizing.reset-size7.size2,.katex .fontsize-ensurer.reset-size7.size2{font-size:.5em}.katex .sizing.reset-size7.size3,.katex .fontsize-ensurer.reset-size7.size3{font-size:.5833333333em}.katex .sizing.reset-size7.size4,.katex .fontsize-ensurer.reset-size7.size4{font-size:.6666666667em}.katex .sizing.reset-size7.size5,.katex .fontsize-ensurer.reset-size7.size5{font-size:.75em}.katex .sizing.reset-size7.size6,.katex .fontsize-ensurer.reset-size7.size6{font-size:.8333333333em}.katex .sizing.reset-size7.size7,.katex .fontsize-ensurer.reset-size7.size7{font-size:1em}.katex .sizing.reset-size7.size8,.katex .fontsize-ensurer.reset-size7.size8{font-size:1.2em}.katex .sizing.reset-size7.size9,.katex .fontsize-ensurer.reset-size7.size9{font-size:1.44em}.katex .sizing.reset-size7.size10,.katex .fontsize-ensurer.reset-size7.size10{font-size:1.7283333333em}.katex .sizing.reset-size7.size11,.katex .fontsize-ensurer.reset-size7.size11{font-size:2.0733333333em}.katex .sizing.reset-size8.size1,.katex .fontsize-ensurer.reset-size8.size1{font-size:.3472222222em}.katex .sizing.reset-size8.size2,.katex .fontsize-ensurer.reset-size8.size2{font-size:.4166666667em}.katex .sizing.reset-size8.size3,.katex .fontsize-ensurer.reset-size8.size3{font-size:.4861111111em}.katex .sizing.reset-size8.size4,.katex .fontsize-ensurer.reset-size8.size4{font-size:.5555555556em}.katex .sizing.reset-size8.size5,.katex .fontsize-ensurer.reset-size8.size5{font-size:.625em}.katex .sizing.reset-size8.size6,.katex .fontsize-ensurer.reset-size8.size6{font-size:.6944444444em}.katex .sizing.reset-size8.size7,.katex .fontsize-ensurer.reset-size8.size7{font-size:.8333333333em}.katex .sizing.reset-size8.size8,.katex .fontsize-ensurer.reset-size8.size8{font-size:1em}.katex .sizing.reset-size8.size9,.katex .fontsize-ensurer.reset-size8.size9{font-size:1.2em}.katex .sizing.reset-size8.size10,.katex .fontsize-ensurer.reset-size8.size10{font-size:1.4402777778em}.katex .sizing.reset-size8.size11,.katex .fontsize-ensurer.reset-size8.size11{font-size:1.7277777778em}.katex .sizing.reset-size9.size1,.katex .fontsize-ensurer.reset-size9.size1{font-size:.2893518519em}.katex .sizing.reset-size9.size2,.katex .fontsize-ensurer.reset-size9.size2{font-size:.3472222222em}.katex .sizing.reset-size9.size3,.katex .fontsize-ensurer.reset-size9.size3{font-size:.4050925926em}.katex .sizing.reset-size9.size4,.katex .fontsize-ensurer.reset-size9.size4{font-size:.462962963em}.katex .sizing.reset-size9.size5,.katex .fontsize-ensurer.reset-size9.size5{font-size:.5208333333em}.katex .sizing.reset-size9.size6,.katex .fontsize-ensurer.reset-size9.size6{font-size:.5787037037em}.katex .sizing.reset-size9.size7,.katex .fontsize-ensurer.reset-size9.size7{font-size:.6944444444em}.katex .sizing.reset-size9.size8,.katex .fontsize-ensurer.reset-size9.size8{font-size:.8333333333em}.katex .sizing.reset-size9.size9,.katex .fontsize-ensurer.reset-size9.size9{font-size:1em}.katex .sizing.reset-size9.size10,.katex .fontsize-ensurer.reset-size9.size10{font-size:1.2002314815em}.katex .sizing.reset-size9.size11,.katex .fontsize-ensurer.reset-size9.size11{font-size:1.4398148148em}.katex .sizing.reset-size10.size1,.katex .fontsize-ensurer.reset-size10.size1{font-size:.2410800386em}.katex .sizing.reset-size10.size2,.katex .fontsize-ensurer.reset-size10.size2{font-size:.2892960463em}.katex .sizing.reset-size10.size3,.katex .fontsize-ensurer.reset-size10.size3{font-size:.337512054em}.katex .sizing.reset-size10.size4,.katex .fontsize-ensurer.reset-size10.size4{font-size:.3857280617em}.katex .sizing.reset-size10.size5,.katex .fontsize-ensurer.reset-size10.size5{font-size:.4339440694em}.katex .sizing.reset-size10.size6,.katex .fontsize-ensurer.reset-size10.size6{font-size:.4821600771em}.katex .sizing.reset-size10.size7,.katex .fontsize-ensurer.reset-size10.size7{font-size:.5785920926em}.katex .sizing.reset-size10.size8,.katex .fontsize-ensurer.reset-size10.size8{font-size:.6943105111em}.katex .sizing.reset-size10.size9,.katex .fontsize-ensurer.reset-size10.size9{font-size:.8331726133em}.katex .sizing.reset-size10.size10,.katex .fontsize-ensurer.reset-size10.size10{font-size:1em}.katex .sizing.reset-size10.size11,.katex .fontsize-ensurer.reset-size10.size11{font-size:1.1996142719em}.katex .sizing.reset-size11.size1,.katex .fontsize-ensurer.reset-size11.size1{font-size:.2009646302em}.katex .sizing.reset-size11.size2,.katex .fontsize-ensurer.reset-size11.size2{font-size:.2411575563em}.katex .sizing.reset-size11.size3,.katex .fontsize-ensurer.reset-size11.size3{font-size:.2813504823em}.katex .sizing.reset-size11.size4,.katex .fontsize-ensurer.reset-size11.size4{font-size:.3215434084em}.katex .sizing.reset-size11.size5,.katex .fontsize-ensurer.reset-size11.size5{font-size:.3617363344em}.katex .sizing.reset-size11.size6,.katex .fontsize-ensurer.reset-size11.size6{font-size:.4019292605em}.katex .sizing.reset-size11.size7,.katex .fontsize-ensurer.reset-size11.size7{font-size:.4823151125em}.katex .sizing.reset-size11.size8,.katex .fontsize-ensurer.reset-size11.size8{font-size:.578778135em}.katex .sizing.reset-size11.size9,.katex .fontsize-ensurer.reset-size11.size9{font-size:.6945337621em}.katex .sizing.reset-size11.size10,.katex .fontsize-ensurer.reset-size11.size10{font-size:.8336012862em}.katex .sizing.reset-size11.size11,.katex .fontsize-ensurer.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .op-limits>.vlist-t{text-align:center}.katex .accent>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{display:block;position:absolute;width:100%;height:inherit;fill:currentColor;stroke:currentColor}.katex svg path{stroke:none}.katex svg{fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex img{border-style:none;min-width:0;min-height:0;max-width:none;max-height:none}.katex .stretchy{width:100%;display:block;position:relative;overflow:hidden}.katex .stretchy:before,.katex .stretchy:after{content:""}.katex .hide-tail{width:100%;position:relative;overflow:hidden}.katex .halfarrow-left{position:absolute;left:0;width:50.2%;overflow:hidden}.katex .halfarrow-right{position:absolute;right:0;width:50.2%;overflow:hidden}.katex .brace-left{position:absolute;left:0;width:25.1%;overflow:hidden}.katex .brace-center{position:absolute;left:25%;width:50%;overflow:hidden}.katex .brace-right{position:absolute;right:0;width:25.1%;overflow:hidden}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .x-arrow,.katex .mover,.katex .munder{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{box-sizing:border-box;border:.04em solid}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{box-sizing:border-box;border-top:.049em solid;border-right:.049em solid;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{counter-increment:katexEqnNo;content:"(" counter(katexEqnNo) ")"}.katex .mml-eqn-num:before{counter-increment:mmlEqnNo;content:"(" counter(mmlEqnNo) ")"}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;position:absolute;left:calc(50% + .3em);text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{text-align:left;padding-left:2em}body{counter-reset:katexEqnNo mmlEqnNo}:root{color-scheme:dark;--terminal-background: #242830;--terminal-foreground: #e7e9ee;--terminal-font: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;--terminal-font-size: 15px;--terminal-line-height: 1.22}*{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden;background:var(--terminal-background);color:var(--terminal-foreground);font-family:var(--terminal-font)}body{display:grid;grid-template-rows:34px minmax(0,1fr)}header{z-index:2;display:flex;align-items:center;gap:12px;padding:0 12px;border-bottom:1px solid #3a414d;background:#1c2027;color:#aeb5c1;font-size:12px}.brand{color:#f0f2f6;font-weight:700}.mode{margin-left:auto}#download-html{padding:3px 9px;border:1px solid #525b69;border-radius:4px;background:#292f38;color:#d8dce4;font:inherit;cursor:pointer}#download-html:hover:not(:disabled){border-color:#73c9d2;color:#f0f2f6}#download-html:disabled{cursor:wait;opacity:.55}.live{color:#70d68b}.error{color:#ff7b72}#transcript-host{min-width:0;min-height:0;overflow:auto;overflow-anchor:none;background:var(--terminal-background)}#transcript{width:max(100%,calc(var(--terminal-columns, 80) * 1ch + 32px));min-height:100%;padding:10px 16px 18vh;font-family:var(--terminal-font);font-size:var(--terminal-font-size);line-height:var(--terminal-line-height);font-variant-ligatures:none}.transcript-block{width:100%}.terminal-group{margin:0 0 .8rem}.terminal-group.panel{margin:.45rem 0 1rem}.terminal-row{min-height:calc(1em * var(--terminal-line-height));margin:0;white-space:pre}.message{position:relative;min-width:0;margin:0 0 1rem;padding:.45rem 1.2rem .65rem 2rem;overflow-wrap:anywhere}.message.assistant:before{position:absolute;top:.55rem;left:.15rem;color:#aeb5c1;content:"\2022"}.message.user{margin:.45rem 0 1rem;padding:.8rem 1.2rem;background:#444955}.message>:first-child{margin-top:0}.message>:last-child{margin-bottom:0}.message p,.message ul,.message ol,.message blockquote,.message pre,.message table,.message .math-display{margin:.7rem 0}.message h1,.message h2,.message h3,.message h4,.message h5,.message h6{margin:1.15rem 0 .55rem;color:#f1f3f7;line-height:1.25}.message h1{padding-bottom:.3rem;border-bottom:1px solid #4a515e;font-size:1.55rem}.message h2{font-size:1.3rem}.message h3{font-size:1.1rem;font-style:italic}.message ul,.message ol{padding-left:1.7rem}.message li+li{margin-top:.25rem}.message blockquote{padding:.2rem 0 .2rem .9rem;border-left:3px solid #697386;color:#c1c6d0}.message code{padding:.08rem .3rem;border:1px solid #454c59;border-radius:4px;background:#1d2128;color:#f0d9a6;font-family:var(--terminal-font);font-size:.93em}.message pre{max-width:100%;padding:.85rem 1rem;overflow:auto;border:1px solid #3c4350;border-radius:7px;background:#1b1f26;white-space:pre}.message pre code{padding:0;border:0;background:transparent;color:#e7e9ee;font-size:.92rem}.message .hljs-comment,.message .hljs-quote{color:#82909f;font-style:italic}.message .hljs-keyword,.message .hljs-selector-tag,.message .hljs-literal,.message .hljs-section,.message .hljs-link{color:#d2a8ff}.message .hljs-string,.message .hljs-attr,.message .hljs-template-tag,.message .hljs-template-variable,.message .hljs-regexp,.message .hljs-addition{color:#a5d6a7}.message .hljs-number,.message .hljs-symbol,.message .hljs-bullet,.message .hljs-variable,.message .hljs-variable.constant_,.message .hljs-meta{color:#79c0ff}.message .hljs-title,.message .hljs-title.class_,.message .hljs-title.function_,.message .hljs-selector-id,.message .hljs-selector-class{color:#dcdcaa}.message .hljs-built_in,.message .hljs-type,.message .hljs-params,.message .hljs-attribute{color:#ffa657}.message .hljs-name,.message .hljs-tag,.message .hljs-deletion{color:#ff7b72}.message .hljs-property,.message .hljs-punctuation{color:#c9d1d9}.message .hljs-emphasis{font-style:italic}.message .hljs-strong{font-weight:700}.message img{display:block;max-width:100%;height:auto;margin:.65rem 0;border:1px solid #3c4350;border-radius:7px}.copy-inline{cursor:copy}.copy-inline:hover,.copy-inline:focus-visible{border-color:#73c9d2;background:#29343c;color:#fff0c6;outline:none}.copy-inline:active,.copy-inline.copy-pressed{border-color:#70d68b;background:#20352c;color:#baf2c8}.copy-inline.copy-failed{border-color:#ff7b72}.copy-block{position:relative;border-radius:7px;cursor:copy}.copy-block:after{position:absolute;top:.42rem;right:.55rem;z-index:1;padding:.16rem .4rem;border:1px solid #56606f;border-radius:4px;background:#252b34;color:#c8ced8;content:"Copy";font-size:.7rem;line-height:1.2;opacity:0;pointer-events:none;transition:opacity .12s ease}.copy-block:hover:after,.copy-block:focus-visible:after,.copy-block:active:after,.copy-block.copy-pressed:after,.copy-block.copy-failed:after{opacity:1}.copy-block:hover>pre,.copy-block:focus-visible>pre{border-color:#5c7d87;background:#20262e}.copy-block:focus-visible{outline:1px solid #73c9d2;outline-offset:2px}.copy-block:active:after,.copy-block.copy-pressed:after{border-color:#70d68b;color:#9be9ad}.copy-block.copy-failed:after{border-color:#ff7b72;color:#ff9b94;content:"Copy failed"}.latex-fence.copy-block{padding:.42rem .55rem;border:1px solid transparent}.latex-fence.copy-block:hover,.latex-fence.copy-block:focus-visible{border-color:#4e6570;background:#282e36}.mermaid-diagram{min-height:4rem;margin:.65rem 0;padding:.8rem;overflow:auto;border:1px solid #3c4350;background:#20242b}.mermaid-diagram:hover,.mermaid-diagram:focus-visible{border-color:#5c7d87;background:#232a32}.mermaid-diagram>svg{display:block;max-width:100%;height:auto;margin:auto}.mermaid-diagram .mermaid-source{margin:0;border:0;background:transparent;white-space:pre-wrap}.mermaid-diagram[data-mermaid-state=failed]{border-color:#ff7b72}.message table{display:block;max-width:100%;overflow-x:auto;border-spacing:0;border-collapse:collapse}.message th,.message td{padding:.45rem .7rem;border:1px solid #48505d;text-align:left;white-space:nowrap}.message th{background:#343a45;color:#f1f3f7}.message tr:nth-child(2n) td{background:#292e37}.message a{color:#73c9d2;text-decoration-thickness:1px;text-underline-offset:.18em}.message hr{margin:1.1rem 0;border:0;border-top:1px solid #48505d}.math-display,.math-inline-display{max-width:100%;overflow-x:auto}.math-display .katex-display,.math-inline-display .katex-display{margin:0;text-align:left!important}.math-inline-display{display:inline-block;vertical-align:middle}.math-source{padding:.65rem .8rem;border-left:3px solid #697386;background:#1f242c;color:#d9dde5;white-space:pre-wrap}.math-source-inline{color:inherit;white-space:pre-wrap}
|