youclaw 4.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- youclaw/__init__.py +24 -0
- youclaw/bot.py +185 -0
- youclaw/cli.py +469 -0
- youclaw/commands.py +151 -0
- youclaw/config.py +170 -0
- youclaw/core_skills.py +210 -0
- youclaw/dashboard.py +1347 -0
- youclaw/discord_handler.py +187 -0
- youclaw/env_manager.py +61 -0
- youclaw/main.py +273 -0
- youclaw/memory_manager.py +440 -0
- youclaw/ollama_client.py +486 -0
- youclaw/personality_manager.py +42 -0
- youclaw/scheduler_manager.py +226 -0
- youclaw/search_client.py +66 -0
- youclaw/skills_manager.py +127 -0
- youclaw/telegram_handler.py +181 -0
- youclaw/vector_manager.py +94 -0
- youclaw-4.6.0.dist-info/LICENSE +21 -0
- youclaw-4.6.0.dist-info/METADATA +128 -0
- youclaw-4.6.0.dist-info/RECORD +24 -0
- youclaw-4.6.0.dist-info/WHEEL +5 -0
- youclaw-4.6.0.dist-info/entry_points.txt +2 -0
- youclaw-4.6.0.dist-info/top_level.txt +1 -0
youclaw/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YouClaw - Your Personal AI Assistant
|
|
3
|
+
Universal Neural Core for Telegram, Discord, and Web Dashboard
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__version__ = "4.6.0"
|
|
7
|
+
__author__ = "Imran"
|
|
8
|
+
|
|
9
|
+
from .bot import youclaw_bot, main
|
|
10
|
+
from .config import config
|
|
11
|
+
from .memory_manager import memory_manager
|
|
12
|
+
from .ollama_client import ollama_client
|
|
13
|
+
from .scheduler_manager import scheduler_manager
|
|
14
|
+
from .skills_manager import skill_manager
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"youclaw_bot",
|
|
18
|
+
"main",
|
|
19
|
+
"config",
|
|
20
|
+
"memory_manager",
|
|
21
|
+
"ollama_client",
|
|
22
|
+
"scheduler_manager",
|
|
23
|
+
"skill_manager",
|
|
24
|
+
]
|
youclaw/bot.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YouClaw - Your Personal AI Assistant
|
|
3
|
+
Main bot orchestrator that runs Discord and Telegram handlers concurrently.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import signal
|
|
9
|
+
import sys
|
|
10
|
+
from .config import config
|
|
11
|
+
from .ollama_client import ollama_client
|
|
12
|
+
from .memory_manager import memory_manager
|
|
13
|
+
from .scheduler_manager import scheduler_manager
|
|
14
|
+
from .discord_handler import discord_handler
|
|
15
|
+
from .telegram_handler import telegram_handler
|
|
16
|
+
from .dashboard import run_dashboard
|
|
17
|
+
from .skills_manager import skill_manager
|
|
18
|
+
|
|
19
|
+
# Set up logging
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=getattr(logging, config.bot.log_level),
|
|
22
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
23
|
+
handlers=[
|
|
24
|
+
logging.StreamHandler(sys.stdout),
|
|
25
|
+
logging.FileHandler('youclaw.log')
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class YouClaw:
|
|
33
|
+
"""Main YouClaw bot orchestrator"""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.running = False
|
|
37
|
+
self.platform_tasks = []
|
|
38
|
+
self.dashboard_task = None
|
|
39
|
+
self.stop_event = asyncio.Event()
|
|
40
|
+
|
|
41
|
+
async def initialize(self):
|
|
42
|
+
"""Initialize bot cores"""
|
|
43
|
+
logger.info("š¦ Initializing YouClaw...")
|
|
44
|
+
|
|
45
|
+
# Initialize memory manager
|
|
46
|
+
await memory_manager.initialize()
|
|
47
|
+
|
|
48
|
+
# Initialize Ollama client
|
|
49
|
+
await ollama_client.initialize()
|
|
50
|
+
|
|
51
|
+
# Initialize scheduler manager
|
|
52
|
+
scheduler_manager.initialize(self, config.bot.database_path)
|
|
53
|
+
|
|
54
|
+
# Load dynamic skills
|
|
55
|
+
skill_manager.load_dynamic_skills()
|
|
56
|
+
|
|
57
|
+
logger.info("š¤ YouClaw Cores Initialized")
|
|
58
|
+
|
|
59
|
+
# Check Ollama health
|
|
60
|
+
if not await ollama_client.check_health():
|
|
61
|
+
logger.error("ā Ollama is not available!")
|
|
62
|
+
# Don't exit here, maybe user will fix it via dashboard?
|
|
63
|
+
# Actually, we need Ollama for core features, but dashboard should stay up.
|
|
64
|
+
|
|
65
|
+
# Load initial config from DB
|
|
66
|
+
await config.refresh_from_db()
|
|
67
|
+
logger.info("š YouClaw initialization complete!")
|
|
68
|
+
|
|
69
|
+
async def _start_platforms(self):
|
|
70
|
+
"""Internal method to start enabled platforms"""
|
|
71
|
+
# Clear existing tasks
|
|
72
|
+
for task in self.platform_tasks:
|
|
73
|
+
task.cancel()
|
|
74
|
+
self.platform_tasks = []
|
|
75
|
+
|
|
76
|
+
if config.discord.enabled and config.discord.token:
|
|
77
|
+
logger.info("Starting Discord handler...")
|
|
78
|
+
task = asyncio.create_task(discord_handler.start())
|
|
79
|
+
self.platform_tasks.append(task)
|
|
80
|
+
elif config.discord.enabled:
|
|
81
|
+
logger.warning("Discord enabled but no token provided!")
|
|
82
|
+
|
|
83
|
+
if config.telegram.enabled and config.telegram.token:
|
|
84
|
+
logger.info("Starting Telegram handler...")
|
|
85
|
+
task = asyncio.create_task(telegram_handler.start())
|
|
86
|
+
self.platform_tasks.append(task)
|
|
87
|
+
elif config.telegram.enabled:
|
|
88
|
+
logger.warning("Telegram enabled but no token provided!")
|
|
89
|
+
|
|
90
|
+
if not self.platform_tasks:
|
|
91
|
+
logger.info("No platform handlers started. Bot is in standby mode.")
|
|
92
|
+
|
|
93
|
+
async def start(self):
|
|
94
|
+
"""Start all enabled platform handlers"""
|
|
95
|
+
self.running = True
|
|
96
|
+
await self._start_platforms()
|
|
97
|
+
|
|
98
|
+
logger.info("ā
All platforms started. YouClaw is now online! š¦")
|
|
99
|
+
|
|
100
|
+
# Keep running until stop_event is set
|
|
101
|
+
await self.stop_event.wait()
|
|
102
|
+
|
|
103
|
+
async def restart_handlers(self):
|
|
104
|
+
"""Refresh config and restart platform handlers ONLY (keep dashboard alive)"""
|
|
105
|
+
logger.info("ā»ļø Restarting handlers with fresh config...")
|
|
106
|
+
|
|
107
|
+
# Stop current platform handlers
|
|
108
|
+
await discord_handler.stop()
|
|
109
|
+
await telegram_handler.stop()
|
|
110
|
+
|
|
111
|
+
# Refresh configuration from database
|
|
112
|
+
await config.refresh_from_db()
|
|
113
|
+
|
|
114
|
+
# Start platforms again
|
|
115
|
+
await self._start_platforms()
|
|
116
|
+
logger.info("ā
Handlers restarted successfully")
|
|
117
|
+
|
|
118
|
+
async def shutdown(self):
|
|
119
|
+
"""Gracefully shutdown all components"""
|
|
120
|
+
logger.info("š Shutting down YouClaw...")
|
|
121
|
+
self.running = False
|
|
122
|
+
|
|
123
|
+
# Set stop event to break the start() wait
|
|
124
|
+
self.stop_event.set()
|
|
125
|
+
|
|
126
|
+
# Cancel all platform tasks
|
|
127
|
+
for task in self.platform_tasks:
|
|
128
|
+
task.cancel()
|
|
129
|
+
|
|
130
|
+
# Cancel dashboard
|
|
131
|
+
if self.dashboard_task:
|
|
132
|
+
self.dashboard_task.cancel()
|
|
133
|
+
|
|
134
|
+
# Stop platform handlers
|
|
135
|
+
await discord_handler.stop()
|
|
136
|
+
await telegram_handler.stop()
|
|
137
|
+
|
|
138
|
+
# Close connections
|
|
139
|
+
await ollama_client.close()
|
|
140
|
+
await memory_manager.close()
|
|
141
|
+
|
|
142
|
+
logger.info("š YouClaw shutdown complete")
|
|
143
|
+
|
|
144
|
+
def handle_signal(self, sig):
|
|
145
|
+
"""Handle shutdown signals"""
|
|
146
|
+
logger.info(f"Received signal {sig}, initiating shutdown...")
|
|
147
|
+
asyncio.create_task(self.shutdown())
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Global bot instance
|
|
151
|
+
youclaw_bot = YouClaw()
|
|
152
|
+
|
|
153
|
+
async def main():
|
|
154
|
+
"""Main entry point"""
|
|
155
|
+
# Initialize cores
|
|
156
|
+
await youclaw_bot.initialize()
|
|
157
|
+
|
|
158
|
+
# Set up signal handlers
|
|
159
|
+
loop = asyncio.get_event_loop()
|
|
160
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
161
|
+
loop.add_signal_handler(sig, lambda s=sig: youclaw_bot.handle_signal(s))
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Start Dashboard as a background task
|
|
165
|
+
# We pass the bot instance so the dashboard can control it
|
|
166
|
+
logger.info("Starting Web Dashboard...")
|
|
167
|
+
youclaw_bot.dashboard_task = asyncio.create_task(run_dashboard(youclaw_bot, port=8080))
|
|
168
|
+
|
|
169
|
+
# Start bot handlers (this blocks until shutdown)
|
|
170
|
+
await youclaw_bot.start()
|
|
171
|
+
|
|
172
|
+
except asyncio.CancelledError:
|
|
173
|
+
pass
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Fatal error: {e}", exc_info=True)
|
|
176
|
+
finally:
|
|
177
|
+
await youclaw_bot.shutdown()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
try:
|
|
182
|
+
asyncio.run(main())
|
|
183
|
+
except KeyboardInterrupt:
|
|
184
|
+
pass
|
|
185
|
+
|
youclaw/cli.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YouClaw CLI - Command Line Interface
|
|
3
|
+
Provides commands for installation, health checks, and management.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import subprocess
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import time
|
|
11
|
+
import asyncio
|
|
12
|
+
import argparse
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
# Add parent directory to path for imports if running locally
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class YouClawCLI:
|
|
21
|
+
"""YouClaw command line interface"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.project_dir = Path(__file__).parent
|
|
25
|
+
|
|
26
|
+
def cmd_install(self, args):
|
|
27
|
+
"""Run installation script"""
|
|
28
|
+
print("š¦ Running YouClaw installation...")
|
|
29
|
+
install_script = self.project_dir / "install.sh"
|
|
30
|
+
|
|
31
|
+
if not install_script.exists():
|
|
32
|
+
print("ā install.sh not found!")
|
|
33
|
+
return 1
|
|
34
|
+
|
|
35
|
+
result = subprocess.run(["bash", str(install_script)])
|
|
36
|
+
return result.returncode
|
|
37
|
+
|
|
38
|
+
def cmd_check(self, args):
|
|
39
|
+
"""Run health checks"""
|
|
40
|
+
print("š¦ YouClaw Health Check")
|
|
41
|
+
print("=" * 50)
|
|
42
|
+
|
|
43
|
+
# Check Python version
|
|
44
|
+
print("\nš¦ Python Version:")
|
|
45
|
+
python_version = sys.version.split()[0]
|
|
46
|
+
print(f" {python_version}", end="")
|
|
47
|
+
if sys.version_info >= (3, 10):
|
|
48
|
+
print(" ā
")
|
|
49
|
+
else:
|
|
50
|
+
print(" ā (Need 3.10+)")
|
|
51
|
+
|
|
52
|
+
# Check Ollama
|
|
53
|
+
print("\nš¤ Ollama:")
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
["curl", "-s", "http://localhost:11434/api/tags"],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
timeout=5
|
|
59
|
+
)
|
|
60
|
+
if result.returncode == 0:
|
|
61
|
+
print(" Connected ā
")
|
|
62
|
+
# Parse models
|
|
63
|
+
import json
|
|
64
|
+
try:
|
|
65
|
+
data = json.loads(result.stdout)
|
|
66
|
+
models = [m["name"] for m in data.get("models", [])]
|
|
67
|
+
print(f" Models: {', '.join(models) if models else 'None'}")
|
|
68
|
+
except:
|
|
69
|
+
pass
|
|
70
|
+
else:
|
|
71
|
+
print(" Not running ā")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print(f" Error: {e} ā")
|
|
74
|
+
|
|
75
|
+
# Check virtual environment
|
|
76
|
+
print("\nš Virtual Environment:")
|
|
77
|
+
venv_path = self.project_dir / "venv"
|
|
78
|
+
if venv_path.exists():
|
|
79
|
+
print(f" {venv_path} ā
")
|
|
80
|
+
else:
|
|
81
|
+
print(" Not found ā")
|
|
82
|
+
|
|
83
|
+
# Check .env file
|
|
84
|
+
print("\nāļø Configuration:")
|
|
85
|
+
env_path = self.project_dir / ".env"
|
|
86
|
+
if env_path.exists():
|
|
87
|
+
print(" .env file exists ā
")
|
|
88
|
+
# Check for tokens
|
|
89
|
+
with open(env_path) as f:
|
|
90
|
+
content = f.read()
|
|
91
|
+
has_discord = "DISCORD_BOT_TOKEN=" in content and "your_discord" not in content
|
|
92
|
+
has_telegram = "TELEGRAM_BOT_TOKEN=" in content and "your_telegram" not in content
|
|
93
|
+
|
|
94
|
+
if has_discord:
|
|
95
|
+
print(" Discord token configured ā
")
|
|
96
|
+
else:
|
|
97
|
+
print(" Discord token not set ā ļø")
|
|
98
|
+
|
|
99
|
+
if has_telegram:
|
|
100
|
+
print(" Telegram token configured ā
")
|
|
101
|
+
else:
|
|
102
|
+
print(" Telegram token not set ā ļø")
|
|
103
|
+
else:
|
|
104
|
+
print(" .env file missing ā")
|
|
105
|
+
|
|
106
|
+
# Check database
|
|
107
|
+
print("\nš¾ Database:")
|
|
108
|
+
db_path = self.project_dir / "data" / "bot.db"
|
|
109
|
+
if db_path.exists():
|
|
110
|
+
size = db_path.stat().st_size
|
|
111
|
+
print(f" {db_path} ({size} bytes) ā
")
|
|
112
|
+
else:
|
|
113
|
+
print(" Not created yet (will be created on first run)")
|
|
114
|
+
|
|
115
|
+
# Check systemd service
|
|
116
|
+
print("\nš§ Systemd Service:")
|
|
117
|
+
try:
|
|
118
|
+
result = subprocess.run(
|
|
119
|
+
["systemctl", "--user", "is-active", "youclaw"],
|
|
120
|
+
capture_output=True,
|
|
121
|
+
text=True
|
|
122
|
+
)
|
|
123
|
+
status = result.stdout.strip()
|
|
124
|
+
if status == "active":
|
|
125
|
+
print(" Running ā
")
|
|
126
|
+
elif status == "inactive":
|
|
127
|
+
print(" Stopped ā ļø")
|
|
128
|
+
else:
|
|
129
|
+
print(f" Status: {status}")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f" Not installed ā ļø")
|
|
132
|
+
|
|
133
|
+
print("\n" + "=" * 50)
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
def cmd_status(self, args):
|
|
137
|
+
"""Show service status"""
|
|
138
|
+
print("š¦ YouClaw Status\n")
|
|
139
|
+
|
|
140
|
+
pid_file = Path("./data/youclaw.pid")
|
|
141
|
+
|
|
142
|
+
if not pid_file.exists():
|
|
143
|
+
print("Status: āŖ Not Running")
|
|
144
|
+
print("\nStart with: youclaw start")
|
|
145
|
+
return 1
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
pid = int(pid_file.read_text().strip())
|
|
149
|
+
|
|
150
|
+
# Check if process is actually running
|
|
151
|
+
os.kill(pid, 0)
|
|
152
|
+
|
|
153
|
+
# Get process info
|
|
154
|
+
import psutil
|
|
155
|
+
try:
|
|
156
|
+
process = psutil.Process(pid)
|
|
157
|
+
uptime_seconds = time.time() - process.create_time()
|
|
158
|
+
hours = int(uptime_seconds // 3600)
|
|
159
|
+
minutes = int((uptime_seconds % 3600) // 60)
|
|
160
|
+
|
|
161
|
+
print(f"Status: ā
Running")
|
|
162
|
+
print(f"PID: {pid}")
|
|
163
|
+
print(f"Uptime: {hours}h {minutes}m")
|
|
164
|
+
print(f"Memory: {process.memory_info().rss / 1024 / 1024:.1f} MB")
|
|
165
|
+
print(f"\nš Dashboard: http://localhost:8080")
|
|
166
|
+
return 0
|
|
167
|
+
except ImportError:
|
|
168
|
+
# psutil not available, basic info only
|
|
169
|
+
print(f"Status: ā
Running")
|
|
170
|
+
print(f"PID: {pid}")
|
|
171
|
+
print(f"\nš Dashboard: http://localhost:8080")
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
except (ProcessLookupError, ValueError):
|
|
175
|
+
print("Status: ā ļø Dead (PID file exists but process not found)")
|
|
176
|
+
print("\nClean up with: youclaw stop")
|
|
177
|
+
return 1
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"Status: ā Error: {e}")
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
def cmd_logs(self, args):
|
|
183
|
+
"""View logs"""
|
|
184
|
+
print("š¦ YouClaw Logs (Ctrl+C to exit)\n")
|
|
185
|
+
|
|
186
|
+
cmd = ["journalctl", "--user", "-u", "youclaw"]
|
|
187
|
+
|
|
188
|
+
if args.follow:
|
|
189
|
+
cmd.append("-f")
|
|
190
|
+
|
|
191
|
+
if args.lines:
|
|
192
|
+
cmd.extend(["-n", str(args.lines)])
|
|
193
|
+
|
|
194
|
+
result = subprocess.run(cmd)
|
|
195
|
+
return result.returncode
|
|
196
|
+
|
|
197
|
+
async def run_wizard(self):
|
|
198
|
+
"""Interactive on-boarding wizard for YouClaw clones"""
|
|
199
|
+
print("\n" + "š¦" * 10)
|
|
200
|
+
print("WELCOME TO THE YOUCLAW NEURAL WIZARD")
|
|
201
|
+
print("Preparing your personal AI Assistant for mission departure...")
|
|
202
|
+
print("š¦" * 10 + "\n")
|
|
203
|
+
|
|
204
|
+
print("[!] Tip: Press ENTER to skip any step and configure later via Dashboard.\n")
|
|
205
|
+
|
|
206
|
+
# š¤ Ollama Configuration
|
|
207
|
+
print("š¤ Ollama AI Engine Configuration")
|
|
208
|
+
ollama_host = input(" - Ollama Host URL (default: http://localhost:11434): ").strip() or "http://localhost:11434"
|
|
209
|
+
ollama_model = input(" - Ollama Model (default: qwen2.5:1.5b-instruct): ").strip() or "qwen2.5:1.5b-instruct"
|
|
210
|
+
|
|
211
|
+
# š°ļø Telegram Setup
|
|
212
|
+
print("\nš°ļø Telegram Bot Setup")
|
|
213
|
+
tg_token = input(" - Bot Token (from @BotFather): ").strip()
|
|
214
|
+
|
|
215
|
+
# š¬ Discord Setup
|
|
216
|
+
print("\nš¬ Discord Bot Setup")
|
|
217
|
+
dc_token = input(" - Bot Token (from Discord Dev Portal): ").strip()
|
|
218
|
+
|
|
219
|
+
# š Search Engine Setup
|
|
220
|
+
print("\nš Neural Search Engine")
|
|
221
|
+
search_url = input(" - Search URL (e.g. http://ip:8080/search): ").strip()
|
|
222
|
+
|
|
223
|
+
# š§ Email Setup
|
|
224
|
+
print("\nš§ Email Link Protocol")
|
|
225
|
+
email_user = input(" - Email Address: ").strip()
|
|
226
|
+
email_pass = input(" - App Password (not your login password!): ").strip()
|
|
227
|
+
email_imap = input(" - IMAP Host (default: imap.gmail.com): ").strip() or "imap.gmail.com"
|
|
228
|
+
email_smtp = input(" - SMTP Host (default: smtp.gmail.com): ").strip() or "smtp.gmail.com"
|
|
229
|
+
|
|
230
|
+
# Write to .env
|
|
231
|
+
env_path = Path(".env")
|
|
232
|
+
env_content = [
|
|
233
|
+
"# YouClaw Managed Configuration",
|
|
234
|
+
f"OLLAMA_HOST={ollama_host}",
|
|
235
|
+
f"OLLAMA_MODEL={ollama_model}",
|
|
236
|
+
f"TELEGRAM_BOT_TOKEN={tg_token or ''}",
|
|
237
|
+
f"ENABLE_TELEGRAM={'true' if tg_token else 'false'}",
|
|
238
|
+
f"DISCORD_BOT_TOKEN={dc_token or ''}",
|
|
239
|
+
f"ENABLE_DISCORD={'true' if dc_token else 'false'}",
|
|
240
|
+
f"SEARCH_ENGINE_URL={search_url or 'http://localhost:8080/search'}",
|
|
241
|
+
f"EMAIL_USER={email_user or ''}",
|
|
242
|
+
f"EMAIL_PASSWORD={email_pass or ''}",
|
|
243
|
+
f"EMAIL_IMAP_HOST={email_imap}",
|
|
244
|
+
f"EMAIL_SMTP_HOST={email_smtp}",
|
|
245
|
+
f"ENABLE_EMAIL={'true' if email_user else 'false'}",
|
|
246
|
+
"DATABASE_PATH=./data/bot.db",
|
|
247
|
+
"ADMIN_USER_IDENTITY=telegram:default"
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
with open(env_path, "w") as f:
|
|
251
|
+
f.write("\n".join(env_content))
|
|
252
|
+
|
|
253
|
+
print("\n⨠Configuration Synced Successfully!")
|
|
254
|
+
print("š Mission Control will be available at: http://localhost:8080")
|
|
255
|
+
print("š Type 'youclaw start' to begin your AI journey.\n")
|
|
256
|
+
|
|
257
|
+
def cmd_start(self, args):
|
|
258
|
+
"""Start YouClaw - Runs wizard if unconfigured"""
|
|
259
|
+
if not Path(".env").exists():
|
|
260
|
+
print("ā ļø No configuration found. Launching Neural Wizard...")
|
|
261
|
+
asyncio.run(self.run_wizard())
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
# Check if already running
|
|
265
|
+
pid_file = Path("./data/youclaw.pid")
|
|
266
|
+
if pid_file.exists():
|
|
267
|
+
try:
|
|
268
|
+
pid = int(pid_file.read_text().strip())
|
|
269
|
+
# Check if process is actually running
|
|
270
|
+
os.kill(pid, 0)
|
|
271
|
+
print(f"ā ļø YouClaw is already running (PID: {pid})")
|
|
272
|
+
print(" Use 'youclaw stop' first, or 'youclaw restart'")
|
|
273
|
+
return 1
|
|
274
|
+
except (ProcessLookupError, ValueError):
|
|
275
|
+
# PID file exists but process is dead, clean it up
|
|
276
|
+
pid_file.unlink()
|
|
277
|
+
|
|
278
|
+
print("š¦ Starting YouClaw in background...")
|
|
279
|
+
|
|
280
|
+
# Start as background daemon
|
|
281
|
+
try:
|
|
282
|
+
import sys
|
|
283
|
+
import os
|
|
284
|
+
|
|
285
|
+
# Fork the process
|
|
286
|
+
pid = os.fork()
|
|
287
|
+
if pid > 0:
|
|
288
|
+
# Parent process - save PID and exit
|
|
289
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
290
|
+
pid_file.write_text(str(pid))
|
|
291
|
+
print(f"ā
YouClaw started (PID: {pid})")
|
|
292
|
+
print("š Dashboard: http://localhost:8080")
|
|
293
|
+
print("\nManage with:")
|
|
294
|
+
print(" youclaw status - Check status")
|
|
295
|
+
print(" youclaw stop - Stop service")
|
|
296
|
+
print(" youclaw restart - Restart service")
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
# Child process - become daemon
|
|
300
|
+
os.setsid() # Create new session
|
|
301
|
+
|
|
302
|
+
# Redirect stdout/stderr to log file
|
|
303
|
+
log_file = open("youclaw.log", "a")
|
|
304
|
+
os.dup2(log_file.fileno(), sys.stdout.fileno())
|
|
305
|
+
os.dup2(log_file.fileno(), sys.stderr.fileno())
|
|
306
|
+
|
|
307
|
+
# Run the bot
|
|
308
|
+
from .bot import main
|
|
309
|
+
asyncio.run(main())
|
|
310
|
+
|
|
311
|
+
except AttributeError:
|
|
312
|
+
# Windows doesn't support fork, run in foreground
|
|
313
|
+
print("š¦ Starting YouClaw (foreground mode on Windows)...")
|
|
314
|
+
print(" Press Ctrl+C to stop")
|
|
315
|
+
try:
|
|
316
|
+
from .bot import main
|
|
317
|
+
asyncio.run(main())
|
|
318
|
+
except KeyboardInterrupt:
|
|
319
|
+
print("\nš YouClaw stopped")
|
|
320
|
+
return 0
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"ā Launch Fault: {e}")
|
|
323
|
+
return 1
|
|
324
|
+
|
|
325
|
+
def cmd_stop(self, args):
|
|
326
|
+
"""Stop YouClaw service"""
|
|
327
|
+
pid_file = Path("./data/youclaw.pid")
|
|
328
|
+
|
|
329
|
+
if not pid_file.exists():
|
|
330
|
+
print("ā ļø YouClaw is not running (no PID file found)")
|
|
331
|
+
return 1
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
pid = int(pid_file.read_text().strip())
|
|
335
|
+
print(f"š¦ Stopping YouClaw (PID: {pid})...")
|
|
336
|
+
|
|
337
|
+
# Send SIGTERM for graceful shutdown
|
|
338
|
+
os.kill(pid, signal.SIGTERM)
|
|
339
|
+
|
|
340
|
+
# Wait up to 10 seconds for process to stop
|
|
341
|
+
import time
|
|
342
|
+
for _ in range(10):
|
|
343
|
+
try:
|
|
344
|
+
os.kill(pid, 0) # Check if still running
|
|
345
|
+
time.sleep(1)
|
|
346
|
+
except ProcessLookupError:
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
# Force kill if still running
|
|
350
|
+
try:
|
|
351
|
+
os.kill(pid, 0)
|
|
352
|
+
print("ā ļø Process didn't stop gracefully, forcing...")
|
|
353
|
+
os.kill(pid, signal.SIGKILL)
|
|
354
|
+
except ProcessLookupError:
|
|
355
|
+
pass
|
|
356
|
+
|
|
357
|
+
pid_file.unlink()
|
|
358
|
+
print("ā
YouClaw stopped")
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
except (ValueError, ProcessLookupError) as e:
|
|
362
|
+
print(f"ā ļø Process not found, cleaning up PID file...")
|
|
363
|
+
pid_file.unlink()
|
|
364
|
+
return 1
|
|
365
|
+
except Exception as e:
|
|
366
|
+
print(f"ā Error stopping YouClaw: {e}")
|
|
367
|
+
return 1
|
|
368
|
+
|
|
369
|
+
def cmd_restart(self, args):
|
|
370
|
+
"""Restart YouClaw service"""
|
|
371
|
+
print("š¦ Restarting YouClaw...")
|
|
372
|
+
self.cmd_stop(args)
|
|
373
|
+
import time
|
|
374
|
+
time.sleep(1) # Brief pause
|
|
375
|
+
return self.cmd_start(args)
|
|
376
|
+
|
|
377
|
+
def cmd_dashboard(self, args):
|
|
378
|
+
"""Start web dashboard"""
|
|
379
|
+
print("š¦ Starting YouClaw Dashboard...")
|
|
380
|
+
print(" Dashboard will be available at http://localhost:8080")
|
|
381
|
+
print(" Press Ctrl+C to stop\n")
|
|
382
|
+
|
|
383
|
+
# Import and run dashboard
|
|
384
|
+
try:
|
|
385
|
+
from dashboard import run_dashboard
|
|
386
|
+
asyncio.run(run_dashboard(port=args.port))
|
|
387
|
+
except ImportError as e:
|
|
388
|
+
print(f"ā Dashboard module not found. Make sure dashboard.py exists. Error: {e}")
|
|
389
|
+
return 1
|
|
390
|
+
except KeyboardInterrupt:
|
|
391
|
+
print("\nš Dashboard stopped")
|
|
392
|
+
return 0
|
|
393
|
+
|
|
394
|
+
def run(self):
|
|
395
|
+
"""Main CLI entry point"""
|
|
396
|
+
parser = argparse.ArgumentParser(
|
|
397
|
+
description="YouClaw - Your Personal AI Assistant",
|
|
398
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
399
|
+
epilog="""
|
|
400
|
+
Examples:
|
|
401
|
+
youclaw install # Run installation
|
|
402
|
+
youclaw check # Health check
|
|
403
|
+
youclaw start # Start service
|
|
404
|
+
youclaw logs -f # Follow logs
|
|
405
|
+
youclaw dashboard # Start web dashboard
|
|
406
|
+
"""
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
410
|
+
|
|
411
|
+
# Install command
|
|
412
|
+
subparsers.add_parser("install", help="Run installation script")
|
|
413
|
+
|
|
414
|
+
# Check command
|
|
415
|
+
subparsers.add_parser("check", help="Run health checks")
|
|
416
|
+
|
|
417
|
+
# Status command
|
|
418
|
+
subparsers.add_parser("status", help="Show service status")
|
|
419
|
+
|
|
420
|
+
# Logs command
|
|
421
|
+
logs_parser = subparsers.add_parser("logs", help="View logs")
|
|
422
|
+
logs_parser.add_argument("-f", "--follow", action="store_true", help="Follow log output")
|
|
423
|
+
logs_parser.add_argument("-n", "--lines", type=int, default=50, help="Number of lines to show")
|
|
424
|
+
|
|
425
|
+
# Start command
|
|
426
|
+
subparsers.add_parser("start", help="Start YouClaw service")
|
|
427
|
+
|
|
428
|
+
# Stop command
|
|
429
|
+
subparsers.add_parser("stop", help="Stop YouClaw service")
|
|
430
|
+
|
|
431
|
+
# Restart command
|
|
432
|
+
subparsers.add_parser("restart", help="Restart YouClaw service")
|
|
433
|
+
|
|
434
|
+
# Dashboard command
|
|
435
|
+
dashboard_parser = subparsers.add_parser("dashboard", help="Start web dashboard")
|
|
436
|
+
dashboard_parser.add_argument("-p", "--port", type=int, default=8080, help="Dashboard port")
|
|
437
|
+
|
|
438
|
+
args = parser.parse_args()
|
|
439
|
+
|
|
440
|
+
if not args.command:
|
|
441
|
+
parser.print_help()
|
|
442
|
+
return 0
|
|
443
|
+
|
|
444
|
+
# Execute command
|
|
445
|
+
command_map = {
|
|
446
|
+
"install": self.cmd_install,
|
|
447
|
+
"check": self.cmd_check,
|
|
448
|
+
"status": self.cmd_status,
|
|
449
|
+
"logs": self.cmd_logs,
|
|
450
|
+
"start": self.cmd_start,
|
|
451
|
+
"stop": self.cmd_stop,
|
|
452
|
+
"restart": self.cmd_restart,
|
|
453
|
+
"dashboard": self.cmd_dashboard,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if args.command in command_map:
|
|
457
|
+
return command_map[args.command](args)
|
|
458
|
+
else:
|
|
459
|
+
print(f"Unknown command: {args.command}")
|
|
460
|
+
return 1
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def main():
|
|
464
|
+
cli = YouClawCLI()
|
|
465
|
+
sys.exit(cli.run())
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
if __name__ == "__main__":
|
|
469
|
+
main()
|