Switch the Wingman protocol to null-delimited JSON, rework the server's socket buffering and send logic, and document the bugs found during the review. Also refactor WingProperty's numeric setters into clearer helper paths while preserving the existing conversion rules.
66 lines
1.5 KiB
Python
Executable File
66 lines
1.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Human-friendly MCP test client.
|
|
|
|
Usage: ue-wingman.py <command> [key=value ...]
|
|
|
|
Values starting with '[' or '{' are parsed as JSON.
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import socket
|
|
|
|
HOST = "localhost"
|
|
PORT = 9851
|
|
TIMEOUT = 120
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
if not args:
|
|
print("Usage: ue-wingman.py <command> [key=value ...]")
|
|
sys.exit(1)
|
|
|
|
msg = {"command": args[0]}
|
|
for arg in args[1:]:
|
|
key, _, value = arg.partition("=")
|
|
if value and value[0] in ('[', '{'):
|
|
try:
|
|
value = json.loads(value)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Bad JSON in {key}: {e.msg}")
|
|
sys.exit(1)
|
|
msg[key] = value
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock.settimeout(TIMEOUT)
|
|
try:
|
|
sock.connect((HOST, PORT))
|
|
except (ConnectionRefusedError, socket.timeout, OSError) as e:
|
|
print(f"Cannot connect to {HOST}:{PORT} — is the editor running?")
|
|
sys.exit(1)
|
|
|
|
sock.sendall((json.dumps(msg) + "\0").encode())
|
|
|
|
result = b""
|
|
while True:
|
|
chunk = sock.recv(65536)
|
|
if not chunk:
|
|
break
|
|
result += chunk
|
|
if b"\0" in result:
|
|
break
|
|
|
|
sock.close()
|
|
result = result[:result.index(b"\0")].decode() if b"\0" in result else result.decode()
|
|
|
|
try:
|
|
parsed = json.loads(result)
|
|
print(json.dumps(parsed, indent=2))
|
|
except json.JSONDecodeError:
|
|
print(result)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|