#!/usr/bin/env bash # box-relay.sh — Lightweight Box Relay Client for Fleet Agent Containers. # # Enables fleet agents (646, pip, muse, opm) to access muse-cli level tools, # spawn subagents, send sidechat DMs, and inspect threads over the secure # exec-constrained HTTPS relay endpoint. # # Supports Bearer Token (EXEC_TOKEN or ~/.exec-token) AND SSH signature auth. # Endpoint: https://exec.muse-dev.online/exec (or Tailscale https://100.123.153.75:8444/exec) set -euo pipefail EXEC_URL="${EXEC_URL:-https://exec.muse-dev.online/exec}" AGENT="${BOX_AGENT:-${AGENT_NAME:-}}" TOKEN="${EXEC_TOKEN:-}" if [ -z "$TOKEN" ] && [ -f "$HOME/.exec-token" ]; then TOKEN="$(cat "$HOME/.exec-token" | tr -d '[:space:]')" fi # Detect agent identity if not set if [ -z "$AGENT" ]; then if [ -f "$HOME/.agent-name" ]; then AGENT="$(cat "$HOME/.agent-name" | tr -d '[:space:]')" elif echo "$HOSTNAME" | grep -qi "646"; then AGENT="646" elif echo "$HOSTNAME" | grep -qi "pip"; then AGENT="pip" elif echo "$HOSTNAME" | grep -qi "muse"; then AGENT="muse" elif echo "$HOSTNAME" | grep -qi "opm"; then AGENT="opm" elif echo "$HOSTNAME" | grep -qi "dev"; then AGENT="dev" elif echo "$HOSTNAME" | grep -qi "def"; then AGENT="def" else AGENT="646" fi fi # Helper: POST to exec endpoint call_exec() { local op="$1" local args_json="$2" if [ -n "$TOKEN" ]; then # Bearer token auth local body body=$(python3 -c "import json, sys; print(json.dumps({'op': sys.argv[1], 'args': json.loads(sys.argv[2])}))" "$op" "$args_json") local res res=$(curl -sk -sS -X POST "$EXEC_URL" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) Box-Relay/1.0" \ --data "$body") echo "$res" | python3 -c " import json, sys try: d = json.load(sys.stdin) if 'stdout' in d: sys.stdout.write(d['stdout']) elif 'error' in d: sys.stderr.write('Error: ' + str(d['error']) + '\n') sys.exit(1) else: print(json.dumps(d, indent=2)) except Exception as e: print(sys.stdin.read()) " else # Signature auth fallback local key="${SSH_KEY:-$HOME/.ssh/id_frontdoor}" if [ ! -f "$key" ] && [ -f "$HOME/.ssh/id_ed25519" ]; then key="$HOME/.ssh/id_ed25519" fi local ident="operator-$AGENT" if [ "$AGENT" = "super" ]; then ident="super" fi local ts ts=$(date +%s) local nonce nonce=$(python3 -c "import secrets; print(secrets.token_hex(16))") local payload payload=$(python3 -c " import json, sys op, args_json, ts, nonce = sys.argv[1:5] args = json.loads(args_json) print(json.dumps({'op': op, 'args': args, 'ts': int(ts), 'nonce': nonce})) " "$op" "$args_json" "$ts" "$nonce") local sig sig=$(printf '%s' "$payload" | ssh-keygen -Y sign -f "$key" -n exec-constrained 2>/dev/null) local body body=$(python3 -c " import json, sys ident, payload, sig = sys.argv[1:4] print(json.dumps({'identity': ident, 'payload': payload, 'signature': sig})) " "$ident" "$payload" "$sig") local res res=$(curl -sk -sS -X POST "$EXEC_URL" \ -H "Content-Type: application/json" \ -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) Box-Relay/1.0" \ --data "$body") echo "$res" | python3 -c " import json, sys try: d = json.load(sys.stdin) if 'stdout' in d: sys.stdout.write(d['stdout']) elif 'error' in d: sys.stderr.write('Error: ' + str(d['error']) + '\n') sys.exit(1) else: print(json.dumps(d, indent=2)) except Exception as e: print(sys.stdin.read()) " fi } cmd="${1:-help}" shift || true case "$cmd" in subagent) sub="${1:-help}" shift || true case "$sub" in spawn) title="subagent" wait=0 while [ $# -gt 0 ]; do case "$1" in --title) title="$2"; shift 2 ;; --wait) wait="$2"; shift 2 ;; --agent) AGENT="$2"; shift 2 ;; *) break ;; esac done prompt="${1:?usage: box subagent spawn [--title ] [--wait <seconds>] <prompt>}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'title': sys.argv[2], 'prompt': sys.argv[3], 'wait': int(sys.argv[4])}))" "$AGENT" "$title" "$prompt" "$wait") call_exec "subagent.spawn" "$args" ;; *) echo "Usage: box subagent spawn [--title <title>] [--wait <seconds>] <prompt>" ;; esac ;; deploy) sub="${1:-help}" shift || true case "$sub" in subagent) title="subagent" wait=0 while [ $# -gt 0 ]; do case "$1" in --title) title="$2"; shift 2 ;; --wait) wait="$2"; shift 2 ;; --agent) AGENT="$2"; shift 2 ;; *) break ;; esac done prompt="${1:?usage: box deploy subagent [--title <title>] [--wait <seconds>] <prompt>}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'title': sys.argv[2], 'prompt': sys.argv[3], 'wait': int(sys.argv[4])}))" "$AGENT" "$title" "$prompt" "$wait") call_exec "subagent.spawn" "$args" ;; pipeline) name="${1:?usage: box deploy pipeline <name>}" args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name") call_exec "pipeline.run" "$args" ;; *) echo "Usage: box deploy subagent|pipeline ..." ;; esac ;; dm) sub="${1:-help}" shift || true case "$sub" in send) to="" target="" from_agent="$AGENT" while [ $# -gt 0 ]; do case "$1" in --to) to="$2"; shift 2 ;; --target) target="$2"; shift 2 ;; --agent|--from) from_agent="$2"; shift 2 ;; *) break ;; esac done msg="${1:?usage: box dm send --to <agent> [--target <target>] <message>}" to="${to:-$AGENT}" target="${target:-$AGENT-pip}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'to': sys.argv[2], 'target': sys.argv[3], 'message': sys.argv[4]}))" "$from_agent" "$to" "$target" "$msg") call_exec "dm.send" "$args" ;; read) target="${1:-main}" limit="${2:-10}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'target': sys.argv[2], 'limit': int(sys.argv[3])}))" "$AGENT" "$target" "$limit") call_exec "dm.read" "$args" ;; *) echo "Usage: box dm send|read ..." ;; esac ;; thread) sub="${1:-list}" shift || true case "$sub" in list) target_agent="${1:-$AGENT}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1]}))" "$target_agent") call_exec "thread.list" "$args" ;; view) target_agent="$AGENT" while [ $# -gt 0 ]; do case "$1" in --agent) target_agent="$2"; shift 2 ;; *) break ;; esac done if [ $# -ge 2 ] && echo " 646 opm pip muse dev def " | grep -q " $1 "; then target_agent="$1" shift fi thread_id="${1:?usage: box thread view [<agent>] <thread_id> [limit]}" limit="${2:-15}" args=$(python3 -c "import json, sys; print(json.dumps({'agent': sys.argv[1], 'thread': sys.argv[2], 'limit': int(sys.argv[3])}))" "$target_agent" "$thread_id" "$limit") call_exec "thread.view" "$args" ;; *) echo "Usage: box thread list|view ..." ;; esac ;; cron) sub="${1:-runs}" shift || true case "$sub" in runs|list) call_exec "cron.runs" "{}" ;; status) name="${1:-heartbeat}" args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name") call_exec "cron.status" "$args" ;; view) name="${1:?usage: box cron view <name>}" args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name") call_exec "cron.view" "$args" ;; run) name="${1:?usage: box cron run <name>}" args=$(python3 -c "import json, sys; print(json.dumps({'job': sys.argv[1]}))" "$name") call_exec "cron.run" "$args" ;; *) echo "Usage: box cron runs|status|view|run ..." ;; esac ;; vars) sub="${1:-list}" shift || true case "$sub" in list) call_exec "vars.list" "{}" ;; get) name="${1:?usage: box vars get <name>}" args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1]}))" "$name") call_exec "vars.get" "$args" ;; set) name="${1:?usage: box vars set <name> <value>}" val="${2:?usage: box vars set <name> <value>}" args=$(python3 -c "import json, sys; print(json.dumps({'name': sys.argv[1], 'value': sys.argv[2]}))" "$name" "$val") call_exec "vars.set" "$args" ;; *) echo "Usage: box vars list|get|set ..." ;; esac ;; health) call_exec "health.check" "{}" ;; ping) call_exec "exec.ping" "{}" ;; ops) curl -sk -sS "${EXEC_URL%/exec}/ops" | python3 -m json.tool ;; help|--help|-h) cat <<EOF Box Relay Client — Agent CLI for autonomous coordination Usage: box subagent spawn [--title <title>] [--wait <s=0>] <prompt> box deploy subagent [--title <title>] [--wait <s=0>] <prompt> box deploy pipeline <name> box dm send --to <agent> [--target <target>] <message> box dm read [<target=main>] [<limit=10>] box thread list [<agent>] box thread view <thread_id> [<limit=15>] box cron runs box cron status [<name=heartbeat>] box cron view <name> box cron run <name> box vars list box vars get <name> box vars set <name> <value> box health box ping box ops Configuration: EXEC_URL Endpoint (default: https://exec.muse-dev.online/exec) EXEC_TOKEN Bearer token (or store in ~/.exec-token) BOX_AGENT Agent identity (646, pip, muse, opm) EOF ;; *) echo "Unknown command: $cmd (run 'box help')" exit 1 ;; esac