feat: JSON-lines protocol with command and event dataclasses
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""JSON-lines protocol for server ↔ TUI communication."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# --- Server → TUI Commands ---
|
||||
|
||||
@dataclass
|
||||
class InitCmd:
|
||||
project: str
|
||||
title: str
|
||||
image_protocol: str
|
||||
description: str = ""
|
||||
cmd: str = field(default="init", init=False)
|
||||
|
||||
@dataclass
|
||||
class DisplayCmd:
|
||||
widget: str
|
||||
pane: str = "main"
|
||||
clear: bool = False
|
||||
content: Optional[str] = None
|
||||
items: Optional[list] = None
|
||||
id: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
placeholder: Optional[str] = None
|
||||
cmd: str = field(default="display", init=False)
|
||||
|
||||
@dataclass
|
||||
class ImageCmd:
|
||||
path: str
|
||||
pane: str = "main"
|
||||
clear: bool = True
|
||||
cmd: str = field(default="image", init=False)
|
||||
|
||||
@dataclass
|
||||
class LogCmd:
|
||||
entry: str
|
||||
level: str = "info"
|
||||
cmd: str = field(default="log", init=False)
|
||||
|
||||
@dataclass
|
||||
class ClearCmd:
|
||||
pane: str = "main"
|
||||
cmd: str = field(default="clear", init=False)
|
||||
|
||||
@dataclass
|
||||
class LayoutCmd:
|
||||
panes: dict = field(default_factory=dict)
|
||||
cmd: str = field(default="layout", init=False)
|
||||
|
||||
@dataclass
|
||||
class NotifyCmd:
|
||||
message: str
|
||||
level: str = "info"
|
||||
cmd: str = field(default="notify", init=False)
|
||||
|
||||
@dataclass
|
||||
class ShutdownCmd:
|
||||
cmd: str = field(default="shutdown", init=False)
|
||||
|
||||
|
||||
# --- TUI → Server Events ---
|
||||
|
||||
@dataclass
|
||||
class ReadyEvent:
|
||||
event: str = field(default="ready", init=False)
|
||||
|
||||
@dataclass
|
||||
class ChecklistToggleEvent:
|
||||
pane: str
|
||||
index: int
|
||||
label: str
|
||||
checked: bool
|
||||
event: str = field(default="checklist_toggle", init=False)
|
||||
|
||||
@dataclass
|
||||
class ButtonClickEvent:
|
||||
pane: str
|
||||
id: str
|
||||
event: str = field(default="button_click", init=False)
|
||||
|
||||
@dataclass
|
||||
class InputSubmitEvent:
|
||||
pane: str
|
||||
id: str
|
||||
value: str
|
||||
event: str = field(default="input_submit", init=False)
|
||||
|
||||
|
||||
# --- Registry for decoding ---
|
||||
|
||||
_CMD_TYPES = {
|
||||
"init": InitCmd,
|
||||
"display": DisplayCmd,
|
||||
"image": ImageCmd,
|
||||
"log": LogCmd,
|
||||
"clear": ClearCmd,
|
||||
"layout": LayoutCmd,
|
||||
"notify": NotifyCmd,
|
||||
"shutdown": ShutdownCmd,
|
||||
}
|
||||
|
||||
_EVENT_TYPES = {
|
||||
"ready": ReadyEvent,
|
||||
"checklist_toggle": ChecklistToggleEvent,
|
||||
"button_click": ButtonClickEvent,
|
||||
"input_submit": InputSubmitEvent,
|
||||
}
|
||||
|
||||
|
||||
def encode_message(msg) -> str:
|
||||
"""Serialize a command or event dataclass to a JSON line (no trailing newline)."""
|
||||
return json.dumps(asdict(msg), separators=(",", ":"))
|
||||
|
||||
|
||||
def decode_message(line: str):
|
||||
"""Deserialize a JSON line to a command or event dataclass. Returns None if unknown."""
|
||||
data = json.loads(line)
|
||||
if "cmd" in data:
|
||||
cls = _CMD_TYPES.get(data["cmd"])
|
||||
if cls is None:
|
||||
return None
|
||||
kwargs = {k: v for k, v in data.items() if k != "cmd"}
|
||||
return cls(**kwargs)
|
||||
elif "event" in data:
|
||||
cls = _EVENT_TYPES.get(data["event"])
|
||||
if cls is None:
|
||||
return None
|
||||
kwargs = {k: v for k, v in data.items() if k != "event"}
|
||||
return cls(**kwargs)
|
||||
return None
|
||||
Reference in New Issue
Block a user