feat: 增加 ZMQ 调试服务器并完善设置功能

This commit is contained in:
2026-04-20 06:37:46 +08:00
parent 85925b9d44
commit 709e15663b
11 changed files with 289 additions and 58 deletions

View File

@@ -1,4 +1,8 @@
enable_built_in_interface = true zmq_debug = false
_zmq_debug_desc = "[调试] ZMQ 调试服务器, 这会在 zmq_debug_port 上打开调试服务器\n可作为 HeurAMS 执行任意 python 代码, 如无必要请关闭"
zmq_debug_port = 5555
_zmq_debug_port_desc = "ZMQ 调试服务器端口"
enable_built_in_interface = false
_enable_built_in_interface_desc = "启用内置基本用户界面\n(当且仅当 HeurAMS 作为程序库时禁用, 以跳过用户界面逻辑)" _enable_built_in_interface_desc = "启用内置基本用户界面\n(当且仅当 HeurAMS 作为程序库时禁用, 以跳过用户界面逻辑)"
_paths_desc = "用户数据路径定义" _paths_desc = "用户数据路径定义"

View File

@@ -4,7 +4,7 @@ quick_pass = true
_quick_pass_desc = "[调试] 启用快速应答功能(跳过测验)" _quick_pass_desc = "[调试] 启用快速应答功能(跳过测验)"
auto_pass = false auto_pass = false
_auto_pass_desc = "[调试] 自动通过测试模式" _auto_pass_desc = "[调试] 自动通过测试模式"
scheduled_num = 420 scheduled_num = "420"
_scheduled_num_desc = "默认记忆单元数量(可被单元集设置覆盖)" _scheduled_num_desc = "默认记忆单元数量(可被单元集设置覆盖)"
algorithm = "NSP-0" algorithm = "NSP-0"
_algorithm_desc = "默认记忆调度算法(可被单元集设置覆盖)" _algorithm_desc = "默认记忆调度算法(可被单元集设置覆盖)"

View File

@@ -1,2 +1,2 @@
min_denominator = 3 min_denominator = "3"
_min_denominator_desc = "设空比例系数的倒数" _min_denominator_desc = "设空比例系数的倒数"

View File

@@ -1,2 +1,2 @@
max_riddles_num = 2 max_riddles_num = "2"
_max_riddles_num_desc = "单次生成的最大谜题数量" _max_riddles_num_desc = "单次生成的最大谜题数量"

View File

@@ -16,6 +16,7 @@ dependencies = [
"textual>=8.2.3", "textual>=8.2.3",
"toml>=0.10.2", "toml>=0.10.2",
"transitions>=0.9.3", "transitions>=0.9.3",
"zmq>=0.0.0",
] ]
[project.scripts] [project.scripts]

View File

@@ -1,6 +1,9 @@
from heurams.interface import * from heurams.interface import *
from heurams.context import config_var from heurams.context import config_var
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
import threading
import zmq
import pickle
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -20,9 +23,43 @@ def environment_check():
print(f"找到 {i}") print(f"找到 {i}")
logger.debug("环境检查完成") logger.debug("环境检查完成")
def start_debug_server(app):
logger = get_logger("zmq_debug")
context = zmq.Context()
socket = context.socket(zmq.REP)
port = config_var.get()['global'].get('zmq_debug_port', 5555)
socket.bind(f"tcp://*:{port}")
logger.info(f"ZMQ Debug server started on port {port}")
first = 1
while True:
msg = socket.recv()
code = pickle.loads(msg)
namespace = {"app": app, "logger": logger, "config_var": config_var}
if first:
app.title += ' [调试已连接]'
first = 0
try:
# 先尝试 eval
result = eval(code, namespace)
socket.send(pickle.dumps(f"成功: {result}"))
except SyntaxError:
# 再尝试 exec
try:
exec(code, namespace)
socket.send(pickle.dumps(f"成功: 执行完成"))
except Exception as e:
socket.send(pickle.dumps(f"错误: {e}"))
except Exception as e:
socket.send(pickle.dumps(f"错误: {e}"))
def main(): def main():
environment_check() environment_check()
app = HeurAMSApp() app = HeurAMSApp()
if config_var.get()['global'].get('zmq_debug', False):
threading.Thread(target=start_debug_server, args=(app,), daemon=True).start()
app.run(inline=False) app.run(inline=False)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -75,17 +75,17 @@ class SettingScreen(Screen):
a = self._get_subcfg(f"{parent_epath}.{i}") a = self._get_subcfg(f"{parent_epath}.{i}")
if a: if a:
lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}')) lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'))
elif f'_{i}_candidate' in parent: elif f'_{i}_candidate' in parent: # 选择框模式
if isinstance(parent[f'_{i}_candidate'], dict): if isinstance(parent[f'_{i}_candidate'], dict):
lst.append(Horizontal( lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select((f"{j} ({k})", j) for j, k in parent[f'_{i}_candidate'].items()), Select(((f"{j} ({k})", j) for j, k in parent[f'_{i}_candidate'].items()), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
classes='container' classes='container'
)) ))
elif isinstance(parent[f'_{i}_candidate'], list): elif isinstance(parent[f'_{i}_candidate'], list):
lst.append(Horizontal( lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select((j, j) for j in parent[f'_{i}_candidate']), Select(((j, j) for j in parent[f'_{i}_candidate']), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
classes='container' classes='container'
)) ))
else: else:
@@ -102,7 +102,7 @@ class SettingScreen(Screen):
elif isinstance(parent[i], bool): elif isinstance(parent[i], bool):
lst.append(Horizontal( lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'), Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Switch(value=str(parent[i]), id=domize(f"{parent_epath}.{i}")), Switch(value=parent[i], id=domize(f"{parent_epath}.{i}")),
classes='container')) classes='container'))
elif isinstance(parent[i], int): elif isinstance(parent[i], int):
lst.append(Horizontal( lst.append(Horizontal(
@@ -122,6 +122,7 @@ class SettingScreen(Screen):
def action_go_back(self) -> None: def action_go_back(self) -> None:
"""返回上一屏幕""" """返回上一屏幕"""
config_var.get().persist()
self.app.pop_screen() self.app.pop_screen()
def action_quit_app(self) -> None: def action_quit_app(self) -> None:
@@ -132,12 +133,27 @@ class SettingScreen(Screen):
"""打开导航器""" """打开导航器"""
self.app.push_screen(NavigatorScreen()) self.app.push_screen(NavigatorScreen())
def on_button_pressed(self, event: Button.Pressed) -> None:
logger.debug(f"event.button.id: {event.button.id}") def on_input_changed(self, event: Input.Changed) -> None:
"""处理按钮点击事件""" widget_id = event.input.id
if str(event.button.id) == 'apply': if not widget_id:
pass return
if str(event.button.id) == 'openfolder': eepath = undomize(widget_id)
pass value = event.value
if str(event.button.id) == 'cancel': epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
pass
def on_switch_changed(self, event: Switch.Changed) -> None:
widget_id = event.switch.id
if not widget_id:
return
eepath = undomize(widget_id)
value = event.value
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
def on_select_changed(self, event: Select.Changed) -> None:
widget_id = event.select.id
if not widget_id:
return
eepath = undomize(widget_id)
value = event.value
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))

View File

@@ -9,17 +9,38 @@ from heurams.services.logger import get_logger
from heurams.services.exceptions import WTFException from heurams.services.exceptions import WTFException
# 我们的流程是: 找到文件名: 返回文件名里头的数据; 找不到: 继续查索引; 所以 self.data 除了存本级各种索引球用没得 # 我们的流程是: 找到文件名: 返回文件名里头的数据; 找不到: 继续查索引; 所以 self.data 除了存本级各种索引球用没得
# 递归就是这么吊 logger = get_logger(__name__)
class ConfigDict(UserDict): # 舒服了 class ConfigDict(UserDict): # 舒服了
_instances = {} # 必须使用单例模式, 不然有严重的多实例导致的配置无法持久化问题
def __new__(cls, config_path: pathlib.Path, dict=None):
if dict:
raise WTFException("不要放默认值...")
# 规范化路径, 免得单例存在"别名"
path_key = config_path.resolve()
if path_key in cls._instances:
return cls._instances[path_key]
instance = super().__new__(cls)
cls._instances[path_key] = instance
return instance
def __init__(self, config_path: pathlib.Path, dict = None): # 需要自己把自己提起来 def __init__(self, config_path: pathlib.Path, dict = None): # 需要自己把自己提起来
# 避免重复初始化
if hasattr(self, '_initialized'):
return
self._initialized = True
if dict: if dict:
raise WTFException("不要放默认值...") raise WTFException("不要放默认值...")
super().__init__(dict) super().__init__(dict)
self.logger = get_logger(__name__) logger = get_logger(__name__)
self.path = config_path self.path = config_path
self.is_dir = self.path.is_dir() self.is_dir = self.path.is_dir()
if self.is_dir: if self.is_dir:
self.update_index() # 狗儿要唱狗儿歌 self.update_index()
else: else:
with open(self.path, 'r+') as f: #TODO: 给这个做缓存 with open(self.path, 'r+') as f: #TODO: 给这个做缓存
try: try:
@@ -35,11 +56,6 @@ class ConfigDict(UserDict): # 舒服了
return ConfigDict(value) return ConfigDict(value)
return value return value
def converter(self, s):
if (self.path / s).exists:
return self.path / s
return self.path / s+'.toml'
def __contains__(self, key): def __contains__(self, key):
return super().__contains__(key) return super().__contains__(key)
@@ -67,22 +83,16 @@ class ConfigDict(UserDict): # 舒服了
if i.suffix == '.toml': if i.suffix == '.toml':
self.data[i.stem] = i self.data[i.stem] = i
else: else:
self.logger.log(f"配置目录中有无效的文件 {i.stem}") # what's up bro logger.debug(f"配置目录中有无效的文件 {i.stem}") # what's up bro
def persist(self): def persist(self):
if self.is_dir: if self.is_dir:
raise WTFException("不准这样浪费性能...") for i in self.data.keys():
j = self[i]
if isinstance(j, ConfigDict):
j.persist()
logger.debug("完成配置持久化")
return
with open(self.path, 'w+') as f: with open(self.path, 'w+') as f:
toml.dump(self.data, f) toml.dump(self.data, f)
def __del__(self):
if not self.is_dir:
self.persist() # 不准循环引用, 懂了吧
@staticmethod
def titleize(objt):
if isinstance(objt, pathlib.Path):
return objt.stem
else:
return objt

View File

@@ -2,35 +2,59 @@ from heurams.services.config import ConfigDict
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
def epath(dct, path: str = '', default=None, parents=False):
def epath(dct, path: str = '', default=None, parents=False, enable_modify=False, new_value=None):
if not path: if not path:
return dct return dct
path = path.rstrip('.') path = path.rstrip('.')
path = path.lstrip('.') path = path.lstrip('.')
target = dct target = dct
keys = path.split('.')
for i in path.split('.'): logger.debug(f"处理 EPATH {path}, {new_value}")
for idx, i in enumerate(keys):
is_last = (idx == len(keys) - 1)
# 处理字典键 # 处理字典键
logger.debug(f'处理 {i}, {(isinstance(target, dict) or isinstance(target, ConfigDict))} {i in target}') logger.debug(f'处理 {i}, {(isinstance(target, dict) or isinstance(target, ConfigDict))} {i in target}')
if (isinstance(target, dict) or isinstance(target, ConfigDict)) and i in target:
target = target[i] if is_last and enable_modify:
# 处理列表索引 # 最后一次循环执行修改
elif i.startswith('[') and i.endswith(']') and isinstance(target, (list, tuple)): if (isinstance(target, dict) or isinstance(target, ConfigDict)):
idx = int(i[1:-1]) target[i] = new_value
if 0 <= idx < len(target): return new_value
target = target[idx] elif i.startswith('[') and i.endswith(']') and isinstance(target, (list, tuple)):
elif parents: idx_num = int(i[1:-1])
while len(target) <= idx: if 0 <= idx_num < len(target):
target.append(None) target[idx_num] = new_value
target[idx] = {} return new_value
target = target[idx] elif parents:
while len(target) <= idx_num:
target.append(None)
target[idx_num] = new_value
return new_value
else:
return default
else: else:
return default return default
elif parents:
target[i] = {}
target = target[i]
else: else:
return default if (isinstance(target, dict) or isinstance(target, ConfigDict)) and i in target:
target = target[i]
elif i.startswith('[') and i.endswith(']') and isinstance(target, (list, tuple)):
idx_num = int(i[1:-1])
if 0 <= idx_num < len(target):
target = target[idx_num]
elif parents:
while len(target) <= idx_num:
target.append(None)
target[idx_num] = {}
target = target[idx_num]
else:
return default
elif parents:
target[i] = {}
target = target[i]
else:
return default
return target return target

View File

@@ -0,0 +1,55 @@
import zmq
import pickle
import readline
import sys
class DebugClient:
def __init__(self, port=5555):
self.context = zmq.Context()
self.socket = self.context.socket(zmq.REQ)
self.socket.connect(f"tcp://localhost:{port}")
print(f"已连接到调试服务器 (端口 {port})")
print("输入Python代码并按回车执行, 输入 'exit' 退出")
print("可用变量: app, logger")
print("-" * 50)
def execute(self, code):
"""执行代码并返回结果"""
try:
self.socket.send(pickle.dumps(code))
response = pickle.loads(self.socket.recv())
return response
except Exception as e:
return f"连接错误: {e}"
def repl(self):
"""交互式REPL循环"""
self.execute('print("test")')
while True:
try:
# 获取用户输入
code = input(">>> ").strip()
if not code:
continue
if code.lower() in ['exit', 'quit']:
print("退出调试客户端")
break
# 执行代码
result = self.execute(code)
print(f"结果: {result}\n")
except KeyboardInterrupt:
print("\n退出调试客户端")
break
except EOFError:
break
if __name__ == "__main__":
# 从命令行参数获取端口
port = int(sys.argv[1]) if len(sys.argv) > 1 else 5555
client = DebugClient(port)
client.repl()

84
uv.lock generated
View File

@@ -113,6 +113,39 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
] ]
[[package]]
name = "cffi"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@@ -211,6 +244,7 @@ dependencies = [
{ name = "textual" }, { name = "textual" },
{ name = "toml" }, { name = "toml" },
{ name = "transitions" }, { name = "transitions" },
{ name = "zmq" },
] ]
[package.metadata] [package.metadata]
@@ -225,6 +259,7 @@ requires-dist = [
{ name = "textual", specifier = ">=8.2.3" }, { name = "textual", specifier = ">=8.2.3" },
{ name = "toml", specifier = ">=0.10.2" }, { name = "toml", specifier = ">=0.10.2" },
{ name = "transitions", specifier = ">=0.9.3" }, { name = "transitions", specifier = ">=0.9.3" },
{ name = "zmq", specifier = ">=0.0.0" },
] ]
[[package]] [[package]]
@@ -510,6 +545,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/54/123f6239685f5f3f2edc123f1e38d2eefacebee18cf3c532d2f4bd51d0ef/pycairo-1.29.0-cp314-cp314t-win_arm64.whl", hash = "sha256:caba0837a4b40d47c8dfb0f24cccc12c7831e3dd450837f2a356c75f21ce5a15", size = 721404, upload-time = "2025-11-11T19:12:36.919Z" }, { url = "https://files.pythonhosted.org/packages/d7/54/123f6239685f5f3f2edc123f1e38d2eefacebee18cf3c532d2f4bd51d0ef/pycairo-1.29.0-cp314-cp314t-win_arm64.whl", hash = "sha256:caba0837a4b40d47c8dfb0f24cccc12c7831e3dd450837f2a356c75f21ce5a15", size = 721404, upload-time = "2025-11-11T19:12:36.919Z" },
] ]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.13.2" version = "2.13.2"
@@ -584,6 +628,37 @@ dependencies = [
] ]
sdist = { url = "https://files.pythonhosted.org/packages/a2/80/09247a2be28af2c2240132a0af6c1005a2b1d089242b13a2cd782d2de8d7/pygobject-3.56.2.tar.gz", hash = "sha256:b816098969544081de9eecedb94ad6ac59c77e4d571fe7051f18bebcec074313", size = 1409059, upload-time = "2026-03-25T16:14:04.008Z" } sdist = { url = "https://files.pythonhosted.org/packages/a2/80/09247a2be28af2c2240132a0af6c1005a2b1d089242b13a2cd782d2de8d7/pygobject-3.56.2.tar.gz", hash = "sha256:b816098969544081de9eecedb94ad6ac59c77e4d571fe7051f18bebcec074313", size = 1409059, upload-time = "2026-03-25T16:14:04.008Z" }
[[package]]
name = "pyzmq"
version = "27.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "implementation_name == 'pypy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" },
{ url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" },
{ url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" },
{ url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" },
{ url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" },
{ url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" },
{ url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" },
{ url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
{ url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
{ url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
{ url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" },
{ url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" },
{ url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" },
{ url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" },
{ url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" },
{ url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" },
{ url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" },
{ url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" },
{ url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" },
{ url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" },
]
[[package]] [[package]]
name = "rich" name = "rich"
version = "15.0.0" version = "15.0.0"
@@ -753,3 +828,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" },
{ url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" },
] ]
[[package]]
name = "zmq"
version = "0.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyzmq" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6e/78/833b2808793c1619835edb1a4e17a023d5d625f4f97ff25ffff986d1f472/zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9", size = 966, upload-time = "2015-05-21T17:34:26.603Z" }