Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
124 lines
3.9 KiB
Python
124 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Chat Bash Executor - Safe, limited bash command execution
|
|
Only allows read-only system status commands
|
|
"""
|
|
|
|
import subprocess
|
|
import time
|
|
from typing import Dict
|
|
|
|
|
|
class ChatBashExecutor:
|
|
"""Execute safe read-only bash commands for chat interface"""
|
|
|
|
# Whitelist of allowed commands (read-only only)
|
|
ALLOWED_COMMANDS = {
|
|
'uptime': 'uptime',
|
|
'load': 'cat /proc/loadavg',
|
|
'disk': 'df -h /',
|
|
'memory': 'free -h',
|
|
'services': 'systemctl --no-pager list-units --type=service --all',
|
|
'active_services': 'systemctl --no-pager list-units --type=service --state=running',
|
|
'failed_services': 'systemctl --no-pager list-units --type=service --state=failed',
|
|
'ps': 'ps aux | head -20',
|
|
'docker_ps': 'docker ps',
|
|
'docker_stats': 'docker stats --no-stream',
|
|
'nginx_status': 'systemctl --no-pager status nginx',
|
|
'date': 'date',
|
|
'hostname': 'hostname',
|
|
'whoami': 'whoami',
|
|
'pwd': 'pwd',
|
|
'ls_home': 'ls -lah /home/admin | head -20',
|
|
'du_home': 'du -sh /home/admin/* 2>/dev/null | sort -h',
|
|
}
|
|
|
|
def __init__(self, timeout_ms: int = 300):
|
|
"""Initialize with execution timeout"""
|
|
self.timeout_ms = timeout_ms
|
|
self.timeout_seconds = timeout_ms / 1000.0
|
|
|
|
def execute(self, command_name: str) -> Dict:
|
|
"""Execute a whitelisted command"""
|
|
if command_name not in self.ALLOWED_COMMANDS:
|
|
return {
|
|
'error': f'Command "{command_name}" not allowed',
|
|
'allowed_commands': list(self.ALLOWED_COMMANDS.keys())
|
|
}
|
|
|
|
command = self.ALLOWED_COMMANDS[command_name]
|
|
|
|
try:
|
|
start_time = time.time()
|
|
|
|
result = subprocess.run(
|
|
command,
|
|
shell=True,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=self.timeout_seconds
|
|
)
|
|
|
|
execution_time_ms = (time.time() - start_time) * 1000
|
|
|
|
return {
|
|
'command': command_name,
|
|
'success': result.returncode == 0,
|
|
'output': result.stdout.strip(),
|
|
'error': result.stderr.strip() if result.stderr else None,
|
|
'exit_code': result.returncode,
|
|
'execution_time_ms': round(execution_time_ms, 2)
|
|
}
|
|
|
|
except subprocess.TimeoutExpired:
|
|
return {
|
|
'command': command_name,
|
|
'error': f'Command timed out after {self.timeout_ms}ms',
|
|
'success': False
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'command': command_name,
|
|
'error': str(e),
|
|
'success': False
|
|
}
|
|
|
|
def system_status(self) -> Dict:
|
|
"""Quick system status summary"""
|
|
status = {
|
|
'timestamp': time.time(),
|
|
'components': {}
|
|
}
|
|
|
|
for check_name in ['uptime', 'load', 'disk', 'memory']:
|
|
result = self.execute(check_name)
|
|
status['components'][check_name] = {
|
|
'success': result.get('success', False),
|
|
'output': result.get('output', '')[:200] # First 200 chars
|
|
}
|
|
|
|
return status
|
|
|
|
def list_allowed_commands(self) -> Dict:
|
|
"""List all allowed commands"""
|
|
return {
|
|
'allowed_commands': [
|
|
{'name': name, 'description': cmd}
|
|
for name, cmd in self.ALLOWED_COMMANDS.items()
|
|
],
|
|
'count': len(self.ALLOWED_COMMANDS),
|
|
'timeout_ms': self.timeout_ms
|
|
}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import json
|
|
executor = ChatBashExecutor()
|
|
|
|
print("System Status:")
|
|
print(json.dumps(executor.system_status(), indent=2, default=str))
|
|
print()
|
|
|
|
print("Uptime:")
|
|
print(json.dumps(executor.execute('uptime'), indent=2))
|