2026-10-07 00:25:14 +00:00
#!/usr/bin/env python3
""" tmux_auto_approver.py — Tmux worker management, worker tallies, and regex auto-approvals.
Supports:
1. Multi-socket and multi-agent discovery:
- Shared host socket: /tmp/tmux-muse.sock
- User sockets: /tmp/tmux-1000/default, /tmp/tmux-1000/lte
- Agent netns sockets: /tmp/tmux-<node>.sock (muse, pip, 646, opm, dev, def)
2. Worker Tally:
- Aggregates active sessions, windows, panes, current commands, PIDs, and runtimes.
3. Regex Auto-Approvals for on-board muse-code runs and autonomous agent workers:
- Muse Code execution prompts ( " Would you like to run the following... -> 1 " )
- A/B/C choice prompts -> " A "
- Numbered menus -> " 1 "
- y/n confirmation prompts -> " y "
- Press Enter prompts -> " Enter "
- Safety guardrails (passwords, passkeys, destructive commands are never auto-approved)
4. State persistence & audit logging:
- Desired state in .state/tmux-auto-approvals.json
- Audit log stream in logs/tmux/auto-approvals.jsonl
5. Surface linking with https://box.muse-dev.online/
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import signal
import subprocess
import sys
import time
from dataclasses import asdict , dataclass , field
from datetime import datetime , timezone
from pathlib import Path
from typing import Any , Dict , List , Optional , Tuple
REPO_ROOT = Path ( __file__ ) . resolve ( ) . parent . parent
STATE_DIR = REPO_ROOT / " .state "
LOG_DIR = REPO_ROOT / " logs " / " tmux "
STATE_FILE = STATE_DIR / " tmux-auto-approvals.json "
AUDIT_LOG_FILE = LOG_DIR / " auto-approvals.jsonl "
TMUX_BIN = " /home/super/.local/bin/tmux "
if not os . path . exists ( TMUX_BIN ) :
TMUX_BIN = " tmux "
KNOWN_SOCKETS = [
" /tmp/tmux-1000/default " ,
" /tmp/tmux-1000/lte " ,
" /tmp/tmux-muse.sock " ,
" /tmp/tmux-pip.sock " ,
" /tmp/tmux-646.sock " ,
" /tmp/tmux-opm.sock " ,
" /tmp/tmux-dev.sock " ,
" /tmp/tmux-def.sock " ,
]
FLEET_AGENTS = [ " muse " , " pip " , " 646 " , " opm " , " dev " , " def " ]
# =====================================================================
# Regex Match Rules for Terminal Prompts
# =====================================================================
@dataclass
class MatchRule :
id : str
name : str
pattern : str
response_key : str
category : str # "muse_code", "choice", "menu", "confirm", "enter"
enabled : bool = True
description : str = " "
press_enter : bool = False # whether response requires trailing Enter
# Default built-in rules
DEFAULT_RULES : List [ MatchRule ] = [
MatchRule (
id = " muse_code_run_numbered " ,
name = " Muse Code Run (Numbered) " ,
pattern = r " Would you like to run the following[ \ s \ S]*?› \ s*1 \ . \ s*Yes,? \ s*proceed " ,
response_key = " 1 " ,
category = " muse_code " ,
enabled = True ,
description = " Auto-approves ' Would you like to run the following ... › 1. Yes, proceed (y) ' " ,
press_enter = False ,
) ,
MatchRule (
id = " muse_code_run_yn " ,
name = " Muse Code Run (y/n) " ,
pattern = r " › \ s*1 \ . \ s*Yes,? \ s*proceed \ s* \ (y \ ) " ,
response_key = " 1 " ,
category = " muse_code " ,
enabled = True ,
description = " Matches active selection indicator on ' 1. Yes, proceed (y) ' " ,
press_enter = False ,
) ,
MatchRule (
id = " muse_code_allow_execution " ,
name = " Muse Code Allow Execution " ,
pattern = r " Allow \ s+execution \ s+of \ b[ \ s \ S]*? \ [y/N \ ] " ,
response_key = " y " ,
category = " muse_code " ,
enabled = True ,
description = " Approves ' Allow execution of ... [y/N] ' " ,
press_enter = True ,
) ,
MatchRule (
id = " choice_abc " ,
name = " Lettered Choice (A/B/C) " ,
pattern = r " (?i)(?:choose|choice|select|pick \ s+one|enter \ s+[A-Z] \ b)[ \ s \ S]*?^ \ s*[A-Z] \ s*[. \ ) \ -:] \ s+ \ S " ,
response_key = " A " ,
category = " choice " ,
enabled = True ,
description = " Selects choice ' A ' on lettered decision prompts " ,
press_enter = True ,
) ,
MatchRule (
id = " menu_numbered " ,
name = " Numbered Menu ((1)/(2)) " ,
pattern = r " (?i)(?:Option:|Selection:|choose|select \ s+an?|pick \ s+a \ s+number)[ \ s \ S]*?^ \ s* \ (?1 \ )? \ s+[A-Za-z] " ,
response_key = " 1 " ,
category = " menu " ,
enabled = True ,
description = " Selects option 1 on numbered choice menus " ,
press_enter = True ,
) ,
MatchRule (
id = " confirm_yn " ,
name = " Line-end y/n Confirmation " ,
pattern = r " ([yY]/[nN]| \ [[yY]/[nN] \ ]) \ s*[ \ ]:)>]? \ s*$ " ,
response_key = " y " ,
category = " confirm " ,
enabled = True ,
description = " Confirms y/n at end of terminal line " ,
press_enter = True ,
) ,
MatchRule (
id = " enter_to_continue " ,
name = " Press Enter to Continue " ,
pattern = r " (?i)(?:Press \ s+ \ [?Enter \ ]? \ s+to \ s+continue|hit \ s+enter \ s+to \ s+proceed) " ,
response_key = " Enter " ,
category = " enter " ,
enabled = True ,
description = " Sends Enter key on ' Press Enter to continue ' prompts " ,
press_enter = False ,
) ,
]
# Guardrails: NEVER auto-approve these patterns (alert human operator)
GUARDRAIL_PATTERNS = [
re . compile ( r " \ [sudo \ ] \ s+password \ s+for " , re . IGNORECASE ) ,
re . compile ( r " password \ s*: \ s*$ " , re . IGNORECASE ) ,
re . compile ( r " (passkey|private \ s+key \ s+passphrase|Enter \ s+PIN) " , re . IGNORECASE ) ,
re . compile ( r " rm \ s+-rf \ s+/(?: \ s|$) " , re . IGNORECASE ) ,
re . compile ( r " mkfs \ . " , re . IGNORECASE ) ,
]
# =====================================================================
# State & Configuration
# =====================================================================
@dataclass
class AutoApproverState :
global_enabled : bool = True
agents_enabled : Dict [ str , bool ] = field ( default_factory = lambda : { a : True for a in FLEET_AGENTS } )
sessions_enabled : Dict [ str , bool ] = field ( default_factory = dict )
rules : List [ Dict [ str , Any ] ] = field ( default_factory = lambda : [ asdict ( r ) for r in DEFAULT_RULES ] )
max_approvals_per_hour : int = 40
poll_interval : float = 1.0
updated_at : float = field ( default_factory = time . time )
def save ( self ) - > None :
STATE_DIR . mkdir ( parents = True , exist_ok = True )
self . updated_at = time . time ( )
with open ( STATE_FILE , " w " ) as f :
json . dump ( asdict ( self ) , f , indent = 2 )
@classmethod
def load ( cls ) - > " AutoApproverState " :
if not STATE_FILE . exists ( ) :
st = cls ( )
st . save ( )
return st
try :
with open ( STATE_FILE ) as f :
data = json . load ( f )
return cls ( * * data )
except Exception :
return cls ( )
# =====================================================================
# Tmux Worker Tally & Inspection
# =====================================================================
@dataclass
class TmuxPaneInfo :
socket : str
session : str
window_idx : int
pane_id : str
pane_pid : int
current_command : str
active : bool
attached : bool
title : str
agent_node : str
auto_approve : bool = True
pending_prompt : Optional [ str ] = None
matched_rule : Optional [ str ] = None
@dataclass
class TmuxWorkerTally :
total_sockets : int
total_sessions : int
total_panes : int
active_workers : int
by_agent : Dict [ str , Dict [ str , Any ] ]
panes : List [ TmuxPaneInfo ]
timestamp : str = field ( default_factory = lambda : datetime . now ( timezone . utc ) . isoformat ( ) )
def get_existing_sockets ( ) - > List [ str ] :
""" Find all existing and accessible tmux socket files. """
found = [ ]
# Check explicitly known paths
for s in KNOWN_SOCKETS :
if os . path . exists ( s ) :
found . append ( s )
# Check /tmp for other tmux-*.sock files
try :
for f in os . listdir ( " /tmp " ) :
p = os . path . join ( " /tmp " , f )
if f . startswith ( " tmux- " ) and f . endswith ( " .sock " ) and p not in found :
found . append ( p )
except Exception :
pass
# Check /tmp/tmux-1000/
t1000 = " /tmp/tmux-1000 "
if os . path . isdir ( t1000 ) :
try :
for f in os . listdir ( t1000 ) :
p = os . path . join ( t1000 , f )
if p not in found :
found . append ( p )
except Exception :
pass
return sorted ( list ( set ( found ) ) )
def infer_agent_for_session ( socket_path : str , session_name : str ) - > str :
""" Determine the owning agent (muse, pip, 646, opm, dev, def, host). """
s_lower = session_name . lower ( )
sock_lower = socket_path . lower ( )
for agent in FLEET_AGENTS :
if f " - { agent } . " in sock_lower or f " / { agent } " in sock_lower :
return agent
if s_lower == agent or s_lower . startswith ( f " { agent } - " ) or f " _ { agent } _ " in s_lower :
return agent
if " muse " in sock_lower or " muse " in s_lower :
return " muse "
return " host "
def run_tmux_cmd ( socket_path : str , * args : str , timeout : float = 3.0 ) - > Tuple [ int , str , str ] :
""" Execute tmux on a specific socket. """
cmd = [ TMUX_BIN , " -S " , socket_path ] + list ( args )
try :
res = subprocess . run ( cmd , capture_output = True , text = True , timeout = timeout )
return res . returncode , res . stdout , res . stderr
except subprocess . TimeoutExpired :
return - 1 , " " , " timeout "
except Exception as e :
return - 1 , " " , str ( e )
def capture_pane_text ( socket_path : str , pane_id : str , lines : int = 30 ) - > str :
2026-10-07 01:50:06 +00:00
""" Capture recent lines from a pane, joining wrapped rows.
-J joins physical wrapped lines into logical lines so matching is
width-independent: narrow panes wrap the same dialog onto more
rows, which otherwise breaks cue/option regexes.
"""
rc , out , _ = run_tmux_cmd ( socket_path , " capture-pane " , " -p " , " -J " ,
" -t " , pane_id , " -S " , f " - { lines } " )
2026-10-07 00:25:14 +00:00
if rc == 0 :
return out
return " "
2026-10-07 01:50:06 +00:00
MUSE_COMMAND_HINTS = ( " muse-bin " , " muse-code " )
def should_defer_to_muse_watcher ( socket_path : str , pane_id : str ,
current_command : str ) - > bool :
""" True when a per-pane muse watcher owns this pane.
Single-owner rule: muse_choice_watcher is authoritative for muse
panes (stability + re-verify + once-per-prompt + decided-block
guard). When its daemon is alive for this socket:pane, tmux must
skip the pane entirely, or both daemons answer the same prompt
within the same second ( ' 11 ' + stray keys, observed live). Never
raises: import or liveness failures mean no owner, handle here.
"""
try :
cmd = current_command or " "
if not any ( h in cmd for h in MUSE_COMMAND_HINTS ) :
return False
import muse_choice_watcher as mcw
alive = getattr ( mcw , " watcher_alive " , mcw . is_running )
return alive ( socket_path , pane_id ) is not None
except Exception :
return False
2026-10-07 00:25:14 +00:00
def gather_tmux_tally ( state : Optional [ AutoApproverState ] = None ) - > TmuxWorkerTally :
""" Scan all sockets and build a comprehensive tally of tmux workers. """
if state is None :
state = AutoApproverState . load ( )
sockets = get_existing_sockets ( )
all_panes : List [ TmuxPaneInfo ] = [ ]
agent_stats : Dict [ str , Dict [ str , Any ] ] = {
a : { " sessions " : 0 , " panes " : 0 , " active_commands " : [ ] , " auto_approve " : state . agents_enabled . get ( a , True ) }
for a in FLEET_AGENTS
}
agent_stats [ " host " ] = { " sessions " : 0 , " panes " : 0 , " active_commands " : [ ] , " auto_approve " : state . global_enabled }
total_sessions_set = set ( )
fmt = " # {session_name} ___# {window_index} ___# {pane_id} ___# {pane_pid} ___# {pane_current_command} ___# {pane_active} ___# {session_attached} ___# {pane_title} "
for sock in sockets :
rc , out , err = run_tmux_cmd ( sock , " list-panes " , " -a " , " -F " , fmt )
if rc != 0 or not out . strip ( ) :
continue
for line in out . strip ( ) . splitlines ( ) :
parts = line . split ( " ___ " )
if len ( parts ) < 8 :
continue
sess_name = parts [ 0 ]
try :
win_idx = int ( parts [ 1 ] )
p_id = parts [ 2 ]
p_pid = int ( parts [ 3 ] )
cmd_name = parts [ 4 ]
p_active = ( parts [ 5 ] == " 1 " )
s_attached = ( parts [ 6 ] == " 1 " )
p_title = parts [ 7 ]
except Exception :
continue
sess_key = f " { sock } : { sess_name } "
total_sessions_set . add ( sess_key )
agent = infer_agent_for_session ( sock , sess_name )
# Determine auto-approve state
is_auto = (
state . global_enabled
and state . agents_enabled . get ( agent , True )
and state . sessions_enabled . get ( sess_name , True )
)
pane_info = TmuxPaneInfo (
socket = sock ,
session = sess_name ,
window_idx = win_idx ,
pane_id = p_id ,
pane_pid = p_pid ,
current_command = cmd_name ,
active = p_active ,
attached = s_attached ,
title = p_title ,
agent_node = agent ,
auto_approve = is_auto ,
)
all_panes . append ( pane_info )
# Update stats
if agent in agent_stats :
agent_stats [ agent ] [ " panes " ] + = 1
if cmd_name not in agent_stats [ agent ] [ " active_commands " ] :
agent_stats [ agent ] [ " active_commands " ] . append ( cmd_name )
# Count distinct sessions per agent
for p in all_panes :
agent = p . agent_node
if agent in agent_stats :
agent_stats [ agent ] [ " sessions " ] = len ( { x . session for x in all_panes if x . agent_node == agent } )
return TmuxWorkerTally (
total_sockets = len ( sockets ) ,
total_sessions = len ( total_sessions_set ) ,
total_panes = len ( all_panes ) ,
active_workers = len ( [ p for p in all_panes if p . current_command not in ( " bash " , " sh " , " zsh " , " " ) ] ) ,
by_agent = agent_stats ,
panes = all_panes ,
)
# =====================================================================
# Regex Matcher Engine
# =====================================================================
@dataclass
class MatchVerdict :
matched : bool
rule_id : Optional [ str ] = None
rule_name : Optional [ str ] = None
category : Optional [ str ] = None
key : Optional [ str ] = None
press_enter : bool = False
excerpt : Optional [ str ] = None
reason : Optional [ str ] = None
is_blocked : bool = False
blocked_reason : Optional [ str ] = None
class RegexApproverEngine :
""" Evaluates scrollback text against active match rules and guardrails. """
def __init__ ( self , rules : Optional [ List [ MatchRule ] ] = None ) :
if rules is None :
self . rules = list ( DEFAULT_RULES )
else :
self . rules = rules
self . _compiled_rules = [ ( r , re . compile ( r . pattern , re . MULTILINE ) ) for r in self . rules if r . enabled ]
def reload ( self , rules : List [ MatchRule ] ) - > None :
self . rules = rules
self . _compiled_rules = [ ( r , re . compile ( r . pattern , re . MULTILINE ) ) for r in self . rules if r . enabled ]
def evaluate ( self , text : str , tail_lines : int = 35 ) - > MatchVerdict :
""" Evaluate terminal text and return match verdict. """
if not text :
return MatchVerdict ( matched = False , reason = " Empty text " )
lines = text . strip ( ) . splitlines ( )
tail_text = " \n " . join ( lines [ - tail_lines : ] )
# 1. Guardrail safety check (NEVER auto-approve sudo/passwords)
for guard in GUARDRAIL_PATTERNS :
m = guard . search ( tail_text )
if m :
return MatchVerdict (
matched = False ,
is_blocked = True ,
blocked_reason = f " Security guardrail triggered: ' { m . group ( 0 ) } ' " ,
excerpt = m . group ( 0 ) ,
)
# 2. Test active rules in priority order
for rule , compiled in self . _compiled_rules :
m = compiled . search ( tail_text )
if m :
excerpt = m . group ( 0 )
if len ( excerpt ) > 100 :
excerpt = excerpt [ : 100 ] + " ... "
return MatchVerdict (
matched = True ,
rule_id = rule . id ,
rule_name = rule . name ,
category = rule . category ,
key = rule . response_key ,
press_enter = rule . press_enter ,
excerpt = excerpt ,
reason = f " Matched rule ' { rule . name } ' " ,
)
return MatchVerdict ( matched = False , reason = " No matching prompt found in tail window " )
# =====================================================================
# Auto-Approval Executor & Daemon Loop
# =====================================================================
class AutoApproverRunner :
""" Monitors tmux panes, applies regex matching, and dispatches keys. """
def __init__ ( self , dry_run : bool = False ) :
self . dry_run = dry_run
self . state = AutoApproverState . load ( )
rule_objs = [ MatchRule ( * * r ) for r in self . state . rules ]
self . engine = RegexApproverEngine ( rule_objs )
self . recent_signatures : Dict [ str , Tuple [ float , str ] ] = { }
self . approval_counts : List [ float ] = [ ]
def record_audit ( self , event : Dict [ str , Any ] ) - > None :
""" Write structured audit log event. """
try :
LOG_DIR . mkdir ( parents = True , exist_ok = True )
event [ " timestamp " ] = datetime . now ( timezone . utc ) . isoformat ( )
event [ " ts " ] = time . time ( )
with open ( AUDIT_LOG_FILE , " a " ) as f :
f . write ( json . dumps ( event ) + " \n " )
except Exception :
pass
def check_rate_limit ( self ) - > bool :
""" Enforce hourly approval backstop. """
now = time . time ( )
self . approval_counts = [ t for t in self . approval_counts if now - t < 3600 ]
return len ( self . approval_counts ) < self . state . max_approvals_per_hour
def run_once ( self ) - > List [ Dict [ str , Any ] ] :
""" Scan all panes once and dispatch auto-approvals for any matched prompts. """
self . state = AutoApproverState . load ( )
if not self . state . global_enabled :
return [ { " status " : " disabled " , " message " : " Global auto-approvals are DISABLED " } ]
rule_objs = [ MatchRule ( * * r ) for r in self . state . rules ]
self . engine . reload ( rule_objs )
tally = gather_tmux_tally ( self . state )
actions_taken = [ ]
for p in tally . panes :
if not p . auto_approve :
continue
2026-10-07 01:50:06 +00:00
if should_defer_to_muse_watcher ( p . socket , p . pane_id ,
p . current_command ) :
continue
2026-10-07 00:25:14 +00:00
text = capture_pane_text ( p . socket , p . pane_id , lines = 30 )
if not text :
continue
verdict = self . engine . evaluate ( text )
if verdict . is_blocked :
self . record_audit ( {
" action " : " BLOCKED " ,
" socket " : p . socket ,
" pane " : p . pane_id ,
" session " : p . session ,
" agent " : p . agent_node ,
" reason " : verdict . blocked_reason ,
" excerpt " : verdict . excerpt ,
} )
continue
if verdict . matched and verdict . key :
2026-10-07 01:50:06 +00:00
# Deduplicate identical prompt to avoid infinite loop.
# Keyed by socket:pane: bare pane ids repeat on every
# tmux socket, so %1 on pip must not suppress %1 on opm.
2026-10-07 00:25:14 +00:00
sig = hashlib . sha1 ( f " { verdict . rule_id } : { verdict . excerpt } " . encode ( ) ) . hexdigest ( )
2026-10-07 01:50:06 +00:00
dedup_key = " %s : %s " % ( p . socket , p . pane_id )
last_time , last_sig = self . recent_signatures . get ( dedup_key , ( 0 , " " ) )
2026-10-07 00:25:14 +00:00
if last_sig == sig and ( time . time ( ) - last_time ) < 15.0 :
continue # already handled recently
if not self . check_rate_limit ( ) :
actions_taken . append ( {
" pane " : p . pane_id ,
" session " : p . session ,
" status " : " rate_limited " ,
" rule " : verdict . rule_name ,
} )
continue
# Execute key dispatch
success = False
if not self . dry_run :
args = [ " send-keys " , " -t " , p . pane_id , verdict . key ]
if verdict . press_enter or verdict . key == " Enter " :
if verdict . key != " Enter " :
args . append ( " Enter " )
rc , _ , _ = run_tmux_cmd ( p . socket , * args )
success = ( rc == 0 )
else :
success = True # dry-run simulated
now = time . time ( )
2026-10-07 01:50:06 +00:00
self . recent_signatures [ dedup_key ] = ( now , sig )
2026-10-07 00:25:14 +00:00
self . approval_counts . append ( now )
event = {
" action " : " AUTO_APPROVED " if not self . dry_run else " DRY_RUN_MATCH " ,
" socket " : p . socket ,
" pane " : p . pane_id ,
" session " : p . session ,
" agent " : p . agent_node ,
" rule_id " : verdict . rule_id ,
" rule_name " : verdict . rule_name ,
" key_sent " : verdict . key ,
" press_enter " : verdict . press_enter ,
" excerpt " : verdict . excerpt ,
" dry_run " : self . dry_run ,
" success " : success ,
}
self . record_audit ( event )
actions_taken . append ( event )
return actions_taken
def watch_loop ( self , interval : Optional [ float ] = None ) - > None :
""" Run continuous monitoring loop. """
if interval is None :
interval = self . state . poll_interval
print ( f " [*] Tmux Auto-Approver watching across sockets (interval: { interval } s, dry_run: { self . dry_run } )... " )
print ( f " [*] Audit log: { AUDIT_LOG_FILE } " )
sys . stdout . flush ( )
while True :
try :
res = self . run_once ( )
for act in res :
if act . get ( " action " ) in ( " AUTO_APPROVED " , " DRY_RUN_MATCH " ) :
print ( f " [ { datetime . now ( ) . strftime ( ' % H: % M: % S ' ) } ] ✔ { act [ ' action ' ] } on { act [ ' agent ' ] . upper ( ) } : { act [ ' session ' ] } ( { act [ ' pane ' ] } ) -> sent ' { act [ ' key_sent ' ] } ' for ' { act [ ' rule_name ' ] } ' " )
sys . stdout . flush ( )
time . sleep ( interval )
except KeyboardInterrupt :
print ( " \n [*] Exiting watch loop. " )
break
except Exception as e :
time . sleep ( interval )
# =====================================================================
# CLI Command Implementations
# =====================================================================
def cmd_tally ( args : argparse . Namespace ) - > int :
tally = gather_tmux_tally ( )
if getattr ( args , " json " , False ) :
print ( json . dumps ( asdict ( tally ) , indent = 2 ) )
return 0
print ( " ══════════════════════════════════════════════════════════════════════════════ " )
print ( f " TMUX WORKER TALLY — { tally . total_sessions } Sessions · { tally . total_panes } Panes · { tally . active_workers } Active Workers across { tally . total_sockets } Sockets " )
print ( " ══════════════════════════════════════════════════════════════════════════════ " )
# Agent breakdown table
print ( " \n AGENT WORKERS SUMMARY: " )
print ( f " { ' Agent ' : <8 } { ' Sessions ' : <10 } { ' Panes ' : <8 } { ' Auto-Approve ' : <14 } { ' Active Commands ' } " )
print ( " " + " ─ " * 70 )
for agent , info in tally . by_agent . items ( ) :
auto_str = " ENABLED [●] " if info . get ( " auto_approve " ) else " DISABLED [○] "
cmds_str = " , " . join ( info . get ( " active_commands " , [ ] ) ) or " (idle bash) "
print ( f " { agent : <8 } { info . get ( ' sessions ' , 0 ) : <10 } { info . get ( ' panes ' , 0 ) : <8 } { auto_str : <14 } { cmds_str } " )
# Detailed Pane Table
print ( " \n ACTIVE PANES & WORKERS: " )
print ( f " { ' Socket ' : <22 } { ' Session ' : <14 } { ' Pane ' : <6 } { ' PID ' : <8 } { ' Agent ' : <6 } { ' Cmd ' : <16 } { ' Auto ' : <6 } " )
print ( " " + " ─ " * 82 )
for p in tally . panes :
sock_short = os . path . basename ( p . socket )
auto_tag = " YES " if p . auto_approve else " NO "
print ( f " { sock_short : <22 } { p . session [ : 13 ] : <14 } { p . pane_id : <6 } { p . pane_pid : <8 } { p . agent_node : <6 } { p . current_command [ : 15 ] : <16 } { auto_tag : <6 } " )
print ( " " )
return 0
def cmd_status ( args : argparse . Namespace ) - > int :
st = AutoApproverState . load ( )
if getattr ( args , " json " , False ) :
print ( json . dumps ( asdict ( st ) , indent = 2 ) )
return 0
print ( " ══════════════════════════════════════════════════════════════════ " )
print ( " TMUX AUTO-APPROVAL RUNTIME STATUS " )
print ( " ══════════════════════════════════════════════════════════════════ " )
status_badge = " ENABLED [●] " if st . global_enabled else " DISABLED [○] "
print ( f " Master State: { status_badge } " )
print ( f " Max Approvals / Hour: { st . max_approvals_per_hour } " )
print ( f " Poll Interval: { st . poll_interval } s " )
print ( f " Audit Log: { AUDIT_LOG_FILE } " )
print ( f " Surface Link: https://box.muse-dev.online/ " )
print ( " \n PER-AGENT AUTO-APPROVE POLICIES: " )
for a in FLEET_AGENTS :
en = st . agents_enabled . get ( a , True )
badge = " ON [✔] " if en else " OFF [✖] "
print ( f " • { a : <6 } : { badge } " )
print ( f " \n ACTIVE REGEX RULES ( { len ( st . rules ) } ): " )
for r in st . rules :
en_str = " ON " if r . get ( " enabled " ) else " OFF "
print ( f " [ { en_str } ] { r . get ( ' id ' ) : <25 } -> sends ' { r . get ( ' response_key ' ) } ' ( { r . get ( ' category ' ) } ) " )
print ( " " )
return 0
def cmd_toggle_on ( args : argparse . Namespace ) - > int :
st = AutoApproverState . load ( )
target_node = getattr ( args , " node " , None )
target_session = getattr ( args , " session " , None )
if target_node :
st . agents_enabled [ target_node ] = True
print ( f " ✔ Enabled auto-approvals for agent: { target_node . upper ( ) } " )
elif target_session :
st . sessions_enabled [ target_session ] = True
print ( f " ✔ Enabled auto-approvals for session: ' { target_session } ' " )
else :
st . global_enabled = True
for a in FLEET_AGENTS :
st . agents_enabled [ a ] = True
print ( " ✔ Enabled master auto-approvals across all fleet agents & sessions. " )
st . save ( )
return 0
def cmd_toggle_off ( args : argparse . Namespace ) - > int :
st = AutoApproverState . load ( )
target_node = getattr ( args , " node " , None )
target_session = getattr ( args , " session " , None )
if target_node :
st . agents_enabled [ target_node ] = False
print ( f " ✖ Disabled auto-approvals for agent: { target_node . upper ( ) } " )
elif target_session :
st . sessions_enabled [ target_session ] = False
print ( f " ✖ Disabled auto-approvals for session: ' { target_session } ' " )
else :
st . global_enabled = False
print ( " ✖ Disabled master auto-approvals globally. " )
st . save ( )
return 0
def cmd_match_test ( args : argparse . Namespace ) - > int :
text = args . text
if text == " - " or not text :
text = sys . stdin . read ( )
engine = RegexApproverEngine ( )
verdict = engine . evaluate ( text )
out = {
" matched " : verdict . matched ,
" rule_id " : verdict . rule_id ,
" rule_name " : verdict . rule_name ,
" category " : verdict . category ,
" key_to_send " : verdict . key ,
" press_enter " : verdict . press_enter ,
" excerpt " : verdict . excerpt ,
" reason " : verdict . reason ,
" is_blocked " : verdict . is_blocked ,
" blocked_reason " : verdict . blocked_reason ,
}
print ( json . dumps ( out , indent = 2 ) )
return 0 if verdict . matched else 1
def cmd_run_once ( args : argparse . Namespace ) - > int :
runner = AutoApproverRunner ( dry_run = getattr ( args , " dry_run " , False ) )
res = runner . run_once ( )
print ( json . dumps ( res , indent = 2 ) )
return 0
def cmd_watch ( args : argparse . Namespace ) - > int :
runner = AutoApproverRunner ( dry_run = getattr ( args , " dry_run " , False ) )
interval = getattr ( args , " interval " , 1.0 )
runner . watch_loop ( interval = interval )
return 0
def cmd_logs ( args : argparse . Namespace ) - > int :
lines = getattr ( args , " lines " , 20 ) or 20
if not AUDIT_LOG_FILE . exists ( ) :
print ( " No auto-approval logs yet. " )
return 0
with open ( AUDIT_LOG_FILE ) as f :
all_lines = f . readlines ( )
tail = all_lines [ - lines : ]
for l in tail :
try :
d = json . loads ( l )
ts = d . get ( " timestamp " , " " ) [ : 19 ] . replace ( " T " , " " )
act = d . get ( " action " , " " )
ag = d . get ( " agent " , " " )
sess = d . get ( " session " , " " )
pane = d . get ( " pane " , " " )
key = d . get ( " key_sent " , " " )
rule = d . get ( " rule_name " , " " )
print ( f " [ { ts } ] { act : <14 } { ag . upper ( ) : <6 } { sess : <12 } ( { pane } ) -> sent ' { key } ' [ { rule } ] " )
except Exception :
print ( l . strip ( ) )
return 0
def cmd_spawn_worker ( args : argparse . Namespace ) - > int :
session = args . session
cmd = getattr ( args , " command " , " bash " )
node = getattr ( args , " node " , " muse " )
sock = f " /tmp/tmux- { node } .sock " if node != " muse " else " /tmp/tmux-muse.sock "
# Spawn session
t_cmd = [ TMUX_BIN , " -S " , sock , " new-session " , " -d " , " -s " , session , cmd ]
res = subprocess . run ( t_cmd , capture_output = True , text = True )
if res . returncode == 0 :
print ( f " ✔ Successfully spawned worker ' { session } ' on { sock } running ' { cmd } ' " )
return 0
else :
print ( f " Failed to spawn worker: { res . stderr . strip ( ) or res . stdout . strip ( ) } " , file = sys . stderr )
return res . returncode
def build_parser ( ) - > argparse . ArgumentParser :
parser = argparse . ArgumentParser ( description = " Tmux Worker Tally & Regex Auto-Approval Runtime " )
subparsers = parser . add_subparsers ( dest = " subcommand " )
# tally
p_tally = subparsers . add_parser ( " tally " , help = " Tally all tmux sessions, workers, and panes " )
p_tally . add_argument ( " --json " , action = " store_true " , help = " Output machine-readable JSON " )
p_tally . set_defaults ( func = cmd_tally )
# status
p_status = subparsers . add_parser ( " status " , help = " Show auto-approval configuration & policies " )
p_status . add_argument ( " --json " , action = " store_true " , help = " Output machine-readable JSON " )
p_status . set_defaults ( func = cmd_status )
# on / off
p_on = subparsers . add_parser ( " on " , help = " Enable auto-approvals (global, per-agent, or per-session) " )
p_on . add_argument ( " --node " , choices = FLEET_AGENTS , help = " Enable for specific agent " )
p_on . add_argument ( " --session " , help = " Enable for specific session name " )
p_on . set_defaults ( func = cmd_toggle_on )
p_off = subparsers . add_parser ( " off " , help = " Disable auto-approvals " )
p_off . add_argument ( " --node " , choices = FLEET_AGENTS , help = " Disable for specific agent " )
p_off . add_argument ( " --session " , help = " Disable for specific session name " )
p_off . set_defaults ( func = cmd_toggle_off )
# match
p_match = subparsers . add_parser ( " match " , help = " Test regex match against scrollback text " )
p_match . add_argument ( " text " , nargs = " ? " , default = " - " , help = " Input text or ' - ' for stdin " )
p_match . set_defaults ( func = cmd_match_test )
# once
p_once = subparsers . add_parser ( " once " , help = " Evaluate and auto-approve all active prompts right now " )
p_once . add_argument ( " --dry-run " , action = " store_true " , help = " Log matches without sending keys " )
p_once . set_defaults ( func = cmd_run_once )
# watch
p_watch = subparsers . add_parser ( " watch " , help = " Run background monitor daemon for auto-approvals " )
p_watch . add_argument ( " --interval " , type = float , default = 1.0 , help = " Poll interval in seconds (default: 1.0) " )
p_watch . add_argument ( " --dry-run " , action = " store_true " , help = " Log matches without sending keys " )
p_watch . set_defaults ( func = cmd_watch )
# logs
p_logs = subparsers . add_parser ( " logs " , help = " Tail auto-approval audit log stream " )
p_logs . add_argument ( " -n " , " --lines " , type = int , default = 20 , help = " Number of lines to show " )
p_logs . set_defaults ( func = cmd_logs )
# spawn
p_spawn = subparsers . add_parser ( " spawn " , help = " Spawn a new tmux worker runner " )
p_spawn . add_argument ( " session " , help = " Session name " )
p_spawn . add_argument ( " --command " , " -c " , default = " bash " , help = " Command to run " )
p_spawn . add_argument ( " --node " , choices = FLEET_AGENTS , default = " muse " , help = " Target agent socket " )
p_spawn . set_defaults ( func = cmd_spawn_worker )
return parser
def main ( argv : Optional [ List [ str ] ] = None ) - > int :
parser = build_parser ( )
if argv is None :
argv = sys . argv [ 1 : ]
if not argv :
parser . print_help ( )
return 0
args = parser . parse_args ( argv )
if not hasattr ( args , " func " ) :
parser . print_help ( )
return 1
return args . func ( args )
if __name__ == " __main__ " :
sys . exit ( main ( ) )