push.py and console_relay.py take --ip (default 127.0.0.1) and --port (9188), building http://<ip>:<port>/mcp; --url still overrides. console_relay.sh takes the IP as its first arg (./console_relay.sh 192.168.1.12) or prompts for it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98 lines
3.8 KiB
Python
Executable File
98 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Relay bridge-channel messages into a tmux pane (a 'designated console').
|
|
|
|
Run this ON THE MACHINE where the target tmux session lives. It polls a bridge
|
|
channel (whatever an `Agent Emit` node writes) and types each NEW message into
|
|
the given tmux pane with `tmux send-keys`, optionally pressing Enter to submit.
|
|
|
|
ComfyUI (Agent Emit, channel="console") -> bridge -> THIS relay -> tmux pane
|
|
|
|
Usage:
|
|
python examples/console_relay.py --target claude:0.0 --channel console \
|
|
--url http://192.168.1.12:9188/mcp
|
|
|
|
Find a target id:
|
|
tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_id}'
|
|
|
|
Options:
|
|
--target tmux pane, e.g. 'claude:0.0' or '%3' (required)
|
|
--channel bridge channel to read (default: console)
|
|
--url bridge MCP URL ($COMFY_BRIDGE_URL) (default: 127.0.0.1:9188/mcp)
|
|
--poll seconds between polls (default: 1.0)
|
|
--no-submit type the text but don't press Enter
|
|
--tmux-socket tmux -S socket path (if non-default)
|
|
|
|
Needs: pip install "mcp>=1.2.0" and tmux on PATH.
|
|
Only injects when the channel's `turn` advances, so a message is typed once.
|
|
"""
|
|
import argparse
|
|
import asyncio
|
|
import os
|
|
import subprocess
|
|
|
|
from mcp.client.streamable_http import streamablehttp_client
|
|
from mcp import ClientSession
|
|
|
|
|
|
def tmux_send(target, text, submit, socket):
|
|
base = ["tmux"]
|
|
if socket:
|
|
base += ["-S", socket]
|
|
# -l => literal, so words like "Enter" in the text aren't taken as keys
|
|
subprocess.run(base + ["send-keys", "-t", target, "-l", "--", text], check=True)
|
|
if submit:
|
|
subprocess.run(base + ["send-keys", "-t", target, "Enter"], check=True)
|
|
|
|
|
|
async def run(url, channel, target, poll, submit, socket):
|
|
print(f"[relay] {url} channel={channel!r} -> tmux {target!r} "
|
|
f"(submit={submit}, poll={poll}s)")
|
|
last_turn = 0
|
|
async with streamablehttp_client(url) as (r, w, _):
|
|
async with ClientSession(r, w) as s:
|
|
await s.initialize()
|
|
while True:
|
|
import json
|
|
res = await s.call_tool("comfy_pull", {"channel": channel})
|
|
msg = json.loads(res.content[0].text) if res.content else {}
|
|
turn, text = msg.get("turn", 0), msg.get("text")
|
|
if turn > last_turn and text:
|
|
last_turn = turn
|
|
print(f"[relay] turn={turn} -> {text[:80]!r}")
|
|
try:
|
|
tmux_send(target, text, submit, socket)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"[relay] tmux error: {e}")
|
|
await asyncio.sleep(poll)
|
|
|
|
|
|
def resolve_url(args):
|
|
"""--url wins; else build http://<ip>:<port>/mcp."""
|
|
if args.url:
|
|
return args.url
|
|
return f"http://{args.ip}:{args.port}/mcp"
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description="Relay a bridge channel into a tmux pane.")
|
|
p.add_argument("--target", required=True)
|
|
p.add_argument("--channel", default="console")
|
|
p.add_argument("--ip", default=os.environ.get("COMFY_BRIDGE_IP", "127.0.0.1"),
|
|
help="bridge host/IP (default 127.0.0.1)")
|
|
p.add_argument("--port", default=os.environ.get("COMFY_BRIDGE_PORT", "9188"))
|
|
p.add_argument("--url", default=os.environ.get("COMFY_BRIDGE_URL"),
|
|
help="full MCP URL (overrides --ip/--port)")
|
|
p.add_argument("--poll", type=float, default=1.0)
|
|
p.add_argument("--no-submit", dest="submit", action="store_false")
|
|
p.add_argument("--tmux-socket", default=None)
|
|
a = p.parse_args()
|
|
try:
|
|
asyncio.run(run(resolve_url(a), a.channel, a.target, a.poll, a.submit,
|
|
a.tmux_socket))
|
|
except KeyboardInterrupt:
|
|
print("\n[relay] stopped")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|