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
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YouClaw Discord Handler
|
|
3
|
+
Handles Discord-specific message processing and bot interactions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import discord
|
|
7
|
+
from discord.ext import commands as discord_commands
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from .config import config
|
|
11
|
+
from .ollama_client import ollama_client
|
|
12
|
+
from .memory_manager import memory_manager
|
|
13
|
+
from .search_client import search_client
|
|
14
|
+
from .commands import command_handler
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DiscordHandler:
|
|
21
|
+
"""Handles Discord platform integration"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
# Set up Discord intents
|
|
25
|
+
intents = discord.Intents.default()
|
|
26
|
+
intents.message_content = True # Required to read message content
|
|
27
|
+
intents.messages = True
|
|
28
|
+
intents.guilds = True
|
|
29
|
+
|
|
30
|
+
self.bot = discord_commands.Bot(command_prefix=config.bot.prefix, intents=intents)
|
|
31
|
+
self.setup_events()
|
|
32
|
+
|
|
33
|
+
def setup_events(self):
|
|
34
|
+
"""Set up Discord event handlers"""
|
|
35
|
+
|
|
36
|
+
@self.bot.event
|
|
37
|
+
async def on_ready():
|
|
38
|
+
logger.info(f"Discord bot logged in as {self.bot.user}")
|
|
39
|
+
await self.bot.change_presence(
|
|
40
|
+
activity=discord.Activity(
|
|
41
|
+
type=discord.ActivityType.listening,
|
|
42
|
+
name="your messages | !help"
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@self.bot.event
|
|
47
|
+
async def on_message(message: discord.Message):
|
|
48
|
+
# Ignore messages from the bot itself
|
|
49
|
+
if message.author == self.bot.user:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Ignore messages from other bots
|
|
53
|
+
if message.author.bot:
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
# Only respond to DMs or mentions
|
|
57
|
+
is_dm = isinstance(message.channel, discord.DMChannel)
|
|
58
|
+
is_mentioned = self.bot.user in message.mentions
|
|
59
|
+
|
|
60
|
+
if not (is_dm or is_mentioned):
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
# Get user info
|
|
64
|
+
user_id = str(message.author.id)
|
|
65
|
+
channel_id = str(message.channel.id) if not is_dm else None
|
|
66
|
+
content = message.content
|
|
67
|
+
|
|
68
|
+
# Remove mention from content
|
|
69
|
+
if is_mentioned:
|
|
70
|
+
content = content.replace(f"<@{self.bot.user.id}>", "").strip()
|
|
71
|
+
|
|
72
|
+
# Show typing indicator
|
|
73
|
+
async with message.channel.typing():
|
|
74
|
+
# Check if it's a command
|
|
75
|
+
command_response = await command_handler.handle_command(
|
|
76
|
+
platform="discord",
|
|
77
|
+
user_id=user_id,
|
|
78
|
+
message=content,
|
|
79
|
+
channel_id=channel_id
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if command_response:
|
|
83
|
+
await self.send_message(message.channel, command_response)
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
# Get conversation history
|
|
87
|
+
history = await memory_manager.get_conversation_history(
|
|
88
|
+
platform="discord",
|
|
89
|
+
user_id=user_id,
|
|
90
|
+
channel_id=channel_id
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Add current message to history
|
|
94
|
+
await memory_manager.add_message(
|
|
95
|
+
platform="discord",
|
|
96
|
+
user_id=user_id,
|
|
97
|
+
role="user",
|
|
98
|
+
content=content,
|
|
99
|
+
channel_id=channel_id,
|
|
100
|
+
metadata={"username": str(message.author)}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Build messages for LLM
|
|
104
|
+
messages = history + [{"role": "user", "content": content}]
|
|
105
|
+
|
|
106
|
+
# Get user profile and onboarding status
|
|
107
|
+
profile = await memory_manager.get_user_profile(platform="discord", user_id=user_id)
|
|
108
|
+
|
|
109
|
+
# Check global settings
|
|
110
|
+
search_enabled = (await memory_manager.get_global_setting("search_enabled", "true")).lower() == "true"
|
|
111
|
+
|
|
112
|
+
# Decide if we need to search
|
|
113
|
+
search_context = None
|
|
114
|
+
if search_enabled and any(word in content.lower() for word in ['search', 'find', 'who is', 'what is', 'latest', 'news']):
|
|
115
|
+
search_results = await search_client.search(content)
|
|
116
|
+
search_context = search_results
|
|
117
|
+
|
|
118
|
+
# Get AI response with autonomous tool use
|
|
119
|
+
logger.info(f"Starting Discord reasoning loop for user {user_id}...")
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# Use chat_with_tools for autonomous behavior
|
|
123
|
+
response = await ollama_client.chat_with_tools(
|
|
124
|
+
messages=messages,
|
|
125
|
+
user_profile=profile,
|
|
126
|
+
context={"user_id": user_id, "platform": "discord"}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if response.strip():
|
|
130
|
+
await self.send_message(message.channel, response)
|
|
131
|
+
else:
|
|
132
|
+
await message.channel.send("Hmm, I'm a bit speechless.")
|
|
133
|
+
|
|
134
|
+
logger.info(f"Reasoning complete for user {user_id}")
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Error during Discord reasoning: {e}")
|
|
138
|
+
error_msg = "Oops, I lost my train of thought. Can we try again?"
|
|
139
|
+
await message.channel.send(error_msg)
|
|
140
|
+
response = error_msg
|
|
141
|
+
|
|
142
|
+
# Simple heuristic to 'complete' onboarding
|
|
143
|
+
if not profile['onboarding_completed']:
|
|
144
|
+
if len(history) > 2:
|
|
145
|
+
await memory_manager.update_user_profile(
|
|
146
|
+
platform="discord",
|
|
147
|
+
user_id=user_id,
|
|
148
|
+
onboarding_completed=True
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Save AI response to memory
|
|
152
|
+
await memory_manager.add_message(
|
|
153
|
+
platform="discord",
|
|
154
|
+
user_id=user_id,
|
|
155
|
+
role="assistant",
|
|
156
|
+
content=response,
|
|
157
|
+
channel_id=channel_id
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def send_message(self, channel, content: str):
|
|
161
|
+
"""Send a message, handling Discord's 2000 char limit"""
|
|
162
|
+
if len(content) <= 2000:
|
|
163
|
+
await channel.send(content)
|
|
164
|
+
else:
|
|
165
|
+
# Split into chunks
|
|
166
|
+
chunks = [content[i:i+2000] for i in range(0, len(content), 2000)]
|
|
167
|
+
for chunk in chunks:
|
|
168
|
+
await channel.send(chunk)
|
|
169
|
+
|
|
170
|
+
async def start(self):
|
|
171
|
+
"""Start the Discord bot"""
|
|
172
|
+
if not config.discord.enabled:
|
|
173
|
+
logger.info("Discord is disabled in config")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
logger.info("Starting Discord bot...")
|
|
177
|
+
await self.bot.start(config.discord.token)
|
|
178
|
+
|
|
179
|
+
async def stop(self):
|
|
180
|
+
"""Stop the Discord bot"""
|
|
181
|
+
if self.bot:
|
|
182
|
+
await self.bot.close()
|
|
183
|
+
logger.info("Discord bot stopped")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Global Discord handler instance
|
|
187
|
+
discord_handler = DiscordHandler()
|
youclaw/env_manager.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YouClaw Environment Manager
|
|
3
|
+
Safely reads and writes to the .env file to allow live configuration updates.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class EnvManager:
|
|
13
|
+
"""Manages reading and writing to the .env file"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, env_path: str = ".env"):
|
|
16
|
+
self.env_path = Path(env_path)
|
|
17
|
+
|
|
18
|
+
def get_all(self) -> dict:
|
|
19
|
+
"""Read all environment variables from the file"""
|
|
20
|
+
if not self.env_path.exists():
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
env_vars = {}
|
|
24
|
+
with open(self.env_path, 'r') as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
line = line.strip()
|
|
27
|
+
if not line or line.startswith('#') or '=' not in line:
|
|
28
|
+
continue
|
|
29
|
+
key, val = line.split('=', 1)
|
|
30
|
+
env_vars[key.strip()] = val.strip()
|
|
31
|
+
return env_vars
|
|
32
|
+
|
|
33
|
+
def set_key(self, key: str, value: str):
|
|
34
|
+
"""Update or add a key-value pair in the .env file"""
|
|
35
|
+
lines = []
|
|
36
|
+
found = False
|
|
37
|
+
|
|
38
|
+
if self.env_path.exists():
|
|
39
|
+
with open(self.env_path, 'r') as f:
|
|
40
|
+
lines = f.readlines()
|
|
41
|
+
|
|
42
|
+
for i, line in enumerate(lines):
|
|
43
|
+
line_strip = line.strip()
|
|
44
|
+
if line_strip.startswith(f"{key}="):
|
|
45
|
+
lines[i] = f"{key}={value}\n"
|
|
46
|
+
found = True
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
if not found:
|
|
50
|
+
# Add with a newline if file is not empty and doesn't end with one
|
|
51
|
+
if lines and not lines[-1].endswith('\n'):
|
|
52
|
+
lines[-1] += '\n'
|
|
53
|
+
lines.append(f"{key}={value}\n")
|
|
54
|
+
|
|
55
|
+
with open(self.env_path, 'w') as f:
|
|
56
|
+
f.writelines(lines)
|
|
57
|
+
|
|
58
|
+
logger.info(f"Updated .env: {key}=***")
|
|
59
|
+
|
|
60
|
+
# Global instance
|
|
61
|
+
env_manager = EnvManager()
|
youclaw/main.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
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 asyncio
|
|
10
|
+
import argparse
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Add parent directory to path for imports
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class YouClawCLI:
|
|
18
|
+
"""YouClaw command line interface"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.project_dir = Path(__file__).parent
|
|
22
|
+
|
|
23
|
+
def cmd_install(self, args):
|
|
24
|
+
"""Run installation script"""
|
|
25
|
+
print("š¦ Running YouClaw installation...")
|
|
26
|
+
install_script = self.project_dir / "install.sh"
|
|
27
|
+
|
|
28
|
+
if not install_script.exists():
|
|
29
|
+
print("ā install.sh not found!")
|
|
30
|
+
return 1
|
|
31
|
+
|
|
32
|
+
result = subprocess.run(["bash", str(install_script)])
|
|
33
|
+
return result.returncode
|
|
34
|
+
|
|
35
|
+
def cmd_check(self, args):
|
|
36
|
+
"""Run health checks"""
|
|
37
|
+
print("š¦ YouClaw Health Check")
|
|
38
|
+
print("=" * 50)
|
|
39
|
+
|
|
40
|
+
# Check Python version
|
|
41
|
+
print("\nš¦ Python Version:")
|
|
42
|
+
python_version = sys.version.split()[0]
|
|
43
|
+
print(f" {python_version}", end="")
|
|
44
|
+
if sys.version_info >= (3, 10):
|
|
45
|
+
print(" ā
")
|
|
46
|
+
else:
|
|
47
|
+
print(" ā (Need 3.10+)")
|
|
48
|
+
|
|
49
|
+
# Check Ollama
|
|
50
|
+
print("\nš¤ Ollama:")
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["curl", "-s", "http://localhost:11434/api/tags"],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
timeout=5
|
|
56
|
+
)
|
|
57
|
+
if result.returncode == 0:
|
|
58
|
+
print(" Connected ā
")
|
|
59
|
+
# Parse models
|
|
60
|
+
import json
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(result.stdout)
|
|
63
|
+
models = [m["name"] for m in data.get("models", [])]
|
|
64
|
+
print(f" Models: {', '.join(models) if models else 'None'}")
|
|
65
|
+
except:
|
|
66
|
+
pass
|
|
67
|
+
else:
|
|
68
|
+
print(" Not running ā")
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f" Error: {e} ā")
|
|
71
|
+
|
|
72
|
+
# Check virtual environment
|
|
73
|
+
print("\nš Virtual Environment:")
|
|
74
|
+
venv_path = self.project_dir / "venv"
|
|
75
|
+
if venv_path.exists():
|
|
76
|
+
print(f" {venv_path} ā
")
|
|
77
|
+
else:
|
|
78
|
+
print(" Not found ā")
|
|
79
|
+
|
|
80
|
+
# Check .env file
|
|
81
|
+
print("\nāļø Configuration:")
|
|
82
|
+
env_path = self.project_dir / ".env"
|
|
83
|
+
if env_path.exists():
|
|
84
|
+
print(" .env file exists ā
")
|
|
85
|
+
# Check for tokens
|
|
86
|
+
with open(env_path) as f:
|
|
87
|
+
content = f.read()
|
|
88
|
+
has_discord = "DISCORD_BOT_TOKEN=" in content and "your_discord" not in content
|
|
89
|
+
has_telegram = "TELEGRAM_BOT_TOKEN=" in content and "your_telegram" not in content
|
|
90
|
+
|
|
91
|
+
if has_discord:
|
|
92
|
+
print(" Discord token configured ā
")
|
|
93
|
+
else:
|
|
94
|
+
print(" Discord token not set ā ļø")
|
|
95
|
+
|
|
96
|
+
if has_telegram:
|
|
97
|
+
print(" Telegram token configured ā
")
|
|
98
|
+
else:
|
|
99
|
+
print(" Telegram token not set ā ļø")
|
|
100
|
+
else:
|
|
101
|
+
print(" .env file missing ā")
|
|
102
|
+
|
|
103
|
+
# Check database
|
|
104
|
+
print("\nš¾ Database:")
|
|
105
|
+
db_path = self.project_dir / "data" / "bot.db"
|
|
106
|
+
if db_path.exists():
|
|
107
|
+
size = db_path.stat().st_size
|
|
108
|
+
print(f" {db_path} ({size} bytes) ā
")
|
|
109
|
+
else:
|
|
110
|
+
print(" Not created yet (will be created on first run)")
|
|
111
|
+
|
|
112
|
+
# Check systemd service
|
|
113
|
+
print("\nš§ Systemd Service:")
|
|
114
|
+
try:
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
["systemctl", "--user", "is-active", "youclaw"],
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True
|
|
119
|
+
)
|
|
120
|
+
status = result.stdout.strip()
|
|
121
|
+
if status == "active":
|
|
122
|
+
print(" Running ā
")
|
|
123
|
+
elif status == "inactive":
|
|
124
|
+
print(" Stopped ā ļø")
|
|
125
|
+
else:
|
|
126
|
+
print(f" Status: {status}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(f" Not installed ā ļø")
|
|
129
|
+
|
|
130
|
+
print("\n" + "=" * 50)
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
def cmd_status(self, args):
|
|
134
|
+
"""Show service status"""
|
|
135
|
+
print("š¦ YouClaw Status\n")
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
["systemctl", "--user", "status", "youclaw"],
|
|
138
|
+
capture_output=False
|
|
139
|
+
)
|
|
140
|
+
return result.returncode
|
|
141
|
+
|
|
142
|
+
def cmd_logs(self, args):
|
|
143
|
+
"""View logs"""
|
|
144
|
+
print("š¦ YouClaw Logs (Ctrl+C to exit)\n")
|
|
145
|
+
|
|
146
|
+
cmd = ["journalctl", "--user", "-u", "youclaw"]
|
|
147
|
+
|
|
148
|
+
if args.follow:
|
|
149
|
+
cmd.append("-f")
|
|
150
|
+
|
|
151
|
+
if args.lines:
|
|
152
|
+
cmd.extend(["-n", str(args.lines)])
|
|
153
|
+
|
|
154
|
+
result = subprocess.run(cmd)
|
|
155
|
+
return result.returncode
|
|
156
|
+
|
|
157
|
+
def cmd_start(self, args):
|
|
158
|
+
"""Start YouClaw service"""
|
|
159
|
+
print("š¦ Starting YouClaw...")
|
|
160
|
+
result = subprocess.run(["systemctl", "--user", "start", "youclaw"])
|
|
161
|
+
if result.returncode == 0:
|
|
162
|
+
print("ā
YouClaw started")
|
|
163
|
+
return result.returncode
|
|
164
|
+
|
|
165
|
+
def cmd_stop(self, args):
|
|
166
|
+
"""Stop YouClaw service"""
|
|
167
|
+
print("š¦ Stopping YouClaw...")
|
|
168
|
+
result = subprocess.run(["systemctl", "--user", "stop", "youclaw"])
|
|
169
|
+
if result.returncode == 0:
|
|
170
|
+
print("ā
YouClaw stopped")
|
|
171
|
+
return result.returncode
|
|
172
|
+
|
|
173
|
+
def cmd_restart(self, args):
|
|
174
|
+
"""Restart YouClaw service"""
|
|
175
|
+
print("š¦ Restarting YouClaw...")
|
|
176
|
+
result = subprocess.run(["systemctl", "--user", "restart", "youclaw"])
|
|
177
|
+
if result.returncode == 0:
|
|
178
|
+
print("ā
YouClaw restarted")
|
|
179
|
+
return result.returncode
|
|
180
|
+
|
|
181
|
+
def cmd_dashboard(self, args):
|
|
182
|
+
"""Start web dashboard"""
|
|
183
|
+
print("š¦ Starting YouClaw Dashboard...")
|
|
184
|
+
print(" Dashboard will be available at http://localhost:8080")
|
|
185
|
+
print(" Press Ctrl+C to stop\n")
|
|
186
|
+
|
|
187
|
+
# Import and run dashboard
|
|
188
|
+
try:
|
|
189
|
+
from dashboard import run_dashboard
|
|
190
|
+
asyncio.run(run_dashboard(port=args.port))
|
|
191
|
+
except ImportError as e:
|
|
192
|
+
print(f"ā Dashboard module not found. Make sure dashboard.py exists. Error: {e}")
|
|
193
|
+
return 1
|
|
194
|
+
except KeyboardInterrupt:
|
|
195
|
+
print("\nš Dashboard stopped")
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
def run(self):
|
|
199
|
+
"""Main CLI entry point"""
|
|
200
|
+
parser = argparse.ArgumentParser(
|
|
201
|
+
description="YouClaw - Your Personal AI Assistant",
|
|
202
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
203
|
+
epilog="""
|
|
204
|
+
Examples:
|
|
205
|
+
youclaw install # Run installation
|
|
206
|
+
youclaw check # Health check
|
|
207
|
+
youclaw start # Start service
|
|
208
|
+
youclaw logs -f # Follow logs
|
|
209
|
+
youclaw dashboard # Start web dashboard
|
|
210
|
+
"""
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
214
|
+
|
|
215
|
+
# Install command
|
|
216
|
+
subparsers.add_parser("install", help="Run installation script")
|
|
217
|
+
|
|
218
|
+
# Check command
|
|
219
|
+
subparsers.add_parser("check", help="Run health checks")
|
|
220
|
+
|
|
221
|
+
# Status command
|
|
222
|
+
subparsers.add_parser("status", help="Show service status")
|
|
223
|
+
|
|
224
|
+
# Logs command
|
|
225
|
+
logs_parser = subparsers.add_parser("logs", help="View logs")
|
|
226
|
+
logs_parser.add_argument("-f", "--follow", action="store_true", help="Follow log output")
|
|
227
|
+
logs_parser.add_argument("-n", "--lines", type=int, default=50, help="Number of lines to show")
|
|
228
|
+
|
|
229
|
+
# Start command
|
|
230
|
+
subparsers.add_parser("start", help="Start YouClaw service")
|
|
231
|
+
|
|
232
|
+
# Stop command
|
|
233
|
+
subparsers.add_parser("stop", help="Stop YouClaw service")
|
|
234
|
+
|
|
235
|
+
# Restart command
|
|
236
|
+
subparsers.add_parser("restart", help="Restart YouClaw service")
|
|
237
|
+
|
|
238
|
+
# Dashboard command
|
|
239
|
+
dashboard_parser = subparsers.add_parser("dashboard", help="Start web dashboard")
|
|
240
|
+
dashboard_parser.add_argument("-p", "--port", type=int, default=8080, help="Dashboard port")
|
|
241
|
+
|
|
242
|
+
args = parser.parse_args()
|
|
243
|
+
|
|
244
|
+
if not args.command:
|
|
245
|
+
parser.print_help()
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
# Execute command
|
|
249
|
+
command_map = {
|
|
250
|
+
"install": self.cmd_install,
|
|
251
|
+
"check": self.cmd_check,
|
|
252
|
+
"status": self.cmd_status,
|
|
253
|
+
"logs": self.cmd_logs,
|
|
254
|
+
"start": self.cmd_start,
|
|
255
|
+
"stop": self.cmd_stop,
|
|
256
|
+
"restart": self.cmd_restart,
|
|
257
|
+
"dashboard": self.cmd_dashboard,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if args.command in command_map:
|
|
261
|
+
return command_map[args.command](args)
|
|
262
|
+
else:
|
|
263
|
+
print(f"Unknown command: {args.command}")
|
|
264
|
+
return 1
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def main():
|
|
268
|
+
cli = YouClawCLI()
|
|
269
|
+
sys.exit(cli.run())
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
main()
|