Files
ethanfelandClaude Opus 4.8 fbd9087758 feat(examples): --ip/--port flags instead of full bridge URL
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>
2026-06-29 19:21:42 +02:00

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()