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 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()