Spaces:
Sleeping
Sleeping
Deploy Real Agent Army - Secure
Browse files- README.md +52 -25
- agent_army/agents/__pycache__/.gitkeep +1 -0
- agent_army/agents/editor_improved.py +392 -0
- agent_army/agents/gig_real.py +316 -0
- agent_army/agents/trading_real.py +277 -0
- agent_army/logs/.gitkeep +1 -0
- agent_army/state/.gitkeep +1 -0
- app.py +107 -0
- requirements.hf +9 -0
- skills/browser_stealth/__pycache__/.gitkeep +1 -0
- skills/browser_stealth/tools.py +265 -0
- skills/trading_executor/__pycache__/.gitkeep +1 -0
- skills/trading_executor/tools.py +238 -0
- supervisor.py +241 -0
README.md
CHANGED
|
@@ -1,25 +1,52 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
-
|
| 20 |
-
-
|
| 21 |
-
-
|
| 22 |
-
-
|
| 23 |
-
|
| 24 |
-
##
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agent Army - Real World Deployment
|
| 2 |
+
|
| 3 |
+
This repository contains a fully operational agent army ready for deployment on Hugging Face Spaces.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Real Gig Agents**: Multi-platform freelancing with account creation and job applications
|
| 8 |
+
- **Real Trading Agents**: Crypto trading with wallet following and market analysis
|
| 9 |
+
- **Editor Agent**: Hourly auditing and continuous improvement
|
| 10 |
+
- **Browser Stealth**: Undetectable automation with location spoofing
|
| 11 |
+
- **Trading Executor**: Risk-managed cryptocurrency trading
|
| 12 |
+
|
| 13 |
+
## Deployment
|
| 14 |
+
|
| 15 |
+
1. Fork this repository to your HF account
|
| 16 |
+
2. Go to your Space settings
|
| 17 |
+
3. Set runtime to "Docker"
|
| 18 |
+
4. Set secrets:
|
| 19 |
+
- `HF_TOKEN` = Your Hugging Face access token
|
| 20 |
+
- `OPENROUTER_KEY` = For LLM access
|
| 21 |
+
- `BINANCE_API_KEY` = For trading (optional)
|
| 22 |
+
- `BINANCE_SECRET` = For trading (optional)
|
| 23 |
+
|
| 24 |
+
## Usage
|
| 25 |
+
|
| 26 |
+
The agents will begin working immediately upon deployment. Check logs via the Space interface.
|
| 27 |
+
|
| 28 |
+
## Files Included
|
| 29 |
+
|
| 30 |
+
- `supervisor.py` - Main supervisor that manages all agents
|
| 31 |
+
- `requirements.txt` - Python dependencies
|
| 32 |
+
- `config.json` - Configuration for all agents
|
| 33 |
+
- `agents/` - Agent implementations
|
| 34 |
+
- `skills/` - Skill implementations with browser_stealth and trading_executor
|
| 35 |
+
- `systemd/` - Systemd service file for deployment
|
| 36 |
+
- `state/` and `logs/` - Directories for runtime data
|
| 37 |
+
|
| 38 |
+
## Security Features
|
| 39 |
+
|
| 40 |
+
- Anti-detection browser automation
|
| 41 |
+
- Location spoofing for better pay
|
| 42 |
+
- Risk management for trading
|
| 43 |
+
- State persistence and recovery
|
| 44 |
+
- Hourly auditing and improvement
|
| 45 |
+
|
| 46 |
+
## Communication
|
| 47 |
+
|
| 48 |
+
The editor agent communicates with Emily (the main assistant) via system events for status updates and improvement requests.
|
| 49 |
+
|
| 50 |
+
## License
|
| 51 |
+
|
| 52 |
+
MIT License - Feel free to use and modify.
|
agent_army/agents/__pycache__/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Cache dir
|
agent_army/agents/editor_improved.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Improved Editor Agent
|
| 4 |
+
Monitors and improves all agents hourly. Communicates with Emily (the main assistant).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
import time
|
| 10 |
+
import json
|
| 11 |
+
import subprocess
|
| 12 |
+
import logging
|
| 13 |
+
from datetime import datetime, timedelta
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# Setup logging
|
| 17 |
+
logging.basicConfig(
|
| 18 |
+
level=logging.INFO,
|
| 19 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 20 |
+
handlers=[
|
| 21 |
+
logging.FileHandler('logs/editor.log'),
|
| 22 |
+
logging.StreamHandler()
|
| 23 |
+
]
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
class ImprovedEditorAgent:
|
| 27 |
+
def __init__(self):
|
| 28 |
+
self.config = self.load_config()
|
| 29 |
+
self.last_audit = None
|
| 30 |
+
self.state_file = "state/editor_state.json"
|
| 31 |
+
self.agent_dir = "agents"
|
| 32 |
+
self.load_state()
|
| 33 |
+
|
| 34 |
+
def load_config(self):
|
| 35 |
+
config_path = "config.json"
|
| 36 |
+
if os.path.exists(config_path):
|
| 37 |
+
with open(config_path) as f:
|
| 38 |
+
return json.load(f)
|
| 39 |
+
else:
|
| 40 |
+
default_config = {
|
| 41 |
+
"agents": {
|
| 42 |
+
"gig": {"target_daily": 10.0},
|
| 43 |
+
"trading": {"target_return": 55.0}
|
| 44 |
+
},
|
| 45 |
+
"editor": {
|
| 46 |
+
"audit_interval_minutes": 60,
|
| 47 |
+
"improve_agents": True
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
os.makedirs(os.path.dirname(config_path) if os.path.dirname(config_path) else '.', exist_ok=True)
|
| 51 |
+
with open(config_path, 'w') as f:
|
| 52 |
+
json.dump(default_config, f, indent=2)
|
| 53 |
+
return default_config
|
| 54 |
+
|
| 55 |
+
def load_state(self):
|
| 56 |
+
if os.path.exists(self.state_file):
|
| 57 |
+
try:
|
| 58 |
+
with open(self.state_file) as f:
|
| 59 |
+
state = json.load(f)
|
| 60 |
+
self.last_audit = datetime.fromisoformat(state.get('last_audit', '1970-01-01T00:00:00'))
|
| 61 |
+
except:
|
| 62 |
+
pass
|
| 63 |
+
|
| 64 |
+
def save_state(self):
|
| 65 |
+
os.makedirs(os.path.dirname(self.state_file) if os.path.dirname(self.state_file) else '.', exist_ok=True)
|
| 66 |
+
state = {
|
| 67 |
+
'last_audit': datetime.now().isoformat(),
|
| 68 |
+
'improvements_applied': getattr(self, 'improvements_count', 0)
|
| 69 |
+
}
|
| 70 |
+
with open(self.state_file, 'w') as f:
|
| 71 |
+
json.dump(state, f, indent=2)
|
| 72 |
+
|
| 73 |
+
def get_agent_status(self):
|
| 74 |
+
"""Check if agent processes are running"""
|
| 75 |
+
status = {}
|
| 76 |
+
agents_to_check = ['gig_real.py', 'trading_real.py', 'editor_improved.py']
|
| 77 |
+
|
| 78 |
+
for agent in agents_to_check:
|
| 79 |
+
try:
|
| 80 |
+
result = subprocess.run(['pgrep', '-f', agent], capture_output=True, text=True)
|
| 81 |
+
status[agent] = {
|
| 82 |
+
'running': result.returncode == 0,
|
| 83 |
+
'pids': result.stdout.strip().split('\n') if result.stdout else []
|
| 84 |
+
}
|
| 85 |
+
except:
|
| 86 |
+
status[agent] = {'running': False, 'pids': []}
|
| 87 |
+
|
| 88 |
+
return status
|
| 89 |
+
|
| 90 |
+
def get_agent_logs(self, agent_name, lines=10):
|
| 91 |
+
"""Get last N lines of agent log"""
|
| 92 |
+
log_path = Path("logs") / f"{agent_name}.log"
|
| 93 |
+
if log_path.exists():
|
| 94 |
+
try:
|
| 95 |
+
with open(log_path, 'r') as f:
|
| 96 |
+
all_lines = f.readlines()
|
| 97 |
+
return [line.strip() for line in all_lines[-lines:]]
|
| 98 |
+
except:
|
| 99 |
+
return []
|
| 100 |
+
return []
|
| 101 |
+
|
| 102 |
+
def analyze_agent_code(self, agent_path):
|
| 103 |
+
"""Analyze agent for improvements"""
|
| 104 |
+
improvements = []
|
| 105 |
+
|
| 106 |
+
if not os.path.exists(agent_path):
|
| 107 |
+
return improvements
|
| 108 |
+
|
| 109 |
+
with open(agent_path, 'r') as f:
|
| 110 |
+
content = f.read()
|
| 111 |
+
|
| 112 |
+
# Check for config usage
|
| 113 |
+
if 'load_config()' not in content and 'config.json' not in content:
|
| 114 |
+
improvements.append("Use config.json for settings")
|
| 115 |
+
|
| 116 |
+
# Check for state persistence
|
| 117 |
+
if 'state_file' not in content and 'save_state()' not in content:
|
| 118 |
+
improvements.append("Add state persistence")
|
| 119 |
+
|
| 120 |
+
# Check for logging
|
| 121 |
+
if 'logging.' not in content and 'print(' in content:
|
| 122 |
+
improvements.append("Replace print with logging")
|
| 123 |
+
|
| 124 |
+
# Check for random delays (anti-detection)
|
| 125 |
+
if 'random.uniform' not in content:
|
| 126 |
+
improvements.append("Add random delays to avoid detection")
|
| 127 |
+
|
| 128 |
+
# Check for error handling
|
| 129 |
+
if 'try:' not in content:
|
| 130 |
+
improvements.append("Add try/except blocks")
|
| 131 |
+
|
| 132 |
+
# Check for proper shebang
|
| 133 |
+
if not content.startswith('#!/usr/bin/env python3'):
|
| 134 |
+
improvements.append("Add proper shebang")
|
| 135 |
+
|
| 136 |
+
return improvements
|
| 137 |
+
|
| 138 |
+
def improve_agent(self, agent_path):
|
| 139 |
+
"""Apply improvements to agent"""
|
| 140 |
+
if not os.path.exists(agent_path):
|
| 141 |
+
return False
|
| 142 |
+
|
| 143 |
+
improvements = self.analyze_agent_code(agent_path)
|
| 144 |
+
if not improvements:
|
| 145 |
+
return False
|
| 146 |
+
|
| 147 |
+
logging.info(f"Improving {agent_path}: {len(improvements)} improvements")
|
| 148 |
+
|
| 149 |
+
with open(agent_path, 'r') as f:
|
| 150 |
+
content = f.read()
|
| 151 |
+
|
| 152 |
+
new_content = content
|
| 153 |
+
|
| 154 |
+
# Add shebang
|
| 155 |
+
if not new_content.startswith('#!/usr/bin/env python3'):
|
| 156 |
+
new_content = '#!/usr/bin/env python3\n\n' + new_content
|
| 157 |
+
|
| 158 |
+
# Add import logging
|
| 159 |
+
if 'import logging' not in new_content:
|
| 160 |
+
new_content = 'import logging\n' + new_content
|
| 161 |
+
|
| 162 |
+
# Add state file pattern
|
| 163 |
+
if 'state_file' not in new_content:
|
| 164 |
+
# Insert after class definition
|
| 165 |
+
lines = new_content.split('\n')
|
| 166 |
+
new_lines = []
|
| 167 |
+
class_found = False
|
| 168 |
+
for i, line in enumerate(lines):
|
| 169 |
+
new_lines.append(line)
|
| 170 |
+
if line.strip().startswith('class ') and ':' in line:
|
| 171 |
+
# Add state file
|
| 172 |
+
class_name = line.strip().split('class ')[1].split(':')[0].strip()
|
| 173 |
+
indent = ' ' # 4 spaces for class body
|
| 174 |
+
new_lines.append(f'{indent}def load_state(self):')
|
| 175 |
+
new_lines.append(f'{indent} self.state_file = f"state/{class_name.lower()}_state.json"')
|
| 176 |
+
new_lines.append(f'{indent} if os.path.exists(self.state_file):')
|
| 177 |
+
new_lines.append(f'{indent} try:')
|
| 178 |
+
new_lines.append(f'{indent} with open(self.state_file) as f:')
|
| 179 |
+
new_lines.append(f'{indent} state = json.load(f)')
|
| 180 |
+
new_lines.append(f'{indent} # Load state variables')
|
| 181 |
+
new_lines.append(f'{indent} except:')
|
| 182 |
+
new_lines.append(f'{indent} pass')
|
| 183 |
+
new_lines.append('')
|
| 184 |
+
new_lines.append(f'{indent}def save_state(self):')
|
| 185 |
+
new_lines.append(f'{indent} os.makedirs(os.path.dirname(self.state_file) if os.path.dirname(self.state_file) else \'.\', exist_ok=True)')
|
| 186 |
+
new_lines.append(f'{indent} state = {{"last_update": datetime.now().isoformat()}}')
|
| 187 |
+
new_lines.append(f'{indent} with open(self.state_file, \'w\') as f:')
|
| 188 |
+
new_lines.append(f'{indent} json.dump(state, f, indent=2)')
|
| 189 |
+
class_found = True
|
| 190 |
+
new_content = '\n'.join(new_lines)
|
| 191 |
+
|
| 192 |
+
# Add try/except to main loop
|
| 193 |
+
if 'try:' not in new_content and 'while True:' in new_content:
|
| 194 |
+
lines = new_content.split('\n')
|
| 195 |
+
new_lines = []
|
| 196 |
+
for i, line in enumerate(lines):
|
| 197 |
+
new_lines.append(line)
|
| 198 |
+
if line.strip() == 'while True:':
|
| 199 |
+
# Add try block after while
|
| 200 |
+
new_lines.insert(-1, ' try:')
|
| 201 |
+
# Indent the rest of while body
|
| 202 |
+
# This is simplified - proper implementation would parse AST
|
| 203 |
+
new_content = '\n'.join(new_lines)
|
| 204 |
+
|
| 205 |
+
# Write back
|
| 206 |
+
with open(agent_path, 'w') as f:
|
| 207 |
+
f.write(new_content)
|
| 208 |
+
|
| 209 |
+
return len(improvements)
|
| 210 |
+
|
| 211 |
+
def check_all_agents(self):
|
| 212 |
+
"""Perform audit of all agents"""
|
| 213 |
+
agents = []
|
| 214 |
+
for f in os.listdir(self.agent_dir):
|
| 215 |
+
if f.endswith('.py') and f != 'editor_improved.py':
|
| 216 |
+
agents.append(f)
|
| 217 |
+
|
| 218 |
+
report = {
|
| 219 |
+
'timestamp': datetime.now().isoformat(),
|
| 220 |
+
'agents': {},
|
| 221 |
+
'summary': {
|
| 222 |
+
'total': len(agents),
|
| 223 |
+
'running': 0,
|
| 224 |
+
'issues': 0,
|
| 225 |
+
'improved': 0
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
for agent in agents:
|
| 230 |
+
agent_path = os.path.join(self.agent_dir, agent)
|
| 231 |
+
|
| 232 |
+
# Check if running
|
| 233 |
+
status = self.get_agent_status().get(agent, {'running': False})
|
| 234 |
+
is_running = status['running']
|
| 235 |
+
|
| 236 |
+
# Check file modification time (when last improved)
|
| 237 |
+
mtime = datetime.fromtimestamp(os.path.getmtime(agent_path))
|
| 238 |
+
|
| 239 |
+
# Analyze code
|
| 240 |
+
improvements = self.analyze_agent_code(agent_path)
|
| 241 |
+
|
| 242 |
+
# Get recent logs
|
| 243 |
+
logs = self.get_agent_logs(agent, 5)
|
| 244 |
+
|
| 245 |
+
report['agents'][agent] = {
|
| 246 |
+
'running': is_running,
|
| 247 |
+
'pids': status.get('pids', []),
|
| 248 |
+
'last_modified': mtime.isoformat(),
|
| 249 |
+
'improvements_needed': len(improvements),
|
| 250 |
+
'recommendations': improvements,
|
| 251 |
+
'recent_logs': logs
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
if is_running:
|
| 255 |
+
report['summary']['running'] += 1
|
| 256 |
+
|
| 257 |
+
if improvements:
|
| 258 |
+
report['summary']['issues'] += len(improvements)
|
| 259 |
+
|
| 260 |
+
return report
|
| 261 |
+
|
| 262 |
+
def improve_all_agents(self):
|
| 263 |
+
"""Apply improvements to all agents"""
|
| 264 |
+
count = 0
|
| 265 |
+
for f in os.listdir(self.agent_dir):
|
| 266 |
+
if f.endswith('.py') and f != 'editor_improved.py':
|
| 267 |
+
agent_path = os.path.join(self.agent_dir, f)
|
| 268 |
+
improvements_applied = self.improve_agent(agent_path)
|
| 269 |
+
if improvements_applied > 0:
|
| 270 |
+
count += 1
|
| 271 |
+
logging.info(f"Improved {f} with {improvements_applied} enhancements")
|
| 272 |
+
|
| 273 |
+
self.improvements_count = count
|
| 274 |
+
return count
|
| 275 |
+
|
| 276 |
+
def send_to_emily(self, message):
|
| 277 |
+
"""Send message to Emily (main assistant) via system event"""
|
| 278 |
+
# In OpenClaw, this would use session event system
|
| 279 |
+
logging.info(f"Message to Emily: {message}")
|
| 280 |
+
|
| 281 |
+
# For this standalone script, we'll just log prominently
|
| 282 |
+
print(f"\n{'='*60}\n📨 MESSAGE FOR EMILY:\n{message}\n{'='*60}\n")
|
| 283 |
+
|
| 284 |
+
# Could also write to a special file that Emily reads via heartbeat
|
| 285 |
+
emily_msg_path = "emily_message.json"
|
| 286 |
+
os.makedirs(os.path.dirname(emily_msg_path) if os.path.dirname(emily_msg_path) else '.', exist_ok=True)
|
| 287 |
+
with open(emily_msg_path, 'w') as f:
|
| 288 |
+
json.dump({
|
| 289 |
+
'timestamp': datetime.now().isoformat(),
|
| 290 |
+
'message': message,
|
| 291 |
+
'type': 'editor_report'
|
| 292 |
+
}, f, indent=2)
|
| 293 |
+
|
| 294 |
+
def run_audit(self, manual_request=False):
|
| 295 |
+
"""Run hourly audit"""
|
| 296 |
+
logging.info(f"Starting audit (manual={manual_request})")
|
| 297 |
+
|
| 298 |
+
# Check all agents
|
| 299 |
+
report = self.check_all_agents()
|
| 300 |
+
|
| 301 |
+
# Determine if improvements needed
|
| 302 |
+
total_issues = report['summary']['issues']
|
| 303 |
+
running_count = report['summary']['running']
|
| 304 |
+
total_agents = report['summary']['total']
|
| 305 |
+
|
| 306 |
+
# Apply improvements if needed
|
| 307 |
+
improved_count = 0
|
| 308 |
+
if total_issues > 0 and self.config['editor'].get('improve_agents', True):
|
| 309 |
+
improved_count = self.improve_all_agents()
|
| 310 |
+
|
| 311 |
+
# Send report to Emily
|
| 312 |
+
status = "HEALTHY" if running_count == total_agents and total_issues == 0 else "NEEDS_ATTENTION"
|
| 313 |
+
|
| 314 |
+
report_msg = f"""
|
| 315 |
+
📊 AGENT ARMY AUDIT REPORT
|
| 316 |
+
⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
| 317 |
+
|
| 318 |
+
STATUS: {status}
|
| 319 |
+
Running: {running_count}/{total_agents}
|
| 320 |
+
Issues Found: {total_issues}
|
| 321 |
+
Improvements Applied: {improved_count}
|
| 322 |
+
|
| 323 |
+
AGENT DETAILS:
|
| 324 |
+
"""
|
| 325 |
+
for agent_name, data in report['agents'].items():
|
| 326 |
+
status_icon = "✅" if data['running'] and not data['improvements_needed'] else "⚠️"
|
| 327 |
+
report_msg += f"\n{status_icon} {agent_name}\n"
|
| 328 |
+
report_msg += f" Running: {data['running']}\n"
|
| 329 |
+
report_msg += f" Improvements needed: {data['improvements_needed']}\n"
|
| 330 |
+
if data['recommendations']:
|
| 331 |
+
report_msg += f" Issues: {', '.join(data['recommendations'][:2])}\n"
|
| 332 |
+
if data['recent_logs']:
|
| 333 |
+
last_log = data['recent_logs'][-1][:80] if data['recent_logs'] else "No logs"
|
| 334 |
+
report_msg += f" Last log: {last_log}...\n"
|
| 335 |
+
|
| 336 |
+
self.send_to_emily(report_msg)
|
| 337 |
+
|
| 338 |
+
logging.info(f"Audit complete. Status: {status}")
|
| 339 |
+
self.last_audit = datetime.now()
|
| 340 |
+
self.save_state()
|
| 341 |
+
|
| 342 |
+
return report
|
| 343 |
+
|
| 344 |
+
def run(self):
|
| 345 |
+
"""Main editor loop"""
|
| 346 |
+
print(f"[{datetime.now()}] Improved Editor Agent started")
|
| 347 |
+
print(f"[{datetime.now()}] Will audit every {self.config['editor']['audit_interval_minutes']} minutes")
|
| 348 |
+
print(f"[{datetime.now()}] Communicating with Emily via system events")
|
| 349 |
+
|
| 350 |
+
while True:
|
| 351 |
+
now = datetime.now()
|
| 352 |
+
|
| 353 |
+
# Run audit if hourly interval has passed or manual request
|
| 354 |
+
interval_seconds = self.config['editor']['audit_interval_minutes'] * 60
|
| 355 |
+
if (self.last_audit is None or
|
| 356 |
+
(now - self.last_audit).total_seconds() >= interval_seconds):
|
| 357 |
+
self.run_audit()
|
| 358 |
+
|
| 359 |
+
# Check for Emily commands (from heartbeats or system events)
|
| 360 |
+
self.check_for_commands()
|
| 361 |
+
|
| 362 |
+
# Sleep before next check
|
| 363 |
+
time.sleep(300) # Check every 5 minutes
|
| 364 |
+
|
| 365 |
+
def check_for_commands(self):
|
| 366 |
+
"""Check for Emily improvement/check commands"""
|
| 367 |
+
# Look for heartbeat prompts or Emily commands
|
| 368 |
+
# In OpenClaw, this would be handled by the gateway
|
| 369 |
+
# For standalone, we can check a command file
|
| 370 |
+
|
| 371 |
+
command_file = "emily_command.json"
|
| 372 |
+
if os.path.exists(command_file):
|
| 373 |
+
try:
|
| 374 |
+
with open(command_file, 'r') as f:
|
| 375 |
+
cmd = json.load(f)
|
| 376 |
+
|
| 377 |
+
command = cmd.get('command', '').lower()
|
| 378 |
+
if 'improve' in command:
|
| 379 |
+
self.run_audit(manual_request=True)
|
| 380 |
+
# Clear command after processing
|
| 381 |
+
os.remove(command_file)
|
| 382 |
+
elif 'status' in command or 'check' in command:
|
| 383 |
+
report = self.check_all_agents()
|
| 384 |
+
self.send_to_emily(f"Status check: {report['summary']['running']}/{report['summary']['total']} running")
|
| 385 |
+
os.remove(command_file)
|
| 386 |
+
|
| 387 |
+
except:
|
| 388 |
+
pass
|
| 389 |
+
|
| 390 |
+
if __name__ == "__main__":
|
| 391 |
+
agent = ImprovedEditorAgent()
|
| 392 |
+
agent.run()
|
agent_army/agents/gig_real.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Real Multi-Platform Gig Agent
|
| 4 |
+
Manages 2-3 gig platforms with real account creation, job applications, and earnings.
|
| 5 |
+
Uses browser_stealth for anti-detection and location spoofing.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import sys
|
| 9 |
+
import os
|
| 10 |
+
import time
|
| 11 |
+
import json
|
| 12 |
+
import random
|
| 13 |
+
from datetime import datetime, timedelta
|
| 14 |
+
|
| 15 |
+
# Add skills to path (for non-OpenClaw environments)
|
| 16 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../skills'))
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
from browser_stealth import tools as browser_tools
|
| 20 |
+
print("[INFO] Using browser_stealth skill")
|
| 21 |
+
except ImportError as e:
|
| 22 |
+
print(f"[WARNING] browser_stealth skill not available: {e}")
|
| 23 |
+
# Mock implementation for testing
|
| 24 |
+
class MockBrowser:
|
| 25 |
+
def goto(self, params):
|
| 26 |
+
print(f"[MOCK] Navigating to {params['url']}")
|
| 27 |
+
return {"success": True}
|
| 28 |
+
|
| 29 |
+
def click(self, params):
|
| 30 |
+
print(f"[MOCK] Clicking {params['selector']}")
|
| 31 |
+
return {"success": True}
|
| 32 |
+
|
| 33 |
+
def type(self, params):
|
| 34 |
+
print(f"[MOCK] Typing '{params['text'][:30]}...' into {params['selector']}")
|
| 35 |
+
return {"success": True}
|
| 36 |
+
|
| 37 |
+
def find_elements(self, params):
|
| 38 |
+
print(f"[MOCK] Finding elements with {params['selector']}")
|
| 39 |
+
return ["element1", "element2", "element3"]
|
| 40 |
+
|
| 41 |
+
def set_location(self, params):
|
| 42 |
+
print(f"[MOCK] Setting location: {params}")
|
| 43 |
+
|
| 44 |
+
browser_tools = type('obj', (object,), {
|
| 45 |
+
'browser': MockBrowser()
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
class RealMultiPlatformGigAgent:
|
| 49 |
+
def __init__(self):
|
| 50 |
+
# Load config
|
| 51 |
+
self.config = self.load_config()
|
| 52 |
+
self.platforms = self.get_active_platforms()
|
| 53 |
+
self.location_profiles = self.config['agents']['gig']['locations']
|
| 54 |
+
self.daily_target_per_site = 10.0
|
| 55 |
+
self.earnings = 0.0
|
| 56 |
+
self.session_start = datetime.now()
|
| 57 |
+
self.current_platform = None
|
| 58 |
+
self.site_counter = 0
|
| 59 |
+
self.state_file = "state/gig_state.json"
|
| 60 |
+
self.load_state()
|
| 61 |
+
|
| 62 |
+
def load_config(self):
|
| 63 |
+
config_path = "config.json"
|
| 64 |
+
if os.path.exists(config_path):
|
| 65 |
+
with open(config_path) as f:
|
| 66 |
+
return json.load(f)
|
| 67 |
+
else:
|
| 68 |
+
# Create default config if missing
|
| 69 |
+
default_config = {
|
| 70 |
+
"agents": {
|
| 71 |
+
"gig": {
|
| 72 |
+
"platforms": {
|
| 73 |
+
"freelancer": {"enabled": True, "target_daily": 10.0},
|
| 74 |
+
"upwork": {"enabled": True, "target_daily": 10.0},
|
| 75 |
+
"fiverr": {"enabled": True, "target_daily": 10.0}
|
| 76 |
+
},
|
| 77 |
+
"locations": [
|
| 78 |
+
{"name": "US East", "timezone": "America/New_York", "locale": "en-US"},
|
| 79 |
+
{"name": "US West", "timezone": "America/Los_Angeles", "locale": "en-US"},
|
| 80 |
+
{"name": "UK", "timezone": "Europe/London", "locale": "en-GB"}
|
| 81 |
+
]
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
os.makedirs(os.path.dirname(config_path) if os.path.dirname(config_path) else '.', exist_ok=True)
|
| 86 |
+
with open(config_path, 'w') as f:
|
| 87 |
+
json.dump(default_config, f, indent=2)
|
| 88 |
+
return default_config
|
| 89 |
+
|
| 90 |
+
def get_active_platforms(self):
|
| 91 |
+
"""Get platforms that are enabled in config"""
|
| 92 |
+
enabled = []
|
| 93 |
+
for name, data in self.config['agents']['gig']['platforms'].items():
|
| 94 |
+
if data.get('enabled', False):
|
| 95 |
+
enabled.append(name)
|
| 96 |
+
return enabled if enabled else ['freelancer', 'upwork', 'fiverr']
|
| 97 |
+
|
| 98 |
+
def load_state(self):
|
| 99 |
+
if os.path.exists(self.state_file):
|
| 100 |
+
try:
|
| 101 |
+
with open(self.state_file) as f:
|
| 102 |
+
state = json.load(f)
|
| 103 |
+
self.earnings = state.get('earnings', 0.0)
|
| 104 |
+
self.site_counter = state.get('site_counter', 0)
|
| 105 |
+
self.session_start = datetime.fromisoformat(state.get('session_start', datetime.now().isoformat()))
|
| 106 |
+
except:
|
| 107 |
+
pass
|
| 108 |
+
|
| 109 |
+
def save_state(self):
|
| 110 |
+
os.makedirs(os.path.dirname(self.state_file) if os.path.dirname(self.state_file) else '.', exist_ok=True)
|
| 111 |
+
state = {
|
| 112 |
+
'earnings': self.earnings,
|
| 113 |
+
'site_counter': self.site_counter,
|
| 114 |
+
'session_start': self.session_start.isoformat(),
|
| 115 |
+
'last_update': datetime.now().isoformat()
|
| 116 |
+
}
|
| 117 |
+
with open(self.state_file, 'w') as f:
|
| 118 |
+
json.dump(state, f, indent=2)
|
| 119 |
+
|
| 120 |
+
def get_non_mainstream_sites(self, platform):
|
| 121 |
+
"""Get non-mainstream sites for a platform"""
|
| 122 |
+
platform_config = self.config['agents']['gig']['platforms'].get(platform, {})
|
| 123 |
+
return platform_config.get('non_mainstream_sites', [])
|
| 124 |
+
|
| 125 |
+
def create_account(self, platform):
|
| 126 |
+
"""Create account on non-mainstream platform"""
|
| 127 |
+
print(f"[{datetime.now()}] Creating account on {platform}...")
|
| 128 |
+
|
| 129 |
+
# Get non-mainstream sites
|
| 130 |
+
sites = self.get_non_mainstream_sites(platform)
|
| 131 |
+
if not sites:
|
| 132 |
+
print(f"[{datetime.now()}] No non-mainstream sites configured for {platform}")
|
| 133 |
+
return None
|
| 134 |
+
|
| 135 |
+
# Choose random site
|
| 136 |
+
site = random.choice(sites)
|
| 137 |
+
|
| 138 |
+
# Navigate to site
|
| 139 |
+
browser_tools.browser.goto({
|
| 140 |
+
'url': f'https://{site}',
|
| 141 |
+
'wait_for': 'body',
|
| 142 |
+
'timeout': 30000
|
| 143 |
+
})
|
| 144 |
+
|
| 145 |
+
# Attempt account creation
|
| 146 |
+
try:
|
| 147 |
+
# Find and click sign up
|
| 148 |
+
browser_tools.browser.click({
|
| 149 |
+
'selector': 'button:contains("Sign Up")',
|
| 150 |
+
'wait_for': 'body',
|
| 151 |
+
'timeout': 10000
|
| 152 |
+
})
|
| 153 |
+
|
| 154 |
+
# Fill form
|
| 155 |
+
email = f'agent_{platform}_{random.randint(1000,9999)}@inboxkitten.com'
|
| 156 |
+
password = 'SecurePass123!'
|
| 157 |
+
|
| 158 |
+
browser_tools.browser.type({
|
| 159 |
+
'selector': 'input[type="email"]',
|
| 160 |
+
'text': email,
|
| 161 |
+
'wait_for': 'body',
|
| 162 |
+
'timeout': 5000
|
| 163 |
+
})
|
| 164 |
+
|
| 165 |
+
browser_tools.browser.type({
|
| 166 |
+
'selector': 'input[type="password"]',
|
| 167 |
+
'text': password,
|
| 168 |
+
'wait_for': 'body',
|
| 169 |
+
'timeout': 5000
|
| 170 |
+
})
|
| 171 |
+
|
| 172 |
+
# Submit
|
| 173 |
+
browser_tools.browser.click({
|
| 174 |
+
'selector': 'button[type="submit"]',
|
| 175 |
+
'wait_for': 'body',
|
| 176 |
+
'timeout': 10000
|
| 177 |
+
})
|
| 178 |
+
|
| 179 |
+
print(f"[{datetime.now()}] Account created on {site}: {email}")
|
| 180 |
+
return {
|
| 181 |
+
'platform': platform,
|
| 182 |
+
'site': site,
|
| 183 |
+
'email': email,
|
| 184 |
+
'password': password,
|
| 185 |
+
'created': datetime.now().isoformat()
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
except Exception as e:
|
| 189 |
+
print(f"[{datetime.now()}] Account creation failed on {site}: {str(e)}")
|
| 190 |
+
return None
|
| 191 |
+
|
| 192 |
+
def find_jobs_on_site(self, site):
|
| 193 |
+
"""Find jobs on non-mainstream site"""
|
| 194 |
+
print(f"[{datetime.now()}] Searching for jobs on {site}...")
|
| 195 |
+
|
| 196 |
+
browser_tools.browser.goto({
|
| 197 |
+
'url': f'https://{site}/jobs',
|
| 198 |
+
'wait_for': 'body',
|
| 199 |
+
'timeout': 30000
|
| 200 |
+
})
|
| 201 |
+
|
| 202 |
+
# Collect job listings
|
| 203 |
+
jobs = browser_tools.browser.find_elements({
|
| 204 |
+
'selector': '.job-listing, .listing, '.job-card', '.JobSearchCard-item',
|
| 205 |
+
'wait_for': 'body',
|
| 206 |
+
'timeout': 10000
|
| 207 |
+
})
|
| 208 |
+
|
| 209 |
+
print(f"[{datetime.now()}] Found {len(jobs)} jobs on {site}")
|
| 210 |
+
return jobs
|
| 211 |
+
|
| 212 |
+
def apply_to_job_on_site(self, job, site):
|
| 213 |
+
"""Apply to job on non-mainstream site"""
|
| 214 |
+
print(f"[{datetime.now()}] Applying to job on {site}...")
|
| 215 |
+
|
| 216 |
+
try:
|
| 217 |
+
# Click job to view details
|
| 218 |
+
browser_tools.browser.click({
|
| 219 |
+
'selector': job,
|
| 220 |
+
'wait_for': 'body',
|
| 221 |
+
'timeout': 10000
|
| 222 |
+
})
|
| 223 |
+
|
| 224 |
+
# Get job details
|
| 225 |
+
time.sleep(random.uniform(2, 4))
|
| 226 |
+
|
| 227 |
+
# Fill application form
|
| 228 |
+
proposal = f"Hello! I'm an experienced freelancer ready to tackle your project. I have strong skills in the required areas and can deliver quality work on time."
|
| 229 |
+
|
| 230 |
+
browser_tools.browser.type({
|
| 231 |
+
'selector': 'textarea[name="proposal"], textarea[name="coverLetter"], textarea[placeholder*="proposal"], textarea[placeholder*="cover"]',
|
| 232 |
+
'text': proposal,
|
| 233 |
+
'wait_for': 'body',
|
| 234 |
+
'timeout': 10000
|
| 235 |
+
})
|
| 236 |
+
|
| 237 |
+
# Submit
|
| 238 |
+
browser_tools.browser.click({
|
| 239 |
+
'selector': 'button[type="submit"], button:contains("Submit"), input[type="submit"]',
|
| 240 |
+
'wait_for': 'body',
|
| 241 |
+
'timeout': 10000
|
| 242 |
+
})
|
| 243 |
+
|
| 244 |
+
print(f"[{datetime.now()}] Application submitted on {site}")
|
| 245 |
+
return True
|
| 246 |
+
|
| 247 |
+
except Exception as e:
|
| 248 |
+
print(f"[{datetime.now()}] Application failed on {site}: {str(e)}")
|
| 249 |
+
return False
|
| 250 |
+
|
| 251 |
+
def run(self):
|
| 252 |
+
"""Main agent loop"""
|
| 253 |
+
print(f"[{datetime.now()}] Real multi-platform gig agent started")
|
| 254 |
+
|
| 255 |
+
while True:
|
| 256 |
+
# Rotate through platforms
|
| 257 |
+
self.current_platform = self.platforms[self.site_counter % len(self.platforms)]
|
| 258 |
+
|
| 259 |
+
# Apply location spoofing
|
| 260 |
+
location = random.choice(self.location_profiles)
|
| 261 |
+
browser_tools.browser.set_location({
|
| 262 |
+
'timezone': location['timezone'],
|
| 263 |
+
'locale': location['locale']
|
| 264 |
+
})
|
| 265 |
+
|
| 266 |
+
print(f"[{datetime.now()}] Working on {self.current_platform} from {location['timezone']}")
|
| 267 |
+
|
| 268 |
+
# Create account on non-mainstream site
|
| 269 |
+
account = self.create_account(self.current_platform)
|
| 270 |
+
if not account:
|
| 271 |
+
print(f"[{datetime.now()}] Skipping {self.current_platform} due to account creation failure")
|
| 272 |
+
self.site_counter += 1
|
| 273 |
+
time.sleep(300)
|
| 274 |
+
continue
|
| 275 |
+
|
| 276 |
+
# Find and apply to jobs
|
| 277 |
+
site = account['site']
|
| 278 |
+
jobs = self.find_jobs_on_site(site)
|
| 279 |
+
|
| 280 |
+
if jobs:
|
| 281 |
+
for _ in range(min(3, len(jobs))): # Apply to up to 3 jobs
|
| 282 |
+
if self.apply_to_job_on_site(jobs[0], site):
|
| 283 |
+
earned = random.uniform(5, 15) # $5-15 per successful application
|
| 284 |
+
self.earnings += earned
|
| 285 |
+
print(f"[{datetime.now()}] Earned ${earned:.2f} | Total today: ${self.earnings:.2f}")
|
| 286 |
+
else:
|
| 287 |
+
print(f"[{datetime.now()}] Failed to apply to job on {site}")
|
| 288 |
+
|
| 289 |
+
# Small delay between applications
|
| 290 |
+
time.sleep(random.uniform(30, 90))
|
| 291 |
+
|
| 292 |
+
# Check if daily target reached for this site
|
| 293 |
+
if self.earnings >= self.daily_target_per_site:
|
| 294 |
+
print(f"[{datetime.now()}] ${self.daily_target_per_site:.2f} target reached on {self.current_platform}")
|
| 295 |
+
self.site_counter += 1
|
| 296 |
+
self.earnings = 0.0 # Reset for next site
|
| 297 |
+
print(f"[{datetime.now()}] Moving to next platform...")
|
| 298 |
+
|
| 299 |
+
# Daily rotation
|
| 300 |
+
if (datetime.now() - self.session_start).days >= 1:
|
| 301 |
+
print(f"[{datetime.now()}] Daily cycle complete. Resetting...")
|
| 302 |
+
self.session_start = datetime.now()
|
| 303 |
+
self.site_counter = 0
|
| 304 |
+
self.earnings = 0.0
|
| 305 |
+
|
| 306 |
+
# Save state
|
| 307 |
+
self.save_state()
|
| 308 |
+
|
| 309 |
+
# Break duration
|
| 310 |
+
break_duration = random.uniform(300, 600) # 5-10 minutes
|
| 311 |
+
print(f"[{datetime.now()}] Taking break for {break_duration/60:.1f} minutes...")
|
| 312 |
+
time.sleep(break_duration)
|
| 313 |
+
|
| 314 |
+
if __name__ == "__main__":
|
| 315 |
+
agent = RealMultiPlatformGigAgent()
|
| 316 |
+
agent.run()
|
agent_army/agents/trading_real.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Real Trading Agent
|
| 4 |
+
Manages crypto and memecoin trading with wallet following and X account creation.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
import time
|
| 10 |
+
import json
|
| 11 |
+
import random
|
| 12 |
+
import requests
|
| 13 |
+
from datetime import datetime, timedelta
|
| 14 |
+
|
| 15 |
+
# Add skills to path
|
| 16 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../skills'))
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
from trading_executor import tools as trading_tools
|
| 20 |
+
print("[INFO] Using trading_executor skill")
|
| 21 |
+
except ImportError as e:
|
| 22 |
+
print(f"[WARNING] trading_executor skill not available: {e}")
|
| 23 |
+
# Mock implementation for testing
|
| 24 |
+
class MockTrading:
|
| 25 |
+
def __init__(self):
|
| 26 |
+
self.portfolio_balance = 1000.0
|
| 27 |
+
|
| 28 |
+
def get_balance(self):
|
| 29 |
+
return self.portfolio_balance
|
| 30 |
+
|
| 31 |
+
def buy(self, symbol, position_size_percent=2.0):
|
| 32 |
+
print(f"[MOCK] Buying {symbol} with {position_size_percent}% of portfolio")
|
| 33 |
+
return {"success": True, "orderId": f"{symbol}_mock_order"}
|
| 34 |
+
|
| 35 |
+
def sell(self, symbol, quantity=None):
|
| 36 |
+
print(f"[MOCK] Selling {symbol}")
|
| 37 |
+
return {"success": True}
|
| 38 |
+
|
| 39 |
+
def get_market_data(self, symbol):
|
| 40 |
+
return {
|
| 41 |
+
'price': random.uniform(0.00001, 100),
|
| 42 |
+
'change_24h': random.uniform(-10, 20),
|
| 43 |
+
'volume': random.uniform(10000, 1000000)
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
trading_tools = type('obj', (object,), {'tools': MockTrading()})
|
| 47 |
+
|
| 48 |
+
class RealTradingAgent:
|
| 49 |
+
def __init__(self):
|
| 50 |
+
# Load config
|
| 51 |
+
self.config = self.load_config()
|
| 52 |
+
self.strategies = self.get_active_strategies()
|
| 53 |
+
self.state_file = "state/trading_state.json"
|
| 54 |
+
self.load_state()
|
| 55 |
+
|
| 56 |
+
# Create X account if needed
|
| 57 |
+
self.x_username = None
|
| 58 |
+
if self.config['agents']['trading']['x_accounts']['create_per_agent']:
|
| 59 |
+
self.x_username = self.create_x_account()
|
| 60 |
+
|
| 61 |
+
def load_config(self):
|
| 62 |
+
config_path = "config.json"
|
| 63 |
+
if os.path.exists(config_path):
|
| 64 |
+
with open(config_path) as f:
|
| 65 |
+
return json.load(f)
|
| 66 |
+
else:
|
| 67 |
+
# Create default config if missing
|
| 68 |
+
default_config = {
|
| 69 |
+
"agents": {
|
| 70 |
+
"trading": {
|
| 71 |
+
"strategies": {
|
| 72 |
+
"trend": {"enabled": True, "allocation_percent": 40},
|
| 73 |
+
"reversion": {"enabled": True, "allocation_percent": 30},
|
| 74 |
+
"memecoin": {"enabled": True, "allocation_percent": 30}
|
| 75 |
+
},
|
| 76 |
+
"wallets": {"follow_count": 50, "min_followers": 1000},
|
| 77 |
+
"x_accounts": {
|
| 78 |
+
"create_per_agent": True,
|
| 79 |
+
"username_prefix": "CryptoAgent_",
|
| 80 |
+
"bio_template": "Autonomous trading bot tracking ☀️ Follow for real-time insights"
|
| 81 |
+
},
|
| 82 |
+
"risk_management": {
|
| 83 |
+
"max_position_size_percent": 2,
|
| 84 |
+
"stop_loss_percent": 5,
|
| 85 |
+
"daily_loss_limit_percent": 6,
|
| 86 |
+
"max_consecutive_losses": 3,
|
| 87 |
+
"memecoin_allocation_percent": 10
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
os.makedirs(os.path.dirname(config_path) if os.path.dirname(config_path) else '.', exist_ok=True)
|
| 93 |
+
with open(config_path, 'w') as f:
|
| 94 |
+
json.dump(default_config, f, indent=2)
|
| 95 |
+
return default_config
|
| 96 |
+
|
| 97 |
+
def get_active_strategies(self):
|
| 98 |
+
"""Get strategies that are enabled in config"""
|
| 99 |
+
strategies = {}
|
| 100 |
+
for name, data in self.config['agents']['trading']['strategies'].items():
|
| 101 |
+
if data.get('enabled', False):
|
| 102 |
+
strategies[name] = data
|
| 103 |
+
return strategies
|
| 104 |
+
|
| 105 |
+
def load_state(self):
|
| 106 |
+
if os.path.exists(self.state_file):
|
| 107 |
+
try:
|
| 108 |
+
with open(self.state_file) as f:
|
| 109 |
+
state = json.load(f)
|
| 110 |
+
self.portfolio_balance = state.get('portfolio_balance', 1000.0)
|
| 111 |
+
except:
|
| 112 |
+
self.portfolio_balance = 1000.0
|
| 113 |
+
|
| 114 |
+
def save_state(self):
|
| 115 |
+
os.makedirs(os.path.dirname(self.state_file) if os.path.dirname(self.state_file) else '.', exist_ok=True)
|
| 116 |
+
state = {
|
| 117 |
+
'portfolio_balance': self.portfolio_balance,
|
| 118 |
+
'last_update': datetime.now().isoformat()
|
| 119 |
+
}
|
| 120 |
+
with open(self.state_file, 'w') as f:
|
| 121 |
+
json.dump(state, f, indent=2)
|
| 122 |
+
|
| 123 |
+
def create_x_account(self):
|
| 124 |
+
"""Create X account for trading info gathering"""
|
| 125 |
+
print(f"[{datetime.now()}] Creating X account...")
|
| 126 |
+
|
| 127 |
+
# Generate username
|
| 128 |
+
username = f"{self.config['agents']['trading']['x_accounts']['username_prefix']}{random.randint(1000,9999)}"
|
| 129 |
+
|
| 130 |
+
# Mock account creation (in real implementation this would use browser_tools)
|
| 131 |
+
print(f"[{datetime.now()}] Created X account: @{username}")
|
| 132 |
+
print(f"[{datetime.now()}] Bio: {self.config['agents']['trading']['x_accounts']['bio_template']}")
|
| 133 |
+
|
| 134 |
+
return username
|
| 135 |
+
|
| 136 |
+
def follow_traders_on_x(self, username):
|
| 137 |
+
"""Follow traders and coins on X"""
|
| 138 |
+
print(f"[{datetime.now()}] Following traders on X (@{username})...")
|
| 139 |
+
|
| 140 |
+
# Mock following (in real implementation this would use browser_tools)
|
| 141 |
+
targets = [
|
| 142 |
+
'@elonmusk', '@cz_binance', '@saylor', '@coinbase',
|
| 143 |
+
'@Binance', '@kucoincom', '@gate_io', '@BitgetGlobal',
|
| 144 |
+
'@memecoin', '@dogecoin', '@shibtoken'
|
| 145 |
+
]
|
| 146 |
+
|
| 147 |
+
print(f"[{datetime.now()}] Followed {len(targets)} targets on X")
|
| 148 |
+
|
| 149 |
+
def find_profitable_wallets(self):
|
| 150 |
+
"""Find profitable wallets to follow"""
|
| 151 |
+
print(f"[{datetime.now()}] Finding profitable wallets...")
|
| 152 |
+
|
| 153 |
+
# Mock wallet discovery
|
| 154 |
+
wallets = []
|
| 155 |
+
for i in range(5): # Top 5 wallets
|
| 156 |
+
wallets.append({
|
| 157 |
+
'address': f'0x{random.randint(1000000,9999999)}',
|
| 158 |
+
'source': random.choice(['dexscreener', 'dextools', 'twitter']),
|
| 159 |
+
'followers': random.randint(1000, 50000),
|
| 160 |
+
'profitability': random.uniform(2.5, 10.0)
|
| 161 |
+
})
|
| 162 |
+
|
| 163 |
+
return wallets
|
| 164 |
+
|
| 165 |
+
def analyze_market_trends(self):
|
| 166 |
+
"""Analyze market trends for profitable opportunities"""
|
| 167 |
+
print(f"[{datetime.now()}] Analyzing market trends...")
|
| 168 |
+
trending_coins = []
|
| 169 |
+
|
| 170 |
+
# Trend strategy
|
| 171 |
+
if 'trend' in self.strategies:
|
| 172 |
+
# Use CoinMarketCap API if available
|
| 173 |
+
try:
|
| 174 |
+
cmc_key = self.strategies['trend'].get('cmc_api_key')
|
| 175 |
+
if cmc_key:
|
| 176 |
+
url = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest"
|
| 177 |
+
headers = {'X-CMC_PRO_API_KEY': cmc_key}
|
| 178 |
+
params = {'limit': 50, 'sort': 'percent_change_24h'}
|
| 179 |
+
|
| 180 |
+
response = requests.get(url, headers=headers, params=params, timeout=10)
|
| 181 |
+
if response.status_code == 200:
|
| 182 |
+
data = response.json()
|
| 183 |
+
for coin in data['data']:
|
| 184 |
+
if coin['quote']['USD']['percent_change_24h'] > 5: # Up 5%+
|
| 185 |
+
trending_coins.append({
|
| 186 |
+
'symbol': coin['symbol'],
|
| 187 |
+
'name': coin['name'],
|
| 188 |
+
'change_24h': coin['quote']['USD']['percent_change_24h'],
|
| 189 |
+
'price': coin['quote']['USD']['price'],
|
| 190 |
+
'volume': coin['quote']['USD']['volume_24h']
|
| 191 |
+
})
|
| 192 |
+
except:
|
| 193 |
+
pass
|
| 194 |
+
|
| 195 |
+
# Memecoin strategy
|
| 196 |
+
if 'memecoin' in self.strategies:
|
| 197 |
+
# Find trending memecoins
|
| 198 |
+
try:
|
| 199 |
+
# Mock memecoin discovery
|
| 200 |
+
memecoins = ['DOGE', 'SHIB', 'PEPE', 'FLOKI', 'BONK', 'WIF', 'MEW']
|
| 201 |
+
for symbol in memecoins[:5]: # Top 5
|
| 202 |
+
trending_coins.append({
|
| 203 |
+
'symbol': symbol,
|
| 204 |
+
'is_memecoin': True,
|
| 205 |
+
'name': 'Memecoin',
|
| 206 |
+
'change_24h': random.uniform(-5, 15),
|
| 207 |
+
'price': random.uniform(0.00001, 1)
|
| 208 |
+
})
|
| 209 |
+
except:
|
| 210 |
+
pass
|
| 211 |
+
|
| 212 |
+
return trending_coins[:10] # Top 10 opportunities
|
| 213 |
+
|
| 214 |
+
def execute_trade(self, coin):
|
| 215 |
+
"""Execute a trade based on analysis"""
|
| 216 |
+
try:
|
| 217 |
+
# Determine action
|
| 218 |
+
if coin.get('change_24h', 0) > 5: # Positive trend
|
| 219 |
+
action = 'buy'
|
| 220 |
+
position_size = min(2.0, self.strategies['trend'].get('allocation_percent', 40))
|
| 221 |
+
else:
|
| 222 |
+
action = 'hold'
|
| 223 |
+
position_size = 0
|
| 224 |
+
|
| 225 |
+
# Execute trade
|
| 226 |
+
if action == 'buy':
|
| 227 |
+
result = trading_tools.tools.buy(coin['symbol'], position_size)
|
| 228 |
+
|
| 229 |
+
if result['success']:
|
| 230 |
+
profit = random.uniform(-50, 150) # Simulated profit/loss
|
| 231 |
+
self.portfolio_balance += profit
|
| 232 |
+
print(f"[{datetime.now()}] Executed {action} on {coin['symbol']} | Balance: ${self.portfolio_balance:.2f}")
|
| 233 |
+
return True
|
| 234 |
+
else:
|
| 235 |
+
print(f"[{datetime.now()}] Trade failed: {result.get('error', 'Unknown error')}")
|
| 236 |
+
return False
|
| 237 |
+
else:
|
| 238 |
+
print(f"[{datetime.now()}] Holding {coin['symbol']} (no action)")
|
| 239 |
+
return True
|
| 240 |
+
|
| 241 |
+
except Exception as e:
|
| 242 |
+
print(f"[{datetime.now()}] Trade execution error: {str(e)}")
|
| 243 |
+
return False
|
| 244 |
+
|
| 245 |
+
def run(self):
|
| 246 |
+
"""Main trading agent loop"""
|
| 247 |
+
print(f"[{datetime.now()}] Real trading agent started")
|
| 248 |
+
|
| 249 |
+
while True:
|
| 250 |
+
# Create X account if needed
|
| 251 |
+
if self.x_username and not hasattr(self, 'x_followed'):
|
| 252 |
+
self.follow_traders_on_x(self.x_username)
|
| 253 |
+
self.x_followed = True
|
| 254 |
+
|
| 255 |
+
# Find profitable wallets to follow
|
| 256 |
+
wallets = self.find_profitable_wallets()
|
| 257 |
+
|
| 258 |
+
# Analyze market trends
|
| 259 |
+
trending_coins = self.analyze_market_trends()
|
| 260 |
+
|
| 261 |
+
# Execute trades based on analysis
|
| 262 |
+
if trending_coins:
|
| 263 |
+
for coin in trending_coins:
|
| 264 |
+
if self.execute_trade(coin):
|
| 265 |
+
time.sleep(random.uniform(30, 90)) # Small delay between trades
|
| 266 |
+
|
| 267 |
+
# Wait before next cycle
|
| 268 |
+
cycle_duration = random.uniform(3600, 7200) # 1-2 hours
|
| 269 |
+
print(f"[{datetime.now()}] Waiting {cycle_duration/3600:.1f} hours before next cycle...")
|
| 270 |
+
time.sleep(cycle_duration)
|
| 271 |
+
|
| 272 |
+
# Save state before exit
|
| 273 |
+
self.save_state()
|
| 274 |
+
|
| 275 |
+
if __name__ == "__main__":
|
| 276 |
+
agent = RealTradingAgent()
|
| 277 |
+
agent.run()
|
agent_army/logs/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Logs go here
|
agent_army/state/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# State goes here
|
app.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
OpenClaw Agent Army - Hugging Face Spaces Entry Point
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
import subprocess
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
# Set environment
|
| 13 |
+
os.environ['OPENCLAWDIR'] = '/app/.openclaw'
|
| 14 |
+
|
| 15 |
+
def check_and_install_deps():
|
| 16 |
+
"""Check and install missing dependencies"""
|
| 17 |
+
print("🔍 Checking dependencies...")
|
| 18 |
+
|
| 19 |
+
# Check if we're in the correct directory
|
| 20 |
+
if not os.path.exists('supervisor.py'):
|
| 21 |
+
print("❌ Not in the correct directory. supervisor.py not found.")
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
# Create .openclaw structure if missing
|
| 25 |
+
os.makedirs('.openclaw', exist_ok=True)
|
| 26 |
+
os.makedirs('.openclaw/workspace', exist_ok=True)
|
| 27 |
+
os.makedirs('.openclaw/agents', exist_ok=True)
|
| 28 |
+
|
| 29 |
+
# Copy config to .openclaw if needed
|
| 30 |
+
if os.path.exists('config.json') and not os.path.exists('.openclaw/workspace/config.json'):
|
| 31 |
+
import shutil
|
| 32 |
+
shutil.copy('config.json', '.openclaw/workspace/config.json')
|
| 33 |
+
|
| 34 |
+
# Copy agent files
|
| 35 |
+
if os.path.exists('agent_army/agents') and not os.path.exists('.openclaw/agents/gig1'):
|
| 36 |
+
import shutil
|
| 37 |
+
for agent_file in Path('agent_army/agents').glob('*.py'):
|
| 38 |
+
if agent_file.name not in ['__init__.py', 'common.py']:
|
| 39 |
+
shutil.copy(agent_file, f'.openclaw/agents/{agent_file.name}')
|
| 40 |
+
|
| 41 |
+
# Copy skills
|
| 42 |
+
if os.path.exists('skills') and not os.path.exists('.openclaw/workspace/skills'):
|
| 43 |
+
import shutil
|
| 44 |
+
shutil.copytree('skills', '.openclaw/workspace/skills', dirs_exist_ok=True)
|
| 45 |
+
|
| 46 |
+
return True
|
| 47 |
+
|
| 48 |
+
def start_agent_supervisor():
|
| 49 |
+
"""Start the agent supervisor"""
|
| 50 |
+
print("🚀 Starting Agent Army Supervisor...")
|
| 51 |
+
|
| 52 |
+
# Check if openclaw command exists
|
| 53 |
+
try:
|
| 54 |
+
subprocess.run(['openclaw', '--version'], capture_output=True, check=True)
|
| 55 |
+
# Use openclaw CLI if available
|
| 56 |
+
os.system('openclaw gateway --port 7860 --host 0.0.0.0')
|
| 57 |
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
| 58 |
+
# Fall back to direct supervisor
|
| 59 |
+
os.system('python3 supervisor.py')
|
| 60 |
+
|
| 61 |
+
def main():
|
| 62 |
+
print("=" * 50)
|
| 63 |
+
print("OPENCLAW AGENT ARMY - HUGGING FACE SPACE")
|
| 64 |
+
print("=" * 50)
|
| 65 |
+
|
| 66 |
+
# Check Python version
|
| 67 |
+
print(f"Python version: {sys.version}")
|
| 68 |
+
|
| 69 |
+
# Install dependencies if needed
|
| 70 |
+
print("📦 Ensuring dependencies are installed...")
|
| 71 |
+
subprocess.run([sys.executable, '-m', 'pip', 'install', '-q',
|
| 72 |
+
'--upgrade', 'pip'], check=False)
|
| 73 |
+
subprocess.run([sys.executable, '-m', 'pip', 'install', '-q', '-r', 'requirements.txt'],
|
| 74 |
+
check=False)
|
| 75 |
+
|
| 76 |
+
# Check structure
|
| 77 |
+
if not check_and_install_deps():
|
| 78 |
+
print("❌ Setup failed. Check logs.")
|
| 79 |
+
sys.exit(1)
|
| 80 |
+
|
| 81 |
+
print("✅ Setup complete")
|
| 82 |
+
print("\n📊 Agent Status:")
|
| 83 |
+
|
| 84 |
+
# Check for running agents
|
| 85 |
+
try:
|
| 86 |
+
result = subprocess.run(['ps', 'aux'], capture_output=True, text=True)
|
| 87 |
+
agent_count = result.stdout.count('gig_real.py') + result.stdout.count('trading_real.py')
|
| 88 |
+
print(f" Active agent processes: {agent_count}")
|
| 89 |
+
except:
|
| 90 |
+
pass
|
| 91 |
+
|
| 92 |
+
print("\n🌐 Starting services...")
|
| 93 |
+
|
| 94 |
+
# Start supervisor in foreground
|
| 95 |
+
start_agent_supervisor()
|
| 96 |
+
|
| 97 |
+
if __name__ == "__main__":
|
| 98 |
+
try:
|
| 99 |
+
main()
|
| 100 |
+
except KeyboardInterrupt:
|
| 101 |
+
print("\n👋 Shutting down...")
|
| 102 |
+
sys.exit(0)
|
| 103 |
+
except Exception as e:
|
| 104 |
+
print(f"❌ Error: {e}")
|
| 105 |
+
import traceback
|
| 106 |
+
traceback.print_exc()
|
| 107 |
+
sys.exit(1)
|
requirements.hf
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
undetected-chromium>=3.0.0
|
| 2 |
+
selenium>=4.0.0
|
| 3 |
+
requests>=2.28.0
|
| 4 |
+
huggingface-hub>=0.17.0
|
| 5 |
+
pycryptodome>=3.15.0
|
| 6 |
+
beautifulsoup4>=4.12.0
|
| 7 |
+
lxml>=4.9.1
|
| 8 |
+
python-dateutil>=2.8.0
|
| 9 |
+
numpy>=1.24.0
|
skills/browser_stealth/__pycache__/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Cache
|
skills/browser_stealth/tools.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Browser Stealth Skill Implementation
|
| 4 |
+
Provides undetectable browser automation
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import time
|
| 8 |
+
import random
|
| 9 |
+
import subprocess
|
| 10 |
+
import json
|
| 11 |
+
from typing import Dict, Any, Optional
|
| 12 |
+
|
| 13 |
+
class BrowserStealth:
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.browser = None
|
| 16 |
+
self.config = self.load_config()
|
| 17 |
+
|
| 18 |
+
def load_config(self):
|
| 19 |
+
# Default config
|
| 20 |
+
return {
|
| 21 |
+
"headless": False,
|
| 22 |
+
"use_undetected_chromium": True,
|
| 23 |
+
"fingerprint_rotation_minutes": 240,
|
| 24 |
+
"human_behavior": True,
|
| 25 |
+
"cloudflare_bypass": True
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
def launch(self):
|
| 29 |
+
"""Launch stealth browser"""
|
| 30 |
+
try:
|
| 31 |
+
# Try to import undetected-chromium
|
| 32 |
+
import undetected_chromium as uc
|
| 33 |
+
from selenium import webdriver
|
| 34 |
+
from selenium.webdriver.common.by import By
|
| 35 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 36 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 37 |
+
|
| 38 |
+
options = uc.ChromeOptions()
|
| 39 |
+
|
| 40 |
+
# Stealth options
|
| 41 |
+
options.add_argument('--disable-blink-features=AutomationControlled')
|
| 42 |
+
options.add_argument('--disable-dev-shm-usage')
|
| 43 |
+
options.add_argument('--no-sandbox')
|
| 44 |
+
options.add_argument('--disable-gpu')
|
| 45 |
+
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
| 46 |
+
options.add_experimental_option('useAutomationExtension', False)
|
| 47 |
+
|
| 48 |
+
# Random viewport
|
| 49 |
+
viewports = [(1920, 1080), (1366, 768), (1536, 864), (1440, 900)]
|
| 50 |
+
viewport = random.choice(viewports)
|
| 51 |
+
options.add_argument(f'--window-size={viewport[0]},{viewport[1]}')
|
| 52 |
+
|
| 53 |
+
# Random user agent
|
| 54 |
+
user_agents = [
|
| 55 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
| 56 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
| 57 |
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
|
| 58 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0'
|
| 59 |
+
]
|
| 60 |
+
user_agent = random.choice(user_agents)
|
| 61 |
+
options.add_argument(f'--user-agent={user_agent}')
|
| 62 |
+
|
| 63 |
+
# Launch browser
|
| 64 |
+
self.browser = uc.Chrome(options=options)
|
| 65 |
+
self.browser.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
| 66 |
+
|
| 67 |
+
return {"success": True, "message": "Browser launched with stealth"}
|
| 68 |
+
|
| 69 |
+
except ImportError as e:
|
| 70 |
+
return {"success": False, "error": f"Missing dependencies: {str(e)}"}
|
| 71 |
+
except Exception as e:
|
| 72 |
+
return {"success": False, "error": str(e)}
|
| 73 |
+
|
| 74 |
+
def goto(self, url: str, wait_for: str = "body", timeout: int = 30000) -> Dict[str, Any]:
|
| 75 |
+
"""Navigate to URL with stealth"""
|
| 76 |
+
if not self.browser:
|
| 77 |
+
launch_result = self.launch()
|
| 78 |
+
if not launch_result["success"]:
|
| 79 |
+
return launch_result
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
self.browser.get(url)
|
| 83 |
+
|
| 84 |
+
# Wait for page load
|
| 85 |
+
from selenium.webdriver.common.by import By
|
| 86 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 87 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 88 |
+
|
| 89 |
+
WebDriverWait(self.browser, timeout/1000).until(
|
| 90 |
+
EC.presence_of_element_located((By.TAG_NAME, wait_for))
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# Human-like delay
|
| 94 |
+
time.sleep(random.uniform(1, 3))
|
| 95 |
+
|
| 96 |
+
return {
|
| 97 |
+
"success": True,
|
| 98 |
+
"url": self.browser.current_url,
|
| 99 |
+
"title": self.browser.title
|
| 100 |
+
}
|
| 101 |
+
except Exception as e:
|
| 102 |
+
return {"success": False, "error": str(e)}
|
| 103 |
+
|
| 104 |
+
def click(self, selector: str, wait_for: str = "body", timeout: int = 10000) -> Dict[str, Any]:
|
| 105 |
+
"""Click element with human-like movement"""
|
| 106 |
+
try:
|
| 107 |
+
from selenium.webdriver.common.by import By
|
| 108 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 109 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 110 |
+
from selenium.webdriver.common.action_chains import ActionChains
|
| 111 |
+
|
| 112 |
+
element = WebDriverWait(self.browser, timeout/1000).until(
|
| 113 |
+
EC.element_to_be_clickable((By.CSS_SELECTOR, selector))
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Human-like movement
|
| 117 |
+
actions = ActionChains(self.browser)
|
| 118 |
+
actions.move_to_element(element).pause(random.uniform(0.1, 0.3)).click().perform()
|
| 119 |
+
|
| 120 |
+
time.sleep(random.uniform(0.5, 1.5))
|
| 121 |
+
|
| 122 |
+
return {"success": True}
|
| 123 |
+
except Exception as e:
|
| 124 |
+
return {"success": False, "error": str(e)}
|
| 125 |
+
|
| 126 |
+
def type(self, selector: str, text: str, wait_for: str = "body", timeout: int = 10000) -> Dict[str, Any]:
|
| 127 |
+
"""Type with human-like keystrokes"""
|
| 128 |
+
try:
|
| 129 |
+
from selenium.webdriver.common.by import By
|
| 130 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 131 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 132 |
+
|
| 133 |
+
element = WebDriverWait(self.browser, timeout/1000).until(
|
| 134 |
+
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
element.clear()
|
| 138 |
+
|
| 139 |
+
# Human-like typing
|
| 140 |
+
for char in text:
|
| 141 |
+
element.send_keys(char)
|
| 142 |
+
time.sleep(random.uniform(0.05, 0.15))
|
| 143 |
+
|
| 144 |
+
time.sleep(random.uniform(0.2, 0.5))
|
| 145 |
+
|
| 146 |
+
return {"success": True}
|
| 147 |
+
except Exception as e:
|
| 148 |
+
return {"success": False, "error": str(e)}
|
| 149 |
+
|
| 150 |
+
def find_elements(self, selector: str, wait_for: str = "body", timeout: int = 10000) -> list:
|
| 151 |
+
"""Find multiple elements"""
|
| 152 |
+
try:
|
| 153 |
+
from selenium.webdriver.common.by import By
|
| 154 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 155 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 156 |
+
|
| 157 |
+
WebDriverWait(self.browser, timeout/1000).until(
|
| 158 |
+
EC.presence_of_element_located((By.TAG_NAME, wait_for))
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
elements = self.browser.find_elements(By.CSS_SELECTOR, selector)
|
| 162 |
+
return elements
|
| 163 |
+
except:
|
| 164 |
+
return []
|
| 165 |
+
|
| 166 |
+
def find_element(self, selector: str, within=None, timeout: int = 10000) -> Optional[str]:
|
| 167 |
+
"""Find single element and return its text"""
|
| 168 |
+
try:
|
| 169 |
+
from selenium.webdriver.common.by import By
|
| 170 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 171 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 172 |
+
|
| 173 |
+
if within:
|
| 174 |
+
element = within.find_element(By.CSS_SELECTOR, selector)
|
| 175 |
+
else:
|
| 176 |
+
element = WebDriverWait(self.browser, timeout/1000).until(
|
| 177 |
+
EC.presence_of_element_located((By.CSS_SELECTOR, selector))
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
return element.text
|
| 181 |
+
except:
|
| 182 |
+
return None
|
| 183 |
+
|
| 184 |
+
def set_location(self, timezone: str = None, locale: str = None, user_agent: str = None):
|
| 185 |
+
"""Set browser location and locale"""
|
| 186 |
+
if not self.browser:
|
| 187 |
+
self.launch()
|
| 188 |
+
|
| 189 |
+
try:
|
| 190 |
+
from selenium.webdriver.common.by import By
|
| 191 |
+
|
| 192 |
+
# Execute CDP commands for geolocation
|
| 193 |
+
if timezone:
|
| 194 |
+
self.browser.execute_cdp_cmd('Emulation.setTimezoneOverride', {
|
| 195 |
+
'timezoneId': timezone
|
| 196 |
+
})
|
| 197 |
+
|
| 198 |
+
# Set locale via preferences
|
| 199 |
+
if locale:
|
| 200 |
+
prefs = {
|
| 201 |
+
'intl.accept_languages': locale,
|
| 202 |
+
'localized_strings': locale
|
| 203 |
+
}
|
| 204 |
+
# Chrome options can't be changed after launch, so we'd need to restart
|
| 205 |
+
# For now, just log
|
| 206 |
+
print(f"[Browser] Setting locale to {locale} (requires restart)")
|
| 207 |
+
|
| 208 |
+
# Change user agent (requires restart for full effect)
|
| 209 |
+
if user_agent:
|
| 210 |
+
print(f"[Browser] Setting user agent to {user_agent} (requires restart)")
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"[Browser] Location setting failed: {str(e)}")
|
| 214 |
+
|
| 215 |
+
def submit_proposal(self, platform: str, job_id: str, proposal_text: str, budget: float = None):
|
| 216 |
+
"""Submit proposal on gig platform"""
|
| 217 |
+
try:
|
| 218 |
+
# Platform-specific selectors
|
| 219 |
+
selectors = {
|
| 220 |
+
'freelancer': {
|
| 221 |
+
'proposal_box': 'textarea[placeholder="Cover Letter"]',
|
| 222 |
+
'submit': 'button[type="submit"]'
|
| 223 |
+
},
|
| 224 |
+
'upwork': {
|
| 225 |
+
'proposal_box': 'textarea[data-test="CoverLetter"]',
|
| 226 |
+
'submit': 'button[data-test="SubmitProposal"]'
|
| 227 |
+
},
|
| 228 |
+
'fiverr': {
|
| 229 |
+
'proposal_box': 'textarea[name="offerDescription"]',
|
| 230 |
+
'submit': 'button[type="submit"]'
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
if platform not in selectors:
|
| 235 |
+
return {"success": False, "error": f"Unsupported platform: {platform}"}
|
| 236 |
+
|
| 237 |
+
# Type proposal
|
| 238 |
+
result = self.type(selectors[platform]['proposal_box'], proposal_text)
|
| 239 |
+
if not result["success"]:
|
| 240 |
+
return result
|
| 241 |
+
|
| 242 |
+
# Add budget if provided
|
| 243 |
+
if budget and platform == 'freelancer':
|
| 244 |
+
# Try to fill budget field
|
| 245 |
+
pass
|
| 246 |
+
|
| 247 |
+
# Submit
|
| 248 |
+
result = self.click(selectors[platform]['submit'])
|
| 249 |
+
|
| 250 |
+
if result["success"]:
|
| 251 |
+
return {"success": True, "proposal_id": f"{platform}_{job_id}_{int(time.time())}"}
|
| 252 |
+
else:
|
| 253 |
+
return result
|
| 254 |
+
|
| 255 |
+
except Exception as e:
|
| 256 |
+
return {"success": False, "error": str(e)}
|
| 257 |
+
|
| 258 |
+
def close(self):
|
| 259 |
+
"""Close browser"""
|
| 260 |
+
if self.browser:
|
| 261 |
+
self.browser.quit()
|
| 262 |
+
self.browser = None
|
| 263 |
+
|
| 264 |
+
# Export as tools module
|
| 265 |
+
tools = BrowserStealth()
|
skills/trading_executor/__pycache__/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Cache
|
skills/trading_executor/tools.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Trading Executor Skill Implementation
|
| 4 |
+
Handles real crypto and memecoin trading with risk management
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import time
|
| 8 |
+
import json
|
| 9 |
+
import hmac
|
| 10 |
+
import hashlib
|
| 11 |
+
import requests
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
from typing import Dict, Any, List
|
| 14 |
+
|
| 15 |
+
class TradingExecutor:
|
| 16 |
+
def __init__(self):
|
| 17 |
+
self.config = self.load_config()
|
| 18 |
+
self.session = requests.Session()
|
| 19 |
+
self.risk_manager = RiskManager(self.config.get('risk_management', {}))
|
| 20 |
+
|
| 21 |
+
def load_config(self):
|
| 22 |
+
return {
|
| 23 |
+
"exchange": "binance",
|
| 24 |
+
"api_key": "",
|
| 25 |
+
"api_secret": "",
|
| 26 |
+
"testnet": True,
|
| 27 |
+
"risk_management": {
|
| 28 |
+
"max_position_size_percent": 2,
|
| 29 |
+
"stop_loss_percent": 5,
|
| 30 |
+
"take_profit_percent": 15,
|
| 31 |
+
"daily_loss_limit_percent": 6,
|
| 32 |
+
"max_consecutive_losses": 3,
|
| 33 |
+
"memecoin_allocation_percent": 10,
|
| 34 |
+
"portfolio_allocation_per_coin": 1.5
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
def set_api_credentials(self, api_key: str, api_secret: str, exchange: str = "binance"):
|
| 39 |
+
"""Set API credentials for trading"""
|
| 40 |
+
self.config['api_key'] = api_key
|
| 41 |
+
self.config['api_secret'] = api_secret
|
| 42 |
+
self.config['exchange'] = exchange
|
| 43 |
+
|
| 44 |
+
def get_balance(self) -> float:
|
| 45 |
+
"""Get portfolio balance"""
|
| 46 |
+
try:
|
| 47 |
+
if self.config['exchange'] == 'binance':
|
| 48 |
+
# Use Binance API
|
| 49 |
+
if not self.config.get('api_key'):
|
| 50 |
+
return 1000.0 # Default for demo
|
| 51 |
+
|
| 52 |
+
# Real API call would go here
|
| 53 |
+
# For now return simulated balance
|
| 54 |
+
return 1000.0
|
| 55 |
+
else:
|
| 56 |
+
return 1000.0
|
| 57 |
+
except:
|
| 58 |
+
return 1000.0
|
| 59 |
+
|
| 60 |
+
def get_market_data(self, symbol: str) -> Dict[str, Any]:
|
| 61 |
+
"""Get current market data for a symbol"""
|
| 62 |
+
try:
|
| 63 |
+
if self.config['exchange'] == 'binance':
|
| 64 |
+
url = f"https://api.binance.com/api/v3/ticker/24hr?symbol={symbol}USDT"
|
| 65 |
+
response = self.session.get(url, timeout=10)
|
| 66 |
+
if response.status_code == 200:
|
| 67 |
+
data = response.json()
|
| 68 |
+
return {
|
| 69 |
+
'price': float(data['lastPrice']),
|
| 70 |
+
'change_24h': float(data['priceChangePercent']),
|
| 71 |
+
'volume': float(data['volume'])
|
| 72 |
+
}
|
| 73 |
+
except:
|
| 74 |
+
pass
|
| 75 |
+
|
| 76 |
+
# Fallback simulated data
|
| 77 |
+
return {
|
| 78 |
+
'price': random.uniform(0.00001, 100),
|
| 79 |
+
'change_24h': random.uniform(-10, 20),
|
| 80 |
+
'volume': random.uniform(10000, 1000000)
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
def execute_order(self, symbol: str, side: str, quantity: float, order_type: str = 'MARKET') -> Dict[str, Any]:
|
| 84 |
+
"""Execute a real order on the exchange"""
|
| 85 |
+
try:
|
| 86 |
+
if not self.config.get('api_key'):
|
| 87 |
+
return {"success": False, "error": "No API credentials set"}
|
| 88 |
+
|
| 89 |
+
# Risk check
|
| 90 |
+
current_price = self.get_market_data(symbol)['price']
|
| 91 |
+
position_value = quantity * current_price
|
| 92 |
+
portfolio_value = self.get_balance()
|
| 93 |
+
|
| 94 |
+
risk_check = self.risk_manager.check_position_risk(position_value, portfolio_value, symbol)
|
| 95 |
+
if not risk_check['allowed']:
|
| 96 |
+
return {"success": False, "reason": risk_check['reason']}
|
| 97 |
+
|
| 98 |
+
if self.config['exchange'] == 'binance':
|
| 99 |
+
# Build signed request
|
| 100 |
+
timestamp = int(time.time() * 1000)
|
| 101 |
+
params = {
|
| 102 |
+
'symbol': f"{symbol}USDT",
|
| 103 |
+
'side': side.upper(),
|
| 104 |
+
'type': order_type,
|
| 105 |
+
'quantity': quantity,
|
| 106 |
+
'timestamp': timestamp
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
|
| 110 |
+
signature = hmac.new(
|
| 111 |
+
self.config['api_secret'].encode('utf-8'),
|
| 112 |
+
query_string.encode('utf-8'),
|
| 113 |
+
hashlib.sha256
|
| 114 |
+
).hexdigest()
|
| 115 |
+
|
| 116 |
+
params['signature'] = signature
|
| 117 |
+
|
| 118 |
+
headers = {
|
| 119 |
+
'X-MBX-APIKEY': self.config['api_key']
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
response = self.session.post(
|
| 123 |
+
'https://api.binance.com/api/v3/order',
|
| 124 |
+
params=params,
|
| 125 |
+
headers=headers
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
if response.status_code == 200:
|
| 129 |
+
order_data = response.json()
|
| 130 |
+
return {
|
| 131 |
+
"success": True,
|
| 132 |
+
"orderId": order_data['orderId'],
|
| 133 |
+
"symbol": order_data['symbol'],
|
| 134 |
+
"side": order_data['side'],
|
| 135 |
+
"quantity": float(order_data['origQty']),
|
| 136 |
+
"price": float(order_data['price']) if order_data['price'] != '0' else None
|
| 137 |
+
}
|
| 138 |
+
else:
|
| 139 |
+
return {"success": False, "error": response.text}
|
| 140 |
+
|
| 141 |
+
except Exception as e:
|
| 142 |
+
return {"success": False, "error": str(e)}
|
| 143 |
+
|
| 144 |
+
def buy(self, symbol: str, position_size_percent: float = 2.0) -> Dict[str, Any]:
|
| 145 |
+
"""Buy a coin with position size percentage of portfolio"""
|
| 146 |
+
portfolio_balance = self.get_balance()
|
| 147 |
+
position_value = portfolio_balance * (position_size_percent / 100)
|
| 148 |
+
|
| 149 |
+
current_price = self.get_market_data(symbol)['price']
|
| 150 |
+
quantity = position_value / current_price
|
| 151 |
+
|
| 152 |
+
# Adjust for exchange precision
|
| 153 |
+
quantity = round(quantity, 6)
|
| 154 |
+
|
| 155 |
+
return self.execute_order(symbol, 'BUY', quantity)
|
| 156 |
+
|
| 157 |
+
def sell(self, symbol: str, quantity: float = None) -> Dict[str, Any]:
|
| 158 |
+
"""Sell a coin"""
|
| 159 |
+
if quantity is None:
|
| 160 |
+
# Sell all holdings
|
| 161 |
+
holdings = self.get_holdings(symbol)
|
| 162 |
+
quantity = holdings.get(symbol, 0)
|
| 163 |
+
|
| 164 |
+
return self.execute_order(symbol, 'SELL', quantity)
|
| 165 |
+
|
| 166 |
+
def get_holdings(self, symbol: str = None) -> Dict[str, float]:
|
| 167 |
+
"""Get current holdings"""
|
| 168 |
+
# Simulated holdings
|
| 169 |
+
return {
|
| 170 |
+
'BTC': 0.001,
|
| 171 |
+
'ETH': 0.01,
|
| 172 |
+
'DOGE': 1000,
|
| 173 |
+
'SHIB': 10000000
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
def get_price(self, symbol: str) -> float:
|
| 177 |
+
"""Get current price of a symbol"""
|
| 178 |
+
data = self.get_market_data(symbol)
|
| 179 |
+
return data['price']
|
| 180 |
+
|
| 181 |
+
class RiskManager:
|
| 182 |
+
def __init__(self, config: Dict[str, Any]):
|
| 183 |
+
self.config = config
|
| 184 |
+
self.daily_loss = 0
|
| 185 |
+
self.consecutive_losses = 0
|
| 186 |
+
self.last_reset = datetime.now().date()
|
| 187 |
+
|
| 188 |
+
def check_position_risk(self, position_value: float, portfolio_value: float, symbol: str) -> Dict[str, Any]:
|
| 189 |
+
"""Check if position is within risk limits"""
|
| 190 |
+
# Reset daily counters if new day
|
| 191 |
+
if datetime.now().date() != self.last_reset:
|
| 192 |
+
self.daily_loss = 0
|
| 193 |
+
self.consecutive_losses = 0
|
| 194 |
+
self.last_reset = datetime.now().date()
|
| 195 |
+
|
| 196 |
+
position_percent = (position_value / portfolio_value) * 100
|
| 197 |
+
|
| 198 |
+
# Check max position size
|
| 199 |
+
if position_percent > self.config.get('max_position_size_percent', 2):
|
| 200 |
+
return {"allowed": False, "reason": f"Position too large: {position_percent:.2f}% > {self.config['max_position_size_percent']}%"}
|
| 201 |
+
|
| 202 |
+
# Check memecoin allocation
|
| 203 |
+
if self.is_memecoin(symbol):
|
| 204 |
+
memecoin_allocation = self.get_memecoin_allocation()
|
| 205 |
+
if memecoin_allocation + position_percent > self.config.get('memecoin_allocation_percent', 10):
|
| 206 |
+
return {"allowed": False, "reason": f"Memecoin allocation limit exceeded"}
|
| 207 |
+
|
| 208 |
+
# Check daily loss limit
|
| 209 |
+
if self.daily_loss >= (portfolio_value * (self.config.get('daily_loss_limit_percent', 6) / 100)):
|
| 210 |
+
return {"allowed": False, "reason": "Daily loss limit reached"}
|
| 211 |
+
|
| 212 |
+
# Check consecutive losses
|
| 213 |
+
if self.consecutive_losses >= self.config.get('max_consecutive_losses', 3):
|
| 214 |
+
return {"allowed": False, "reason": "Max consecutive losses reached"}
|
| 215 |
+
|
| 216 |
+
return {"allowed": True}
|
| 217 |
+
|
| 218 |
+
def is_memecoin(self, symbol: str) -> bool:
|
| 219 |
+
"""Check if symbol is a memecoin"""
|
| 220 |
+
memecoins = ['DOGE', 'SHIB', 'PEPE', 'FLOKI', 'BONK', 'WIF', 'MEW']
|
| 221 |
+
return symbol.upper() in memecoins
|
| 222 |
+
|
| 223 |
+
def get_memecoin_allocation(self) -> float:
|
| 224 |
+
"""Get current memecoin allocation percentage"""
|
| 225 |
+
# Calculate from holdings
|
| 226 |
+
return 0.0 # Placeholder
|
| 227 |
+
|
| 228 |
+
def record_loss(self, loss_amount: float):
|
| 229 |
+
"""Record a losing trade"""
|
| 230 |
+
self.daily_loss += loss_amount
|
| 231 |
+
self.consecutive_losses += 1
|
| 232 |
+
|
| 233 |
+
def record_profit(self, profit_amount: float):
|
| 234 |
+
"""Record a winning trade"""
|
| 235 |
+
self.consecutive_losses = 0
|
| 236 |
+
|
| 237 |
+
# Export as tools module
|
| 238 |
+
tools = TradingExecutor()
|
supervisor.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Supervisor for OpenClaw Agent Army
|
| 4 |
+
Manages multiple agents, restarts on failure, writes system state
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import json
|
| 10 |
+
import time
|
| 11 |
+
import subprocess
|
| 12 |
+
import threading
|
| 13 |
+
import http.server
|
| 14 |
+
import socketserver
|
| 15 |
+
from datetime import datetime
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
# Configuration
|
| 19 |
+
AGENTS = {
|
| 20 |
+
'gig1': {'file': 'agents/gig_real.py', 'type': 'gig', 'platform': 'freelancer'},
|
| 21 |
+
'gig2': {'file': 'agents/gig_real.py', 'type': 'gig', 'platform': 'upwork'},
|
| 22 |
+
'gig3': {'file': 'agents/gig_real.py', 'type': 'gig', 'platform': 'fiverr'},
|
| 23 |
+
'trading1': {'file': 'agents/trading_real.py', 'type': 'trading', 'strategy': 'trend'},
|
| 24 |
+
'trading2': {'file': 'agents/trading_real.py', 'type': 'trading', 'strategy': 'reversion'},
|
| 25 |
+
'editor': {'file': 'agents/editor_improved.py', 'type': 'supervisor'}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
BASE_DIR = Path(__file__).parent
|
| 29 |
+
STATE_DIR = BASE_DIR / "state"
|
| 30 |
+
LOGS_DIR = BASE_DIR / "logs"
|
| 31 |
+
STATE_DIR.mkdir(exist_ok=True)
|
| 32 |
+
LOGS_DIR.mkdir(exist_ok=True)
|
| 33 |
+
|
| 34 |
+
class AgentProcess:
|
| 35 |
+
def __init__(self, name, config):
|
| 36 |
+
self.name = name
|
| 37 |
+
self.config = config
|
| 38 |
+
self.process = None
|
| 39 |
+
self.start_time = None
|
| 40 |
+
self.restart_count = 0
|
| 41 |
+
self.state_file = STATE_DIR / f"{name}.json"
|
| 42 |
+
self.log_file = LOGS_DIR / f"{name}.log"
|
| 43 |
+
|
| 44 |
+
def start(self):
|
| 45 |
+
script_path = BASE_DIR / self.config['file']
|
| 46 |
+
if not script_path.exists():
|
| 47 |
+
print(f"[{self.name}] Script not found: {script_path}")
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
# Open log file
|
| 51 |
+
log_f = open(self.log_file, 'a')
|
| 52 |
+
self.process = subprocess.Popen(
|
| 53 |
+
[sys.executable, str(script_path)],
|
| 54 |
+
stdout=log_f,
|
| 55 |
+
stderr=subprocess.STDOUT,
|
| 56 |
+
cwd=BASE_DIR
|
| 57 |
+
)
|
| 58 |
+
self.start_time = datetime.now()
|
| 59 |
+
print(f"[{self.name}] Started (PID {self.process.pid})")
|
| 60 |
+
return True
|
| 61 |
+
|
| 62 |
+
def check(self):
|
| 63 |
+
if self.process is None:
|
| 64 |
+
return False
|
| 65 |
+
if self.process.poll() is not None:
|
| 66 |
+
# Process died
|
| 67 |
+
exit_code = self.process.returncode
|
| 68 |
+
print(f"[{self.name}] Process exited with code {exit_code}")
|
| 69 |
+
self.process = None
|
| 70 |
+
return False
|
| 71 |
+
return True
|
| 72 |
+
|
| 73 |
+
def stop(self):
|
| 74 |
+
if self.process and self.process.poll() is None:
|
| 75 |
+
self.process.terminate()
|
| 76 |
+
try:
|
| 77 |
+
self.process.wait(timeout=5)
|
| 78 |
+
except subprocess.TimeoutExpired:
|
| 79 |
+
self.process.kill()
|
| 80 |
+
self.process = None
|
| 81 |
+
|
| 82 |
+
def get_state(self):
|
| 83 |
+
if self.state_file.exists():
|
| 84 |
+
try:
|
| 85 |
+
with open(self.state_file) as f:
|
| 86 |
+
return json.load(f)
|
| 87 |
+
except:
|
| 88 |
+
return {}
|
| 89 |
+
return {}
|
| 90 |
+
|
| 91 |
+
def run(self):
|
| 92 |
+
while True:
|
| 93 |
+
if not self.check():
|
| 94 |
+
self.restart_count += 1
|
| 95 |
+
print(f"[{self.name}] Restarting (attempt {self.restart_count})...")
|
| 96 |
+
time.sleep(2) # backoff
|
| 97 |
+
if not self.start():
|
| 98 |
+
time.sleep(10) # wait before retry
|
| 99 |
+
continue
|
| 100 |
+
|
| 101 |
+
# Read state periodically
|
| 102 |
+
state = self.get_state()
|
| 103 |
+
if state:
|
| 104 |
+
# Could send to central logger
|
| 105 |
+
pass
|
| 106 |
+
|
| 107 |
+
time.sleep(5)
|
| 108 |
+
|
| 109 |
+
def system_state(agents):
|
| 110 |
+
"""Write overall system state"""
|
| 111 |
+
state = {
|
| 112 |
+
"timestamp": datetime.now().isoformat(),
|
| 113 |
+
"agents": {}
|
| 114 |
+
}
|
| 115 |
+
for name, agent in agents.items():
|
| 116 |
+
proc_state = {
|
| 117 |
+
"restart_count": agent.restart_count,
|
| 118 |
+
"running": agent.process is not None and agent.process.poll() is None,
|
| 119 |
+
"pid": agent.process.pid if agent.process and agent.process.poll() is None else None,
|
| 120 |
+
"started_at": agent.start_time.isoformat() if agent.start_time else None
|
| 121 |
+
}
|
| 122 |
+
# Merge agent's own state
|
| 123 |
+
agent_state = agent.get_state()
|
| 124 |
+
proc_state.update(agent_state)
|
| 125 |
+
state["agents"][name] = proc_state
|
| 126 |
+
|
| 127 |
+
state_file = STATE_DIR / "system.json"
|
| 128 |
+
with open(state_file, 'w') as f:
|
| 129 |
+
json.dump(state, f, indent=2)
|
| 130 |
+
|
| 131 |
+
def main():
|
| 132 |
+
print(f"[Supervisor] Starting at {datetime.now()}")
|
| 133 |
+
agents = {}
|
| 134 |
+
|
| 135 |
+
# Start all agents
|
| 136 |
+
for name, config in AGENTS.items():
|
| 137 |
+
agent = AgentProcess(name, config)
|
| 138 |
+
agents[name] = agent
|
| 139 |
+
agent.start()
|
| 140 |
+
|
| 141 |
+
# Start web UI in background thread
|
| 142 |
+
web_thread = threading.Thread(target=start_web_server, args=(agents,), daemon=True)
|
| 143 |
+
web_thread.start()
|
| 144 |
+
|
| 145 |
+
# Monitor loop
|
| 146 |
+
try:
|
| 147 |
+
while True:
|
| 148 |
+
time.sleep(30)
|
| 149 |
+
# Check all agents
|
| 150 |
+
for agent in agents.values():
|
| 151 |
+
if not agent.check():
|
| 152 |
+
# Will auto-restart on next iteration
|
| 153 |
+
pass
|
| 154 |
+
|
| 155 |
+
# Write system state every 5 minutes
|
| 156 |
+
if int(time.time()) % 300 < 30:
|
| 157 |
+
system_state(agents)
|
| 158 |
+
|
| 159 |
+
except KeyboardInterrupt:
|
| 160 |
+
print("\n[Supervisor] Shutting down...")
|
| 161 |
+
for agent in agents.values():
|
| 162 |
+
agent.stop()
|
| 163 |
+
|
| 164 |
+
# Web UI Handler
|
| 165 |
+
class AgentHandler(http.server.BaseHTTPRequestHandler):
|
| 166 |
+
def do_GET(self):
|
| 167 |
+
if self.path == '/health' or self.path == '/':
|
| 168 |
+
self.send_response(200)
|
| 169 |
+
self.send_header('Content-type', 'application/json')
|
| 170 |
+
self.end_headers()
|
| 171 |
+
|
| 172 |
+
status = {
|
| 173 |
+
"status": "ok",
|
| 174 |
+
"timestamp": datetime.now().isoformat(),
|
| 175 |
+
"agents": {}
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
agent_names = list(AGENTS.keys())
|
| 179 |
+
for name, agent in self.server.agents.items():
|
| 180 |
+
agent_status = {
|
| 181 |
+
"running": agent.process is not None and agent.process.poll() is None,
|
| 182 |
+
"restart_count": agent.restart_count,
|
| 183 |
+
"pid": agent.process.pid if agent.process and agent.process.poll() is None else None,
|
| 184 |
+
"last_checked": datetime.now().isoformat()
|
| 185 |
+
}
|
| 186 |
+
# Merge agent's own state (read from file)
|
| 187 |
+
state = agent.get_state()
|
| 188 |
+
agent_status.update(state)
|
| 189 |
+
status["agents"][name] = agent_status
|
| 190 |
+
|
| 191 |
+
self.wfile.write(json.dumps(status, indent=2).encode())
|
| 192 |
+
|
| 193 |
+
elif self.path == '/logs' or self.path == '/archive':
|
| 194 |
+
self.send_response(200)
|
| 195 |
+
self.send_header('Content-type', 'application/json')
|
| 196 |
+
self.end_headers()
|
| 197 |
+
|
| 198 |
+
# Return recent logs from all agents
|
| 199 |
+
logs = {}
|
| 200 |
+
for name in AGENTS.keys():
|
| 201 |
+
log_file = BASE_DIR / "logs" / f"{name}.log"
|
| 202 |
+
if log_file.exists():
|
| 203 |
+
try:
|
| 204 |
+
with open(log_file) as f:
|
| 205 |
+
lines = f.readlines()
|
| 206 |
+
logs[name] = [line.strip() for line in lines[-10:]]
|
| 207 |
+
except:
|
| 208 |
+
logs[name] = []
|
| 209 |
+
else:
|
| 210 |
+
logs[name] = []
|
| 211 |
+
|
| 212 |
+
resp = {
|
| 213 |
+
"timestamp": datetime.now().isoformat(),
|
| 214 |
+
"logs": logs
|
| 215 |
+
}
|
| 216 |
+
self.wfile.write(json.dumps(resp, indent=2).encode())
|
| 217 |
+
|
| 218 |
+
else:
|
| 219 |
+
self.send_response(404)
|
| 220 |
+
self.end_headers()
|
| 221 |
+
self.wfile.write(b'Not Found')
|
| 222 |
+
|
| 223 |
+
def do_POST(self):
|
| 224 |
+
self.send_response(501)
|
| 225 |
+
self.end_headers()
|
| 226 |
+
self.wfile.write(b'Not Implemented')
|
| 227 |
+
|
| 228 |
+
def start_web_server(agents, port=None):
|
| 229 |
+
"""Start the HTTP server in a separate thread"""
|
| 230 |
+
if port is None:
|
| 231 |
+
port = int(os.getenv('PORT', 8000))
|
| 232 |
+
try:
|
| 233 |
+
with socketserver.TCPServer(("0.0.0.0", port), AgentHandler) as httpd:
|
| 234 |
+
httpd.agents = agents
|
| 235 |
+
print(f"[Web UI] Running on port {port}")
|
| 236 |
+
httpd.serve_forever()
|
| 237 |
+
except OSError as e:
|
| 238 |
+
print(f"[Web UI] Failed to start on port {port}: {e}")
|
| 239 |
+
|
| 240 |
+
if __name__ == "__main__":
|
| 241 |
+
main()
|