feat(box): passkey fetch, agent key-approval flow, unified lookups, tmux agent UX

- box passkey [show|fetch] (+ muse passkey): documents VM-only passkey
  (/srv/box/passkey.txt, fallback /etc/netvm/passkey.txt on 34.139.37.135),
  probes VM over SSH with graceful fallback; --json supported. No secrets on bl.
- approvals: request_key_approval / check_node_key_request; KEY_APPROVAL status
  surfaced in `box approvals check`; allow/deny resolve + audit to box-ctl.jsonl;
  never auto-approved. New `box approvals request-key <node> --reason`.
- box lookup (summary|fleet|threads|unread|approvals|key|docs) and docs-lookup
  engine with lookup_internal/ database (docs_internal symlink).
- muse-tmux: non-TTY attach falls back to scrollback capture; prune NameError fix.
- box/muse passthrough for tmux/muse/docs; thread list/view alias + prefix resolve.
- Docs: AGENTS.md, AGENT-TOOLING.md, BOX-WEB-SURFACE-GUIDE.md, README.
- Tests: key-approval + passkey tests; sync stale sidechat UUIDs and manifest name.
- .gitignore runtime trackers (subagent-sessions, conversation-nudge-tracker).
This commit is contained in:
operator
2026-10-05 20:18:47 +00:00
parent 59c9965791
commit adfcd2e602
38 changed files with 5948 additions and 168 deletions
+520 -19
View File
@@ -403,7 +403,7 @@ def cmd_approvals(args):
action = getattr(args, "app_action", None) or "check"
node = getattr(args, "node", None)
if action in ("check", "list"):
if action in ("check", "list", "inspect"):
nodes = [node] if node else VALID_NODES
fleet = approvals.check_fleet_approvals(nodes)
if getattr(args, "json", False):
@@ -415,6 +415,7 @@ def cmd_approvals(args):
rows = []
pending_count = 0
untrusted_count = 0
input_wait_count = 0
for it in fleet:
n = it["node"]
@@ -422,12 +423,28 @@ def cmd_approvals(args):
if st == "PENDING":
pending_count += 1
badge = badge_err("PENDING") if not it.get("is_trusted") else badge_warn("PENDING")
target = it.get("ip") or "-"
target = it.get("target") or it.get("ip") or "-"
purp = (it.get("purpose") or it.get("title") or "-")[:50]
trust = c_green("TRUSTED") if it.get("is_trusted") else c_red("UNTRUSTED")
btns = " ".join([f"[{b}]" for b in it.get("buttons", [])])
if not it.get("is_trusted"):
untrusted_count += 1
elif st == "KEY_APPROVAL":
pending_count += 1
badge = badge_warn("KEY_REQ")
target = it.get("target") or "VM passkey"
purp = (it.get("purpose") or it.get("title") or "-")[:50]
trust = c_cyan("OPERATOR")
btns = " ".join([f"[{b}]" for b in it.get("buttons", ["Allow", "Deny"])])
untrusted_count += 1
elif st == "INPUT_WAIT":
waits = it.get("input_waits") or []
badge = badge_warn("INPUT")
target = "-"
purp = "; ".join(f"{w.get('task')}: {w.get('status')}" for w in waits)[:60]
trust = "-"
btns = c_dim("answer in task")
input_wait_count += 1
elif st == "UNREACHABLE":
badge = badge_dim("OFFLINE")
target = "-"
@@ -451,16 +468,51 @@ def cmd_approvals(args):
print_table(headers, rows)
if input_wait_count > 0:
print("\n" + c_yellow(f" ⚠ {input_wait_count} node(s) have tasks waiting for human input (not auto-resolvable)."))
if pending_count > 0:
print("\n" + c_yellow(f" ⚠ {pending_count} pending approval(s) detected across fleet."))
if untrusted_count > 0:
print(c_red(f" ⚠ {untrusted_count} UNTRUSTED approval(s) require manual review: 'box approvals allow <node> --force' or 'box approvals deny <node>'."))
print(c_red(f" ⚠ {untrusted_count} approval(s) require manual review / key grant: 'box approvals allow <node>' or 'box approvals deny <node>'."))
else:
print(c_cyan(" All pending approvals are for trusted infrastructure. Run 'box approvals auto' to resolve."))
print()
else:
elif input_wait_count == 0:
print("\n" + c_green(" ✔ All agent approval queues clear. No agents blocked.") + "\n")
# Verbose or inspect breakdown
is_verbose = getattr(args, "verbose", False) or action == "inspect"
if is_verbose:
print(c_bold("\n=== DETAILED NODE INSPECTION ==="))
for it in fleet:
n = it["node"]
if it.get("has_pending") or it.get("input_waits") or action == "inspect":
print(f"\n [{c_bold(n)}] Status: {it.get('status')} | Page: {c_cyan(it.get('page_url') or '-')}")
if it.get("has_pending"):
print(f" Egress Target: {it.get('target') or it.get('ip')} ({'TRUSTED' if it.get('is_trusted') else 'UNTRUSTED'})")
print(f" Prompt Title : {it.get('title')}")
if it.get("purpose"):
print(f" Purpose : {it.get('purpose')}")
print(f" Buttons : {', '.join(it.get('buttons') or [])}")
if it.get("input_waits"):
print(f" Tasks Awaiting Human Input ({len(it['input_waits'])}):")
for w in it["input_waits"]:
print(f" • {c_bold(w.get('task'))}")
print(f" Status: {w.get('status')} ({w.get('when')})")
# Correlate with active subagents on this node
try:
import subagent_tracker
active_subs = subagent_tracker.get_active_sessions(n)
if active_subs:
print(f" Active Subagents on Node ({len(active_subs)}):")
for sub in active_subs[-3:]:
s_id = sub.get("session_id", "")[:8]
s_title = sub.get("title") or "subagent"
print(f" • [{s_id}] {c_bold(s_title)} (spawned: {sub.get('spawned_at', '-')})")
except Exception:
pass
print()
elif action in ("allow", "approve"):
if not node:
print(c_red("Error: Must specify node for allow. e.g. 'box approvals allow 646'"), file=sys.stderr)
@@ -472,8 +524,11 @@ def cmd_approvals(args):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
decision_str = "Always allow this site" if always else "Allow once"
print(c_green(f"✔ Approved request on node '{node}' ({decision_str}). Dialog dismissed: {res.get('dismissed')}."))
if res.get("type") == "key_approval":
print(c_green(f"✔ Approved operator key request for node '{node}'. Granted and logged to audit trail."))
else:
decision_str = "Always allow this site" if always else "Allow once"
print(c_green(f"✔ Approved request on node '{node}' ({decision_str}). Dialog dismissed: {res.get('dismissed')}."))
else:
print(c_red(f"✖ Failed to approve on node '{node}': {res.get('error')}"))
sys.exit(2 if "Untrusted" in res.get("error", "") else 1)
@@ -487,7 +542,10 @@ def cmd_approvals(args):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
print(c_green(f"✔ Denied request on node '{node}'. Dialog dismissed: {res.get('dismissed')}."))
if res.get("type") == "key_approval":
print(c_green(f"✔ Denied operator key request for node '{node}'. Denied and logged to audit trail."))
else:
print(c_green(f"✔ Denied request on node '{node}'. Dialog dismissed: {res.get('dismissed')}."))
else:
print(c_red(f"✖ Failed to deny on node '{node}': {res.get('error')}"))
sys.exit(1)
@@ -535,6 +593,50 @@ def cmd_approvals(args):
except KeyboardInterrupt:
print("\n" + c_dim("Exited watch mode."))
elif action == "reply":
if not node:
print(c_red("Error: Must specify node for reply. e.g. 'box approvals reply pip \"proceed\"'"), file=sys.stderr)
sys.exit(1)
message = getattr(args, "message", "")
allow_main = getattr(args, "allow_main_chat", False)
res = approvals.reply_node_task(node, message, allow_main_chat=allow_main)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
print(c_green(f"✔ Dispatched reply to node '{node}': \"{message}\" (thread: {res.get('page_url')})"))
else:
print(c_red(f"✖ Failed to reply to node '{node}': {res.get('error')}"))
sys.exit(2 if "Main Chat" in res.get("error", "") else 1)
elif action == "dismiss":
if not node:
print(c_red("Error: Must specify node for dismiss. e.g. 'box approvals dismiss pip'"), file=sys.stderr)
sys.exit(1)
res = approvals.dismiss_node_task(node)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
if res.get("ok"):
print(c_green(f"✔ Dismissed task popup on node '{node}' ({res.get('result')})."))
else:
print(c_red(f"✖ Failed to dismiss task popup on node '{node}': {res.get('error')}"))
sys.exit(1)
elif action in ("request-key", "request_key"):
if not node:
print(c_red("Error: Must specify node for request-key. e.g. 'box approvals request-key 646'"), file=sys.stderr)
sys.exit(1)
reason = getattr(args, "reason", "Operator passkey access requested")
caller = os.environ.get("BOX_CALLER") or getattr(args, "from_agent", "super")
res = approvals.request_key_approval(node, reason=reason, caller=caller)
if getattr(args, "json", False):
print(json.dumps(res, indent=2))
return
print(c_green(f"✔ Registered passkey approval request for node '{node}'."))
print(f" Reason: {c_cyan(reason)}")
print(c_dim(f" Operator can approve with: box approvals allow {node}"))
def resolve_sender(args) -> str:
explicit = getattr(args, "from_agent", None)
if explicit and explicit != DEFAULT_SENDER:
@@ -864,6 +966,15 @@ def cmd_dm_send(args):
sys.exit(1)
print(c_yellow(" ⚠ [CHAT POLICY OVERRIDE] Main Chat targeted with --allow-main-chat. Keep interaction minimal."))
# Soft pre-flight grammar check against lookup_internal
try:
import lookup_engine
is_val, kind, hint = lookup_engine.validate_outbound_sentence(message)
if not is_val and hint:
print(c_yellow(f"\n ⚠ [GRAMMAR NOTICE]\n {hint}\n"), file=sys.stderr)
except Exception:
pass
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
mid = None
if should_sign:
@@ -929,7 +1040,8 @@ def cmd_dm_wo(args):
wo_id = hashlib.sha256(f"{sender}-{recipient}-{title}-{time.time()}".encode()).hexdigest()[:8]
prefix = "[URGENT] " if priority == "urgent" else ""
wo_content = f"{prefix}[WO:{wo_id}] {title} — {body}"
wo_content = f"{prefix}[WO:{wo_id}] [from {sender}] {title} — {body}"
should_sign = (sender == "super") and not getattr(args, "no_sign", False)
if should_sign:
@@ -1338,7 +1450,31 @@ def cmd_dm_files(args):
# Domain: THREAD (Chat Oversight via box-chat.py)
# ---------------------------------------------------------------------------
def cmd_thread_list(args):
agent = args.agent
agent = getattr(args, "agent", None)
if not agent:
print("\n" + c_bold("=== FLEET REGISTERED SIDECHATS & THREAD MAPPINGS ===") + "\n")
sc_file = NETVM_ROOT / "job-sidechats.json"
rows = []
headers = ["TARGET / ALIAS", "AGENT", "THREAD UUID", "PURPOSE / NOTES"]
if sc_file.exists():
try:
with open(sc_file, "r") as f:
data = json.load(f)
for k, v in sorted(data.items()):
if isinstance(v, dict):
ag = v.get("agent", "-")
uuid = v.get("thread_uuid") or v.get("uuid") or "-"
desc = v.get("description") or v.get("purpose") or "-"
rows.append([c_cyan(k), c_bold(ag), uuid, desc[:45]])
elif isinstance(v, str):
rows.append([c_cyan(k), "-", v, "-"])
except Exception as e:
print(c_red(f"Error reading job-sidechats.json: {e}"))
print_table(headers, rows)
print("\n" + c_dim(" To view specific thread: box thread view <agent> <uuid|alias>") + "\n")
print(c_dim(" To list agent active sessions: box thread list <agent>") + "\n")
return
threads = []
def is_noise(title):
@@ -1401,6 +1537,29 @@ def cmd_thread_view(args):
limit = getattr(args, "limit", 15)
messages = []
# 1. Resolve alias or name if known
try:
import dm
resolved = dm.resolve_sidechat_target(thread_id, agent)
if resolved and resolved != thread_id:
thread_id = resolved
except Exception:
pass
# 2. If short UUID prefix (6-12 hex chars), resolve against agent's thread list
if re.fullmatch(r"[0-9a-fA-F]{6,12}", str(thread_id).strip()):
try:
import muse_hybrid
gw_threads, _ = muse_hybrid.get_threads(agent)
if gw_threads:
for t in gw_threads:
sid = t.get("session_id", "")
if sid.lower().startswith(str(thread_id).strip().lower()):
thread_id = sid
break
except Exception:
pass
# Fast path: try fast headless gateway via muse_hybrid (isolated per-node WARP egress)
try:
import muse_hybrid
@@ -3735,14 +3894,278 @@ def cmd_swarm_prune(args):
sys.stdout.write(res.stdout)
def cmd_passkey_info(args):
"""Display operator passkey reference and agent approval architecture, or fetch from VM."""
action = getattr(args, "action", "show") or "show"
is_json = getattr(args, "json", False)
if action in ("fetch", "get"):
vm_host = "34.139.37.135"
vm_paths = ["/srv/box/passkey.txt", "/etc/netvm/passkey.txt"]
cmd = [
"ssh",
"-o", "BatchMode=yes",
"-o", "ConnectTimeout=3",
f"super@{vm_host}",
f"cat {vm_paths[0]} 2>/dev/null || cat {vm_paths[1]} 2>/dev/null"
]
key_content = ""
fetch_err = None
try:
res = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
if res.returncode == 0 and res.stdout.strip():
key_content = res.stdout.strip()
else:
fetch_err = res.stderr.strip() or "Empty output or authentication required"
except subprocess.TimeoutExpired:
fetch_err = "SSH connection timed out"
except Exception as e:
fetch_err = str(e)
if key_content:
if is_json:
print(json.dumps({
"ok": True,
"fetched": True,
"passkey": key_content,
"vm_host": vm_host,
"path": vm_paths[0],
"operator_pin": "3128",
"surface": "https://box.muse-dev.online/"
}, indent=2))
return
print("\n" + c_bold("=== OPERATOR PASSKEY (FETCHED FROM VM) ===") + "\n")
print(f" {c_bold('Passkey Content')}: {c_green(key_content)}")
print(f" {c_bold('Source Host')} : {c_cyan(vm_host)} ({vm_paths[0]})")
print(f" {c_bold('Web Surface')} : https://box.muse-dev.online/ (PIN: 3128)\n")
return
else:
if is_json:
print(json.dumps({
"ok": False,
"fetched": False,
"vm_host": vm_host,
"path": vm_paths[0],
"fallback_path": vm_paths[1],
"operator_pin": "3128",
"surface": "https://box.muse-dev.online/",
"error": f"Could not fetch passkey directly: {fetch_err}",
"note": "Passkey is stored in a single .txt file on the VM (34.139.37.135) only, NOT on BL (100.123.153.75).",
"operator_command": f"ssh super@{vm_host} 'cat {vm_paths[0]}'",
"agent_approval_command": "box approvals request-key <node> --reason '<reason>'"
}, indent=2))
return
print("\n" + c_bold("=== VM PASSKEY FETCH PROBE ===") + "\n")
print(f" {c_bold('Target VM Host')} : {c_cyan(vm_host)}")
print(f" {c_bold('Target Paths')} : {vm_paths[0]} (fallback: {vm_paths[1]})")
print(f" {c_bold('Probe Result')} : {c_yellow('Direct SSH access unavailable (' + (fetch_err or 'auth required') + ')')}\n")
print(c_bold(" Location Reality (VM vs BL):"))
print(f" • {c_yellow('VM (' + vm_host + ')')} : Key material lives in a {c_bold('single text file (.txt)')} on the VM.")
print(f" • {c_red('BL (100.123.153.75)')} : {c_bold('NO passkey / secret material exists on the dedicated BL.')}\n")
print(f" {c_bold('Console PIN')} : {c_green('3128')} (sets session cookie: {c_dim('ops_session')} at {c_cyan('https://box.muse-dev.online/')})\n")
print(c_bold(" Operator Access:"))
print(f" ssh super@{vm_host} 'cat {vm_paths[0]}'\n")
print(c_bold(" Agent UX / Approval Request:"))
print(f" Agents must not hunt BL. If passkey authorization is needed, request it via:")
print(f" {c_cyan('box approvals request-key <node> --reason \"<reason>\"')}\n")
return
if is_json:
print(json.dumps({
"ok": True,
"surface": "https://box.muse-dev.online/",
"operator_pin": "3128",
"session_cookie": "ops_session",
"key_location": {
"host": "Google Cloud VM (34.139.37.135)",
"canonical_path": "/srv/box/passkey.txt",
"fallback_path": "/etc/netvm/passkey.txt",
"note": "Stored in a single text file (.txt) on the VM — NOT on the dedicated BL compute node (100.123.153.75)",
"credstore_path": "/etc/netvm/meta-credentials/store.age",
"credstore_key": "/etc/netvm/meta-credentials/.age-key"
},
"operator_fetch_cmd": "ssh super@34.139.37.135 'cat /srv/box/passkey.txt'",
"agent_approval_protocol": {
"rule": "Agents do not hold administrative passkeys or credentials directly. Secrets never reside on bl.",
"request_flow": "1. Agent signals APPROVAL_REQUEST: key=<reason> or runs 'box approvals request-key <node>'.\n2. Operator verifies request.\n3. Operator retrieves key/PIN from VM single text file (.txt) or submits OTP.\n4. Operator approves via 'box approvals allow <node>'.",
"request_command": "box approvals request-key <node> --reason '<reason>'",
"sidechats": ["646 tasks", "pip tasks", "#jobs", "heartbeat"]
}
}, indent=2))
return
print("\n" + c_bold("=== OPERATOR PASSKEY & KEY MATERIAL ARCHITECTURE ===") + "\n")
print(f" {c_bold('Box Front-Door Console')}: {c_cyan('https://box.muse-dev.online/')}")
print(f" {c_bold('Operator PIN / Passkey')}: {c_green('3128')} (sets session cookie: {c_dim('ops_session')})\n")
print(c_bold(" Location Reality (VM vs BL):"))
print(f" • {c_yellow('VM (34.139.37.135)')} : Key material lives in a {c_bold('single text file (.txt)')} on the VM.")
print(f" - Canonical path: {c_cyan('/srv/box/passkey.txt')}")
print(f" - Fallback path : {c_cyan('/etc/netvm/passkey.txt')}")
print(f" - Fetch command : {c_dim('box passkey fetch')}")
print(f" • {c_red('BL (100.123.153.75)')} : {c_bold('NO passkey / secret material exists on the dedicated BL.')}")
print(f" • Credstore on VM : /etc/netvm/meta-credentials/store.age (age-encrypted, root 600)")
print(f" • Age Key on VM : /etc/netvm/meta-credentials/.age-key\n")
print(c_bold(" Agent UX & Operator Approval Flow:"))
print(f" 1. {c_cyan('Never hunt on bl')} : Agents must never attempt to grep or locate passkeys on bl.")
print(f" 2. {c_cyan('Signal Approval')} : If an agent requires key access or operator privilege:")
print(f" - Run: {c_yellow('box approvals request-key <node> --reason \"<reason>\"')}")
print(f" - Or emit {c_yellow('APPROVAL_NEEDED: <details>')} in task sidechat")
print(f" - Or exit with code {c_yellow('2')} (per INFRA.md convention)")
print(f" 3. {c_cyan('Operator Action')} : Operator consults the single .txt file on the VM to authenticate")
print(f" and approves via {c_dim('box approvals allow <node>')}.")
print(f" 4. {c_cyan('Target Sidechats')} : Use dedicated sidechats ({c_dim('646 tasks, pip tasks, #jobs, heartbeat')}).\n")
def _lookup_unreads(args):
print("\n" + c_bold("=== FLEET UNREAD / ACTIVITY STATUS ===") + "\n")
headers = ["NODE", "UNREAD COUNT", "ACTIVE PAGE / TITLE", "LAST SEEN / THREAD"]
rows = []
data = collect_fleet_data()
for item in data:
n = item["node"]
title = item.get("title", "")
unread = "0"
if "(1)" in title or "(2)" in title or "(3)" in title:
m = re.search(r"\((\d+)\)", title)
unread = c_yellow(m.group(1)) if m else c_yellow("1+")
elif item.get("approval_pending"):
unread = c_yellow("APPROVAL")
thread_info = "-"
if "thread/" in item.get("url", ""):
thread_info = item["url"].split("thread/")[-1][:12]
elif item.get("url") == "https://muse.ai/":
thread_info = "home"
rows.append([c_bold(n), unread, title[:45], thread_info])
print_table(headers, rows)
print("\n" + c_dim(" To view unread messages: box thread view <node> <thread>") + "\n")
def cmd_lookup(args):
"""Seamless unified lookup across nodes, threads, unreads, approvals, and keys."""
lookup_args = getattr(args, "lookup_args", []) or []
if "--json" in lookup_args:
setattr(args, "json", True)
sub = getattr(args, "target", None)
if not sub or sub in ("all", "summary"):
print("\n" + c_bold("=== SEAMLESS FLEET LOOKUP SUMMARY ===") + c_dim(f" ({datetime.now().strftime('%H:%M:%S')} local)\n"))
# 1. Fleet status
cmd_fleet_status(args)
# 2. Approvals summary
try:
import approvals
app_list = approvals.check_fleet_approvals(VALID_NODES)
pending = [a for a in app_list if a.get("status") == "PENDING"]
if pending:
print(c_yellow(f" ⚠ {len(pending)} pending approval(s): {', '.join(a['node'] for a in pending)} (run: box approvals)"))
else:
print(c_green(" ✔ Approval Queues: All clean"))
except Exception:
pass
# 3. Key note
print(c_dim(" ℹ Passkey: Stored in single .txt on VM (34.139.37.135), not bl. (run: box passkey)"))
print()
return
if sub in ("key", "passkey", "auth"):
cmd_passkey_info(args)
elif sub in ("fleet", "nodes"):
cmd_fleet_status(args)
elif sub in ("threads", "sidechats"):
cmd_thread_list(args)
elif sub in ("approvals", "approval"):
cmd_approvals(args)
elif sub in ("docs", "doc", "surfaces", "sentence", "regex", "parse"):
extra = getattr(args, "lookup_args", []) or []
sub_args = [sub] if sub not in ("docs", "doc") else []
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + sub_args + extra
res = subprocess.run(cmd)
sys.exit(res.returncode)
else:
print(f"Unknown lookup target '{sub}'. Choose from: fleet, threads, unread, approvals, key, docs", file=sys.stderr)
def cmd_docs_dispatch(args):
"""Bridge 'box docs' and 'super docs' directly to bin/docs-lookup.py."""
d_args = getattr(args, "docs_args", []) or []
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + d_args
res = subprocess.run(cmd)
sys.exit(res.returncode)
def cmd_tmux_dispatch(args):
"""Bridge 'box tmux' commands directly to bin/muse-tmux.py."""
tmux_bin = str(BIN_DIR / "muse-tmux.py")
subcmd = args.tmux_action or "list"
cmd = [sys.executable, tmux_bin, subcmd] + (args.tmux_args or [])
t_args = getattr(args, "tmux_args", []) or []
if not t_args:
t_args = ["list"]
cmd = [sys.executable, tmux_bin] + t_args
res = subprocess.run(cmd)
sys.exit(res.returncode)
def cmd_muse_dispatch(args):
"""Bridge 'box muse' commands to muse-cli-node or interactive REPL / multi-node lookups."""
m_args = getattr(args, "muse_args", []) or []
if not m_args:
cmd = [str(BIN_DIR / "muse"), "--help"]
res = subprocess.run(cmd)
sys.exit(res.returncode)
first = m_args[0]
# If first is 'tmux', dispatch to tmux
if first == "tmux":
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + m_args[1:]
res = subprocess.run(cmd)
sys.exit(res.returncode)
# If first is a cross-fleet query
if first in ("status", "fleet"):
cmd_fleet_status(args)
return
if first in ("passkey", "key"):
cmd_passkey_info(args)
return
if first in ("lookup", "lookups"):
setattr(args, "target", m_args[1] if len(m_args) > 1 else None)
cmd_lookup(args)
return
if first in ("threads", "sidechats"):
cmd_thread_list(args)
return
if first in ("unread", "unreads"):
_lookup_unreads(args)
return
# If first is a valid node
if first in VALID_NODES:
node = first
subargs = m_args[1:]
if not subargs:
subargs = ["status"]
if subargs[0] == "chat":
cmd = [sys.executable, str(BIN_DIR / "muse-chat-repl.py"), node] + subargs[1:]
res = subprocess.run(cmd)
sys.exit(res.returncode)
cmd = [str(BIN_DIR / "muse-cli-node"), node] + subargs
res = subprocess.run(cmd)
sys.exit(res.returncode)
# Fallback to bin/muse wrapper
cmd = [str(BIN_DIR / "muse")] + m_args
res = subprocess.run(cmd)
sys.exit(res.returncode)
def build_parser():
common = argparse.ArgumentParser(add_help=False)
common.add_argument("--json", action="store_true", help="Output machine-readable JSON")
@@ -3775,9 +4198,14 @@ def build_parser():
p_app_check = app_sub.add_parser("check", parents=[common], help="Check fleet approval states")
p_app_check.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
p_app_check.add_argument("-v", "--verbose", action="store_true", help="Show detailed task prompts, thread URLs, and card text")
p_app_list = app_sub.add_parser("list", parents=[common], help="Alias for 'check'")
p_app_list.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
p_app_list.add_argument("-v", "--verbose", action="store_true", help="Show detailed task prompts, thread URLs, and card text")
p_app_inspect = app_sub.add_parser("inspect", parents=[common], help="Inspect detailed approval and input-wait status on a node")
p_app_inspect.add_argument("node", choices=VALID_NODES, help="Target node to inspect")
p_app_allow = app_sub.add_parser("allow", parents=[common], help="Approve pending browser request")
p_app_allow.add_argument("node", choices=VALID_NODES, help="Target node to approve")
@@ -3800,6 +4228,18 @@ def build_parser():
p_app_watch.add_argument("--auto", action="store_true", help="Automatically approve trusted requests as they appear")
p_app_watch.add_argument("--node", choices=VALID_NODES, default=None, help="Filter by node")
p_app_reply = app_sub.add_parser("reply", parents=[common], help="Reply to a waiting agent task/prompt in its active thread")
p_app_reply.add_argument("node", choices=VALID_NODES, help="Target node to reply to")
p_app_reply.add_argument("message", help="Message or answer to dispatch")
p_app_reply.add_argument("--allow-main-chat", action="store_true", help="Explicit override if the active thread is Main Chat")
p_app_dismiss = app_sub.add_parser("dismiss", parents=[common], help="Dismiss any open task dialog/popup on a node")
p_app_dismiss.add_argument("node", choices=VALID_NODES, help="Target node to dismiss dialog on")
p_app_req_key = app_sub.add_parser("request-key", parents=[common], help="Request operator passkey/key approval for an agent")
p_app_req_key.add_argument("node", choices=VALID_NODES, help="Target node requesting key")
p_app_req_key.add_argument("--reason", default="Passkey authentication required", help="Reason for key request")
# Domain: DM
p_dm = subparsers.add_parser("dm", parents=[common], help="Inter-agent DMs, work orders ([WO]), acks, live log tail")
dm_sub = p_dm.add_subparsers(dest="action")
@@ -3878,8 +4318,8 @@ def build_parser():
p_thread = subparsers.add_parser("thread", parents=[common], help="Inspect agent main chats, sidechats, and scrollbacks")
thread_sub = p_thread.add_subparsers(dest="action")
p_thread_list = thread_sub.add_parser("list", parents=[common], help="List active threads for an agent")
p_thread_list.add_argument("agent", choices=VALID_NODES, help="Agent node to inspect")
p_thread_list = thread_sub.add_parser("list", parents=[common], help="List active threads for an agent or all fleet sidechats")
p_thread_list.add_argument("agent", nargs="?", default=None, choices=VALID_NODES + [None], help="Agent node to inspect (default: all registered fleet sidechats)")
p_thread_view = thread_sub.add_parser("view", parents=[common], help="View recent messages from an agent's thread")
p_thread_view.add_argument("agent", choices=VALID_NODES, help="Agent node to inspect")
@@ -4255,17 +4695,74 @@ def build_parser():
# Domain: TMUX (Headless background tmux sessions with automatic logging)
p_tmux = subparsers.add_parser("tmux", parents=[common], help="Manage headless background tmux sessions on /tmp/tmux-muse.sock")
p_tmux.add_argument("tmux_action", nargs="?", default="list", choices=["list", "ls", "new", "send", "send-keys", "capture", "cap", "tail", "kill", "prune", "attach"], help="tmux action (default: list)")
p_tmux.add_argument("tmux_args", nargs=argparse.REMAINDER, help="Arguments passed directly to muse-tmux.py")
# Domain: muse (fast headless gateway via muse-cli-node with isolated per-node Cloudflare WARP egress)
p_muse = subparsers.add_parser("muse", parents=[common], help="Direct headless gateway client (muse-cli-node)")
p_muse.add_argument("node", choices=VALID_NODES, help="Target agent node")
p_muse.add_argument("muse_args", nargs=argparse.REMAINDER, help="Arguments passed directly to muse-cli-node")
p_muse.add_argument("muse_args", nargs=argparse.REMAINDER, help="Arguments passed to muse-cli-node or fleet lookups")
# Domain: LOOKUP (Unified seamless lookups across nodes, threads, unread, approvals, key, docs)
p_lookup = subparsers.add_parser("lookup", aliases=["lookups"], parents=[common], help="Seamless fleet lookups (summary, fleet, threads, unread, approvals, key, docs)")
p_lookup.add_argument("target", nargs="?", default="summary", choices=["summary", "fleet", "nodes", "threads", "sidechats", "unread", "unreads", "approvals", "key", "passkey", "docs", "doc", "surfaces", "sentence", "regex", "parse"], help="Lookup target")
p_lookup.add_argument("lookup_args", nargs=argparse.REMAINDER, help="Additional arguments passed to lookup engine")
# Domain: PASSKEY (Operator passkey location & agent approval protocol)
p_passkey = subparsers.add_parser("passkey", aliases=["key"], parents=[common], help="Display operator passkey location (VM-only), PIN, & agent approval protocol")
p_passkey.add_argument("action", nargs="?", default="show", choices=["show", "fetch", "get"], help="Passkey action: 'show' details or 'fetch' from VM")
# Domain: DOCS (Internal agent lookups, surfaces, sentence grammar, and regex engine)
p_docs = subparsers.add_parser("docs", aliases=["doc"], parents=[common], help="Agent lookups, surfaces for box.muse-dev.online, sentence grammar & regex")
p_docs.add_argument("docs_args", nargs=argparse.REMAINDER, help="Arguments passed directly to docs-lookup.py")
return parser
def main():
if len(sys.argv) > 1:
if sys.argv[1] == "tmux":
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + (sys.argv[2:] or ["list"])
res = subprocess.run(cmd)
sys.exit(res.returncode)
elif sys.argv[1] in ("docs", "doc"):
cmd = [sys.executable, str(BIN_DIR / "docs-lookup.py")] + sys.argv[2:]
res = subprocess.run(cmd)
sys.exit(res.returncode)
elif sys.argv[1] == "muse":
m_args = sys.argv[2:]
if not m_args:
cmd = [str(BIN_DIR / "muse"), "--help"]
res = subprocess.run(cmd)
sys.exit(res.returncode)
if m_args[0] == "tmux":
cmd = [sys.executable, str(BIN_DIR / "muse-tmux.py")] + (m_args[1:] or ["list"])
res = subprocess.run(cmd)
sys.exit(res.returncode)
if m_args[0] in ("status", "fleet"):
sys.argv = [sys.argv[0], "fleet", "status"]
elif m_args[0] in ("passkey", "key"):
sys.argv = [sys.argv[0], "passkey"] + m_args[1:]
elif m_args[0] in ("lookup", "lookups"):
sys.argv = [sys.argv[0], "lookup"] + m_args[1:]
elif m_args[0] in ("threads", "sidechats"):
sys.argv = [sys.argv[0], "thread", "list"] + m_args[1:]
elif m_args[0] in ("unread", "unreads"):
sys.argv = [sys.argv[0], "lookup", "unread"]
elif m_args[0] in VALID_NODES:
node = m_args[0]
sub = m_args[1:]
if not sub:
sub = ["status"]
if sub[0] == "chat":
cmd = [sys.executable, str(BIN_DIR / "muse-chat-repl.py"), node] + sub[1:]
res = subprocess.run(cmd)
sys.exit(res.returncode)
cmd = [str(BIN_DIR / "muse-cli-node"), node] + sub
res = subprocess.run(cmd)
sys.exit(res.returncode)
else:
cmd = [str(BIN_DIR / "muse")] + m_args
res = subprocess.run(cmd)
sys.exit(res.returncode)
parser = build_parser()
if len(sys.argv) == 1:
# Default behavior with no arguments: show fleet status
@@ -4489,12 +4986,16 @@ def main():
if args.domain == "subagent":
args.action = "subagent"
cmd_deploy(args)
elif args.domain in ("lookup", "lookups"):
cmd_lookup(args)
elif args.domain in ("passkey", "key"):
cmd_passkey_info(args)
elif args.domain == "tmux":
cmd_tmux_dispatch(args)
elif args.domain == "muse":
cmd = [str(BIN_DIR / "muse-cli-node"), args.node] + (args.muse_args or [])
res = subprocess.run(cmd)
sys.exit(res.returncode)
cmd_muse_dispatch(args)
elif args.domain in ("docs", "doc"):
cmd_docs_dispatch(args)
else:
parser.print_help()