ambivo-agents 1.3.3__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.
ambivo_agents/cli.py ADDED
@@ -0,0 +1,1871 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Ambivo Agents CLI Interface - Version 1.0 with Agent Caching and Memory Persistence
4
+
5
+ Author: Hemant Gosain 'Sunny'
6
+ Company: Ambivo
7
+ Email: sgosain@ambivo.com
8
+ License: MIT
9
+ """
10
+
11
+ import asyncio
12
+ import click
13
+ import json
14
+ import sys
15
+ import time
16
+ import yaml
17
+ import os
18
+ import uuid
19
+ from pathlib import Path
20
+ from datetime import datetime
21
+ from typing import Optional, Dict, Any, Union, Tuple
22
+
23
+ # Import agents directly using clean imports
24
+ from ambivo_agents import (
25
+ AssistantAgent,
26
+ KnowledgeBaseAgent,
27
+ YouTubeDownloadAgent,
28
+ MediaEditorAgent,
29
+ WebSearchAgent,
30
+ WebScraperAgent,
31
+ CodeExecutorAgent
32
+ )
33
+
34
+ # Import AgentSession with fallback
35
+ try:
36
+ from ambivo_agents import AgentSession
37
+
38
+ AGENT_SESSION_AVAILABLE = True
39
+ except ImportError:
40
+ try:
41
+ from ambivo_agents.core.base import AgentSession
42
+
43
+ AGENT_SESSION_AVAILABLE = True
44
+ except ImportError:
45
+ AGENT_SESSION_AVAILABLE = False
46
+ AgentSession = None
47
+
48
+ # Fallback to service for complex routing if needed
49
+ try:
50
+ from ambivo_agents.services import create_agent_service
51
+
52
+ SERVICE_AVAILABLE = True
53
+ except ImportError:
54
+ SERVICE_AVAILABLE = False
55
+
56
+
57
+ class ConfigManager:
58
+ """Manages YAML configuration for Ambivo Agents"""
59
+
60
+ def __init__(self, config_path: Optional[str] = None):
61
+ self.config_path = config_path
62
+ self.config = self._load_default_config()
63
+ self._load_config()
64
+
65
+ def _load_default_config(self) -> Dict[str, Any]:
66
+ """Load default configuration"""
67
+ return {
68
+ 'cli': {
69
+ 'version': '1.0.0',
70
+ 'default_mode': 'shell',
71
+ 'auto_session': True,
72
+ 'session_prefix': 'ambivo',
73
+ 'verbose': False,
74
+ 'theme': 'default'
75
+ },
76
+ 'agents': {
77
+ 'youtube': {
78
+ 'default_audio_only': True,
79
+ 'output_directory': './downloads',
80
+ 'max_concurrent_downloads': 3
81
+ },
82
+ 'media': {
83
+ 'temp_directory': './temp',
84
+ 'supported_formats': ['mp4', 'avi', 'mov', 'mp3', 'wav'],
85
+ 'ffmpeg_path': 'ffmpeg'
86
+ },
87
+ 'knowledge_base': {
88
+ 'default_embedding_model': 'sentence-transformers/all-MiniLM-L6-v2',
89
+ 'chunk_size': 1000,
90
+ 'chunk_overlap': 200
91
+ },
92
+ 'web_search': {
93
+ 'default_max_results': 5,
94
+ 'timeout': 30,
95
+ 'providers': ['brave', 'duckduckgo']
96
+ },
97
+ 'web_scraper': {
98
+ 'user_agent': 'Ambivo-Agent/1.0',
99
+ 'timeout': 30,
100
+ 'max_retries': 3
101
+ },
102
+ 'code_executor': {
103
+ 'docker_enabled': False,
104
+ 'allowed_languages': ['python', 'javascript', 'bash'],
105
+ 'timeout': 300
106
+ }
107
+ },
108
+ 'session': {
109
+ 'auto_cleanup': True,
110
+ 'session_timeout': 3600,
111
+ 'max_sessions': 10
112
+ },
113
+ 'logging': {
114
+ 'level': 'INFO',
115
+ 'file': './logs/ambivo-agents.log',
116
+ 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
117
+ }
118
+ }
119
+
120
+ def _load_config(self):
121
+ """Load configuration from file, prioritizing agent_config.yaml"""
122
+ if not self.config_path:
123
+ # Try agent_config.yaml first, then other locations
124
+ possible_paths = [
125
+ './agent_config.yaml',
126
+ './agent_config.yml',
127
+ '~/.ambivo/agent_config.yaml',
128
+ '~/.ambivo/agent_config.yml',
129
+ './ambivo-agents.yaml',
130
+ './ambivo-agents.yml',
131
+ '~/.ambivo/config.yaml',
132
+ '~/.ambivo/config.yml',
133
+ '/etc/ambivo/config.yaml'
134
+ ]
135
+
136
+ for path in possible_paths:
137
+ expanded_path = Path(path).expanduser()
138
+ if expanded_path.exists():
139
+ self.config_path = str(expanded_path)
140
+ break
141
+
142
+ if self.config_path and Path(self.config_path).exists():
143
+ try:
144
+ with open(self.config_path, 'r') as f:
145
+ file_config = yaml.safe_load(f)
146
+ if file_config:
147
+ self._merge_config(self.config, file_config)
148
+ click.echo(f"šŸ“‹ Loaded configuration from: {self.config_path}")
149
+ return
150
+ except Exception as e:
151
+ click.echo(f"āš ļø Warning: Failed to load config from {self.config_path}: {e}")
152
+
153
+ # If no config found, prompt to create agent_config.yaml
154
+ if not self.config_path:
155
+ self._prompt_for_config_creation()
156
+
157
+ def _prompt_for_config_creation(self):
158
+ """Prompt user to create agent_config.yaml"""
159
+ click.echo("šŸ“‹ No configuration file found.")
160
+ click.echo("šŸ’” Would you like to create agent_config.yaml with default settings?")
161
+
162
+ if click.confirm("Create agent_config.yaml?"):
163
+ config_path = "./agent_config.yaml"
164
+ if self.save_sample_config(config_path):
165
+ click.echo(f"āœ… Created configuration file: {config_path}")
166
+ click.echo("šŸ’” Edit this file to customize your settings")
167
+ self.config_path = config_path
168
+ else:
169
+ click.echo("āŒ Failed to create configuration file")
170
+ click.echo("šŸ’” Continuing with default settings")
171
+ else:
172
+ click.echo("šŸ’” Continuing with default settings")
173
+
174
+ def _merge_config(self, base: Dict, override: Dict):
175
+ """Recursively merge configuration dictionaries"""
176
+ for key, value in override.items():
177
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
178
+ self._merge_config(base[key], value)
179
+ else:
180
+ base[key] = value
181
+
182
+ def get(self, path: str, default=None):
183
+ """Get configuration value using dot notation (e.g., 'agents.youtube.default_audio_only')"""
184
+ keys = path.split('.')
185
+ current = self.config
186
+
187
+ for key in keys:
188
+ if isinstance(current, dict) and key in current:
189
+ current = current[key]
190
+ else:
191
+ return default
192
+
193
+ return current
194
+
195
+ def save_sample_config(self, path: str):
196
+ """Save a sample configuration file"""
197
+ try:
198
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
199
+ with open(path, 'w') as f:
200
+ yaml.dump(self.config, f, default_flow_style=False, indent=2)
201
+ return True
202
+ except Exception as e:
203
+ click.echo(f"āŒ Failed to save sample config: {e}")
204
+ return False
205
+
206
+
207
+ class AmbivoAgentsCLI:
208
+ """Enhanced CLI with agent caching and session management"""
209
+
210
+ def __init__(self, config_manager: ConfigManager):
211
+ self.config = config_manager
212
+ self.user_id = "cli_user"
213
+ self.tenant_id = "cli_tenant"
214
+ self.session_metadata = {
215
+ "cli_session": True,
216
+ "version": self.config.get('cli.version', '1.0.0'),
217
+ "mode": "shell_default"
218
+ }
219
+ self.session_file = Path.home() / ".ambivo_agents_session"
220
+
221
+ # šŸ†• Agent caching system
222
+ self._session_agents: Dict[str, Tuple[Any, Any]] = {} # Cache agents per session
223
+ self._agent_creation_lock = asyncio.Lock() # Prevent race conditions
224
+
225
+ self._ensure_auto_session()
226
+
227
+ # Check import status
228
+ if not AGENT_SESSION_AVAILABLE:
229
+ if self.config.get('cli.verbose', False):
230
+ print("āš ļø Warning: AgentSession not available - some features may be limited")
231
+ print("šŸ’” Ensure AgentSession is exported in ambivo_agents/__init__.py")
232
+
233
+ def _ensure_auto_session(self):
234
+ """Automatically create a session if none exists and auto_session is enabled"""
235
+ if self.config.get('cli.auto_session', True):
236
+ current_session = self.get_current_session()
237
+ if not current_session:
238
+ # Auto-create a UUID4 session
239
+ session_id = str(uuid.uuid4())
240
+ self.set_current_session(session_id)
241
+ if self.config.get('cli.verbose', False):
242
+ click.echo(f"šŸ”„ Auto-created session: {session_id}")
243
+
244
+ def get_current_session(self) -> Optional[str]:
245
+ """Get the currently active session from file"""
246
+ try:
247
+ if self.session_file.exists():
248
+ return self.session_file.read_text().strip()
249
+ except Exception:
250
+ pass
251
+ return None
252
+
253
+ def set_current_session(self, session_id: str):
254
+ """Set the current session and save to file"""
255
+ try:
256
+ self.session_file.write_text(session_id)
257
+ return True
258
+ except Exception as e:
259
+ click.echo(f"āŒ Failed to save session: {e}")
260
+ return False
261
+
262
+ def clear_current_session(self):
263
+ """Clear the current session"""
264
+ try:
265
+ if self.session_file.exists():
266
+ self.session_file.unlink()
267
+ return True
268
+ except Exception as e:
269
+ click.echo(f"āŒ Failed to clear session: {e}")
270
+ return False
271
+
272
+ async def get_or_create_agent(self, agent_class, session_id: str = None,
273
+ additional_metadata: Dict[str, Any] = None):
274
+ """
275
+ šŸ†• Get existing agent from cache or create new one
276
+ This ensures agent reuse within a session
277
+ """
278
+ if session_id is None:
279
+ session_id = self.get_current_session()
280
+
281
+ if not session_id:
282
+ raise ValueError("No session ID available for agent creation")
283
+
284
+ # Create cache key: agent_class + session_id
285
+ cache_key = f"{agent_class.__name__}_{session_id}"
286
+
287
+ # Use lock to prevent race conditions during agent creation
288
+ async with self._agent_creation_lock:
289
+ # Check if agent already exists in cache
290
+ if cache_key in self._session_agents:
291
+ agent, context = self._session_agents[cache_key]
292
+
293
+ if self.config.get('cli.verbose', False):
294
+ click.echo(f"šŸ”„ Reusing cached {agent_class.__name__} (ID: {agent.agent_id})")
295
+
296
+ return agent, context
297
+
298
+ # Create new agent if not in cache
299
+ if self.config.get('cli.verbose', False):
300
+ click.echo(f"šŸ†• Creating new {agent_class.__name__} for session {session_id[:8]}...")
301
+
302
+ metadata = {**self.session_metadata}
303
+ if additional_metadata:
304
+ metadata.update(additional_metadata)
305
+
306
+ metadata['config'] = {
307
+ 'agent_type': agent_class.__name__,
308
+ 'configured': True,
309
+ 'cached': True,
310
+ 'session_id': session_id
311
+ }
312
+
313
+ # šŸ”§ FIXED: Use consistent agent_id based on session + agent type
314
+ consistent_agent_id = f"{agent_class.__name__.lower()}_{session_id}"
315
+
316
+ agent, context = agent_class.create(
317
+ agent_id=consistent_agent_id, # šŸŽÆ CONSISTENT ID
318
+ user_id=self.user_id,
319
+ tenant_id=self.tenant_id,
320
+ session_metadata=metadata,
321
+ session_id=session_id,
322
+ conversation_id=session_id
323
+ )
324
+
325
+ # Cache the agent
326
+ self._session_agents[cache_key] = (agent, context)
327
+
328
+ if self.config.get('cli.verbose', False):
329
+ click.echo(f"āœ… Cached {agent_class.__name__} (ID: {agent.agent_id})")
330
+ click.echo(f"šŸ“Š Total cached agents: {len(self._session_agents)}")
331
+
332
+ return agent, context
333
+
334
+ def clear_session_agents(self, session_id: str = None):
335
+ """
336
+ šŸ†• Clear cached agents for a specific session
337
+ Call this when session ends
338
+ """
339
+ if session_id is None:
340
+ session_id = self.get_current_session()
341
+
342
+ if not session_id:
343
+ return
344
+
345
+ keys_to_remove = [key for key in self._session_agents.keys() if key.endswith(session_id)]
346
+
347
+ for key in keys_to_remove:
348
+ agent, context = self._session_agents[key]
349
+
350
+ if self.config.get('cli.verbose', False):
351
+ click.echo(f"šŸ—‘ļø Removing cached agent: {agent.agent_id}")
352
+
353
+ # Optionally cleanup the agent
354
+ try:
355
+ # Note: We can call cleanup_session here since we're ending the session
356
+ asyncio.create_task(agent.cleanup_session())
357
+ except Exception as e:
358
+ click.echo(f"āš ļø Warning during agent cleanup: {e}")
359
+
360
+ del self._session_agents[key]
361
+
362
+ if keys_to_remove and self.config.get('cli.verbose', False):
363
+ click.echo(f"🧹 Cleared {len(keys_to_remove)} agents for session {session_id[:8]}...")
364
+
365
+ def clear_all_agents(self):
366
+ """
367
+ šŸ†• Clear all cached agents (emergency cleanup)
368
+ """
369
+ if self.config.get('cli.verbose', False):
370
+ click.echo(f"🧹 Clearing all {len(self._session_agents)} cached agents...")
371
+
372
+ for key, (agent, context) in self._session_agents.items():
373
+ try:
374
+ asyncio.create_task(agent.cleanup_session())
375
+ except Exception as e:
376
+ click.echo(f"āš ļø Warning during agent cleanup: {e}")
377
+
378
+ self._session_agents.clear()
379
+
380
+ def get_cached_agents_info(self) -> Dict[str, Any]:
381
+ """
382
+ šŸ†• Get information about cached agents
383
+ """
384
+ info = {
385
+ 'total_agents': len(self._session_agents),
386
+ 'agents': []
387
+ }
388
+
389
+ for key, (agent, context) in self._session_agents.items():
390
+ agent_info = {
391
+ 'cache_key': key,
392
+ 'agent_id': agent.agent_id,
393
+ 'agent_type': agent.__class__.__name__,
394
+ 'session_id': context.session_id,
395
+ 'created_at': context.created_at.isoformat(),
396
+ 'memory_available': hasattr(agent, 'memory') and agent.memory is not None
397
+ }
398
+ info['agents'].append(agent_info)
399
+
400
+ return info
401
+
402
+ # šŸ”§ UPDATED: Replace the old create_agent method
403
+ async def create_agent(self, agent_class, additional_metadata: Dict[str, Any] = None):
404
+ """
405
+ šŸ”§ UPDATED: Now uses caching system
406
+ Kept for backward compatibility but delegates to get_or_create_agent
407
+ """
408
+ return await self.get_or_create_agent(agent_class, None, additional_metadata)
409
+
410
+ async def smart_message_routing(self, message: str) -> str:
411
+ """
412
+ šŸ”§ UPDATED: Smart routing with agent caching
413
+ Now reuses agents instead of creating new ones each time
414
+ """
415
+ message_lower = message.lower()
416
+ current_session = self.get_current_session()
417
+
418
+ if not current_session:
419
+ raise ValueError("No active session for message processing")
420
+
421
+ # šŸŽ¬ YouTube Download Detection
422
+ if any(keyword in message_lower for keyword in ['youtube', 'download', 'youtu.be']) and (
423
+ 'http' in message or 'www.' in message):
424
+ agent, context = await self.get_or_create_agent(YouTubeDownloadAgent, current_session,
425
+ {"operation": "youtube_download"})
426
+
427
+ try:
428
+ # Extract YouTube URLs
429
+ import re
430
+ youtube_patterns = [
431
+ r'https?://(?:www\.)?youtube\.com/watch\?v=[\w-]+',
432
+ r'https?://(?:www\.)?youtu\.be/[\w-]+',
433
+ ]
434
+
435
+ urls = []
436
+ for pattern in youtube_patterns:
437
+ urls.extend(re.findall(pattern, message))
438
+
439
+ if urls:
440
+ url = urls[0] # Use first URL found
441
+
442
+ # Use configuration for default behavior
443
+ default_audio_only = self.config.get('agents.youtube.default_audio_only', True)
444
+ wants_video = any(keyword in message_lower for keyword in ['video', 'mp4', 'watch', 'visual'])
445
+ audio_only = default_audio_only if not wants_video else False
446
+
447
+ if 'info' in message_lower or 'information' in message_lower:
448
+ result = await agent._get_youtube_info(url)
449
+ else:
450
+ result = await agent._download_youtube(url, audio_only=audio_only)
451
+
452
+ # šŸ”§ REMOVED: await agent.cleanup_session() - Agent stays cached
453
+
454
+ if result['success']:
455
+ return f"āœ… YouTube operation completed!\n{result.get('message', '')}\nSession: {context.session_id}\nAgent: {agent.agent_id}"
456
+ else:
457
+ return f"āŒ YouTube operation failed: {result['error']}"
458
+ else:
459
+ return "āŒ No valid YouTube URLs found in message"
460
+
461
+ except Exception as e:
462
+ return f"āŒ YouTube operation error: {e}"
463
+
464
+ # šŸŽµ Media Processing Detection
465
+ elif any(keyword in message_lower for keyword in
466
+ ['extract audio', 'convert video', 'media', 'ffmpeg', '.mp4', '.avi', '.mov']):
467
+ agent, context = await self.get_or_create_agent(MediaEditorAgent, current_session,
468
+ {"operation": "media_processing"})
469
+
470
+ try:
471
+ supported_formats = self.config.get('agents.media.supported_formats', ['mp4', 'avi', 'mov'])
472
+
473
+ if 'extract audio' in message_lower:
474
+ return f"šŸŽµ Media Editor Agent ready for audio extraction!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
475
+ elif 'convert' in message_lower:
476
+ return f"šŸŽ„ Media Editor Agent ready for video conversion!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}"
477
+ else:
478
+ return f"šŸŽ¬ Media Editor Agent ready!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}"
479
+
480
+ except Exception as e:
481
+ return f"āŒ Media processing error: {e}"
482
+
483
+ # šŸ“š Knowledge Base Detection
484
+ elif any(keyword in message_lower for keyword in ['knowledge base', 'ingest', 'query', 'document', 'kb ']):
485
+ agent, context = await self.get_or_create_agent(KnowledgeBaseAgent, current_session,
486
+ {"operation": "knowledge_base"})
487
+
488
+ try:
489
+ chunk_size = self.config.get('agents.knowledge_base.chunk_size', 1000)
490
+
491
+ if 'ingest' in message_lower:
492
+ return f"šŸ“„ Knowledge Base Agent ready for document ingestion!\nChunk size: {chunk_size}\nAgent: {agent.agent_id}"
493
+ elif 'query' in message_lower:
494
+ return f"šŸ” Knowledge Base Agent ready for queries!\nAgent: {agent.agent_id}"
495
+ else:
496
+ return f"šŸ“š Knowledge Base Agent ready!\nAgent: {agent.agent_id}"
497
+
498
+ except Exception as e:
499
+ return f"āŒ Knowledge base error: {e}"
500
+
501
+ # šŸ” Web Search Detection
502
+ elif any(keyword in message_lower for keyword in ['search', 'find', 'look up', 'google']):
503
+ agent, context = await self.get_or_create_agent(WebSearchAgent, current_session,
504
+ {"operation": "web_search"})
505
+
506
+ try:
507
+ max_results = self.config.get('agents.web_search.default_max_results', 5)
508
+
509
+ # Extract search query
510
+ search_query = message
511
+ for prefix in ['search for', 'find', 'look up', 'google']:
512
+ if prefix in message_lower:
513
+ search_query = message[message_lower.find(prefix) + len(prefix):].strip()
514
+ break
515
+
516
+ result = await agent._search_web(search_query, max_results=max_results)
517
+
518
+ if result['success']:
519
+ response = f"šŸ” Search Results for '{search_query}':\n\n"
520
+ for i, res in enumerate(result['results'][:3], 1):
521
+ response += f"{i}. **{res.get('title', 'No title')}**\n"
522
+ response += f" {res.get('url', 'No URL')}\n"
523
+ response += f" {res.get('snippet', 'No snippet')[:150]}...\n\n"
524
+ response += f"Agent: {agent.agent_id}\nSession: {context.session_id}"
525
+ return response
526
+ else:
527
+ return f"āŒ Search failed: {result['error']}"
528
+
529
+ except Exception as e:
530
+ return f"āŒ Search error: {e}"
531
+
532
+ # šŸ•·ļø Web Scraping Detection
533
+ elif any(keyword in message_lower for keyword in ['scrape', 'extract', 'crawl']) and (
534
+ 'http' in message or 'www.' in message):
535
+ agent, context = await self.get_or_create_agent(WebScraperAgent, current_session,
536
+ {"operation": "web_scraping"})
537
+
538
+ try:
539
+ # Extract URLs
540
+ import re
541
+ url_pattern = r'https?://[^\s]+'
542
+ urls = re.findall(url_pattern, message)
543
+
544
+ if urls:
545
+ url = urls[0]
546
+ user_agent = self.config.get('agents.web_scraper.user_agent', 'Ambivo-Agent/1.0')
547
+ return f"šŸ•·ļø Web Scraper Agent ready to scrape: {url}\nUser-Agent: {user_agent}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
548
+ else:
549
+ return "āŒ No valid URLs found for scraping"
550
+
551
+ except Exception as e:
552
+ return f"āŒ Web scraping error: {e}"
553
+
554
+ # šŸ’» Code Execution Detection
555
+ elif '```' in message:
556
+ agent, context = await self.get_or_create_agent(CodeExecutorAgent, current_session,
557
+ {"operation": "code_execution"})
558
+
559
+ try:
560
+ allowed_languages = self.config.get('agents.code_executor.allowed_languages', ['python', 'javascript'])
561
+ return f"šŸ’» Code Executor Agent ready!\nAllowed languages: {', '.join(allowed_languages)}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
562
+
563
+ except Exception as e:
564
+ return f"āŒ Code execution error: {e}"
565
+
566
+ # šŸ¤– General Assistant (fallback) - UPDATED for caching
567
+ else:
568
+ agent, context = await self.get_or_create_agent(AssistantAgent, current_session,
569
+ {"operation": "general_assistance"})
570
+
571
+ try:
572
+ # Create an AgentMessage for the AssistantAgent
573
+ from ambivo_agents.core.base import AgentMessage, MessageType
574
+ import uuid
575
+
576
+ agent_message = AgentMessage(
577
+ id=f"msg_{str(uuid.uuid4())[:8]}",
578
+ sender_id="cli_user",
579
+ recipient_id=agent.agent_id,
580
+ content=message,
581
+ message_type=MessageType.USER_INPUT,
582
+ session_id=context.session_id,
583
+ conversation_id=context.conversation_id
584
+ )
585
+
586
+ response_message = await agent.process_message(agent_message, context.to_execution_context())
587
+
588
+
589
+ return response_message.content
590
+
591
+ except Exception as e:
592
+ return f"āŒ Error processing your question: {e}"
593
+
594
+
595
+ # Initialize configuration and CLI
596
+ config_manager = None
597
+ cli_instance = None
598
+
599
+
600
+ def initialize_cli(config_path: Optional[str] = None, verbose: bool = False):
601
+ """Initialize CLI with configuration"""
602
+ global config_manager, cli_instance
603
+
604
+ config_manager = ConfigManager(config_path)
605
+ if verbose:
606
+ config_manager.config['cli']['verbose'] = True
607
+
608
+ cli_instance = AmbivoAgentsCLI(config_manager)
609
+ return cli_instance
610
+
611
+
612
+ @click.group(invoke_without_command=True)
613
+ @click.version_option(version="1.0.0", prog_name="Ambivo Agents")
614
+ @click.option('--config', '-c', help='Configuration file path')
615
+ @click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
616
+ @click.pass_context
617
+ def cli(ctx, config: Optional[str], verbose: bool):
618
+ """
619
+ Ambivo Agents - Multi-Agent AI System CLI (Shell Mode by Default)
620
+
621
+ 🌟 Features:
622
+ - YouTube Downloads with pytubefix
623
+ - Media Processing with FFmpeg
624
+ - Knowledge Base Operations with Qdrant
625
+ - Web Search with multiple providers
626
+ - Web Scraping with proxy support
627
+ - Code Execution in Docker containers
628
+ - YAML Configuration Support
629
+ - Shell Mode by Default
630
+ - Auto-Session Creation
631
+ - Agent Caching & Reuse
632
+
633
+ Author: Hemant Gosain 'Sunny'
634
+ Company: Ambivo
635
+ Email: sgosain@ambivo.com
636
+ """
637
+ global cli_instance
638
+
639
+ # Initialize CLI
640
+ cli_instance = initialize_cli(config, verbose)
641
+
642
+ if verbose:
643
+ click.echo("šŸ¤– Ambivo Agents CLI v1.0.0 - Shell Mode Default with Agent Caching")
644
+ click.echo("šŸ“§ Contact: sgosain@ambivo.com")
645
+ click.echo("šŸ¢ Company: https://www.ambivo.com")
646
+ click.echo("🌟 Agent caching and session management enabled")
647
+
648
+ # If no command was provided, start shell mode by default
649
+ if ctx.invoked_subcommand is None:
650
+ default_mode = cli_instance.config.get('cli.default_mode', 'shell')
651
+ if default_mode == 'shell':
652
+ ctx.invoke(shell)
653
+ else:
654
+ click.echo(ctx.get_help())
655
+
656
+
657
+ @cli.group()
658
+ def config():
659
+ """Configuration management commands"""
660
+ pass
661
+
662
+
663
+ @config.command()
664
+ def show():
665
+ """Show current configuration"""
666
+ click.echo("šŸ“‹ Current Configuration:")
667
+ click.echo("=" * 50)
668
+
669
+ def print_config(data, indent=0):
670
+ for key, value in data.items():
671
+ if isinstance(value, dict):
672
+ click.echo(" " * indent + f"{key}:")
673
+ print_config(value, indent + 1)
674
+ else:
675
+ click.echo(" " * indent + f"{key}: {value}")
676
+
677
+ print_config(cli_instance.config.config)
678
+
679
+ if cli_instance.config.config_path:
680
+ click.echo(f"\nšŸ“ Loaded from: {cli_instance.config.config_path}")
681
+ else:
682
+ click.echo(f"\nšŸ“ Using default configuration")
683
+
684
+
685
+ @config.command()
686
+ @click.argument('path')
687
+ def save_sample(path: str):
688
+ """Save a sample configuration file"""
689
+ if cli_instance.config.save_sample_config(path):
690
+ click.echo(f"āœ… Sample configuration saved to: {path}")
691
+ click.echo("šŸ’” Edit the file and use --config to load it")
692
+ else:
693
+ click.echo("āŒ Failed to save sample configuration")
694
+
695
+
696
+ @config.command()
697
+ @click.argument('key')
698
+ @click.argument('value')
699
+ def set(key: str, value: str):
700
+ """Set a configuration value (runtime only)"""
701
+ # Try to parse value as appropriate type
702
+ try:
703
+ if value.lower() in ['true', 'false']:
704
+ parsed_value = value.lower() == 'true'
705
+ elif value.isdigit():
706
+ parsed_value = int(value)
707
+ elif '.' in value and value.replace('.', '').isdigit():
708
+ parsed_value = float(value)
709
+ else:
710
+ parsed_value = value
711
+ except:
712
+ parsed_value = value
713
+
714
+ # Set the value in current config
715
+ keys = key.split('.')
716
+ current = cli_instance.config.config
717
+ for k in keys[:-1]:
718
+ if k not in current:
719
+ current[k] = {}
720
+ current = current[k]
721
+
722
+ current[keys[-1]] = parsed_value
723
+ click.echo(f"āœ… Set {key} = {parsed_value}")
724
+ click.echo("šŸ’” This change is runtime-only. Save to file to persist.")
725
+
726
+
727
+ @config.command()
728
+ @click.argument('key')
729
+ def get(key: str):
730
+ """Get a configuration value"""
731
+ value = cli_instance.config.get(key)
732
+ if value is not None:
733
+ click.echo(f"{key}: {value}")
734
+ else:
735
+ click.echo(f"āŒ Configuration key '{key}' not found")
736
+
737
+
738
+ @cli.group()
739
+ def session():
740
+ """Session management commands"""
741
+ pass
742
+
743
+
744
+ @session.command()
745
+ @click.argument('session_name', required=False)
746
+ def create(session_name: Optional[str]):
747
+ """Create and activate a session (auto-generates UUID4 if no name provided)"""
748
+ # šŸ†• Clear any existing cached agents before creating new session
749
+ if hasattr(cli_instance, '_session_agents'):
750
+ cli_instance.clear_all_agents()
751
+
752
+ if session_name:
753
+ session_prefix = cli_instance.config.get('cli.session_prefix', 'ambivo')
754
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
755
+ full_session_id = f"{session_prefix}_{session_name}_{timestamp}"
756
+ else:
757
+ # Generate UUID4 session
758
+ full_session_id = str(uuid.uuid4())
759
+
760
+ if cli_instance.set_current_session(full_session_id):
761
+ click.echo(f"āœ… Created and activated session: {full_session_id}")
762
+ click.echo(f"šŸ’” All commands will now use this session automatically")
763
+ click.echo(f"🧹 Cleared all cached agents")
764
+ click.echo(f"šŸ”§ Use 'ambivo-agents session end' to deactivate")
765
+ else:
766
+ click.echo("āŒ Failed to create session")
767
+ sys.exit(1)
768
+
769
+
770
+ @session.command()
771
+ @click.argument('session_name')
772
+ def use(session_name: str):
773
+ """Switch to an existing session"""
774
+ # Clear cached agents when switching sessions
775
+ if hasattr(cli_instance, '_session_agents'):
776
+ cli_instance.clear_all_agents()
777
+
778
+ if cli_instance.set_current_session(session_name):
779
+ click.echo(f"āœ… Switched to session: {session_name}")
780
+ click.echo(f"šŸ’” All commands will now use this session")
781
+ click.echo(f"🧹 Cleared cached agents from previous session")
782
+ else:
783
+ click.echo("āŒ Failed to switch session")
784
+ sys.exit(1)
785
+
786
+
787
+ @session.command()
788
+ def current():
789
+ """Show the currently active session"""
790
+ current = cli_instance.get_current_session()
791
+ if current:
792
+ click.echo(f"šŸ“‹ Current session: {current}")
793
+
794
+ # Show cached agents info
795
+ if hasattr(cli_instance, '_session_agents'):
796
+ agents_info = cli_instance.get_cached_agents_info()
797
+ click.echo(f"šŸ¤– Cached agents: {agents_info['total_agents']}")
798
+ else:
799
+ click.echo("āŒ No active session")
800
+ click.echo("šŸ’” Create one with: ambivo-agents session create my_session")
801
+
802
+
803
+ @session.command()
804
+ def end():
805
+ """End the current session"""
806
+ current = cli_instance.get_current_session()
807
+ if current:
808
+ # šŸ†• Clear cached agents for this session
809
+ if hasattr(cli_instance, '_session_agents'):
810
+ cli_instance.clear_session_agents(current)
811
+
812
+ if cli_instance.clear_current_session():
813
+ click.echo(f"āœ… Ended session: {current}")
814
+ click.echo(f"🧹 Cleared cached agents for session")
815
+
816
+ # Auto-create a replacement session if auto_session is enabled
817
+ cli_instance._ensure_auto_session()
818
+ new_session = cli_instance.get_current_session()
819
+ if new_session:
820
+ click.echo(f"šŸ”„ Auto-created replacement session: {new_session}")
821
+ else:
822
+ click.echo("āŒ Failed to end session")
823
+ sys.exit(1)
824
+ else:
825
+ click.echo("āŒ No active session to end")
826
+
827
+
828
+ @session.command()
829
+ @click.option('--limit', '-l', default=20, help='Maximum number of messages to show')
830
+ @click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
831
+ def history(limit: int, format: str):
832
+ """Show conversation history for the current session"""
833
+
834
+ # Try to get the current session from shell mode first
835
+ try:
836
+ from pathlib import Path
837
+ session_file = Path.home() / '.ambivo' / 'current_session'
838
+ if session_file.exists():
839
+ current_session = session_file.read_text().strip()
840
+ click.echo(f"šŸ” Debug: Using shell session: {current_session}")
841
+ else:
842
+ current_session = None
843
+ click.echo(f"šŸ” Debug: No shell session file found")
844
+ except Exception as e:
845
+ current_session = None
846
+ click.echo(f"šŸ” Debug: Error reading shell session: {e}")
847
+
848
+ if not current_session:
849
+ click.echo("āŒ No active session")
850
+ click.echo("šŸ’” Create a session with: ambivo-agents session create my_session")
851
+ click.echo("šŸ’” Or start shell mode: ambivo-agents")
852
+ return
853
+
854
+ async def show_history():
855
+ try:
856
+ # šŸ”§ UPDATED: Use cached agent if available
857
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session, {
858
+ "operation": "history_access",
859
+ "use_session": current_session
860
+ })
861
+
862
+ # Override the context to use the shell session
863
+ agent.context.session_id = current_session
864
+ agent.context.conversation_id = current_session
865
+
866
+ # Get conversation history
867
+ history_data = await agent.get_conversation_history(limit=limit, include_metadata=True)
868
+
869
+ if not history_data:
870
+ click.echo(f"šŸ“‹ No conversation history found for session: {current_session}")
871
+ return
872
+
873
+ if format == 'json':
874
+ click.echo(json.dumps(history_data, indent=2, default=str))
875
+ else:
876
+ session_display = current_session[:12] + "..." if len(current_session) > 12 else current_session
877
+ click.echo(f"šŸ“‹ **Conversation History** (Session: {session_display})")
878
+ click.echo(f"šŸ“Š Total Messages: {len(history_data)}")
879
+ click.echo("=" * 60)
880
+
881
+ for i, msg in enumerate(history_data, 1):
882
+ timestamp = msg.get('timestamp', 'Unknown time')
883
+ if isinstance(timestamp, str):
884
+ try:
885
+ from datetime import datetime
886
+ dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
887
+ timestamp = dt.strftime('%Y-%m-%d %H:%M:%S')
888
+ except:
889
+ pass
890
+
891
+ message_type = msg.get('message_type', 'unknown')
892
+ content = msg.get('content', '')
893
+ sender = msg.get('sender_id', 'unknown')
894
+
895
+ # Format based on message type
896
+ if message_type == 'user_input':
897
+ icon = "šŸ—£ļø "
898
+ label = "You"
899
+ elif message_type == 'agent_response':
900
+ icon = "šŸ¤–"
901
+ label = f"Agent ({sender})"
902
+ else:
903
+ icon = "ā„¹ļø "
904
+ label = message_type.title()
905
+
906
+ click.echo(f"\n{i}. {icon} **{label}** - {timestamp}")
907
+ click.echo(f" {content}")
908
+
909
+ if i < len(history_data):
910
+ click.echo(" " + "-" * 50)
911
+
912
+ except Exception as e:
913
+ click.echo(f"āŒ Error retrieving history: {e}")
914
+ import traceback
915
+ click.echo(f"šŸ” Debug traceback: {traceback.format_exc()}")
916
+
917
+ asyncio.run(show_history())
918
+
919
+
920
+ @session.command()
921
+ @click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
922
+ def summary(format: str):
923
+ """Show summary of the current session"""
924
+ current_session = cli_instance.get_current_session()
925
+
926
+ if not current_session:
927
+ click.echo("āŒ No active session")
928
+ click.echo("šŸ’” Create a session with: ambivo-agents session create my_session")
929
+ return
930
+
931
+ async def show_summary():
932
+ try:
933
+ # šŸ”§ UPDATED: Use cached agent if available
934
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
935
+ {"operation": "summary_access"})
936
+
937
+ # Get conversation summary
938
+ summary_data = await agent.get_conversation_summary()
939
+
940
+ if format == 'json':
941
+ click.echo(json.dumps(summary_data, indent=2, default=str))
942
+ else:
943
+ if 'error' in summary_data:
944
+ click.echo(f"āŒ Error getting summary: {summary_data['error']}")
945
+ else:
946
+ click.echo(f"šŸ“Š **Session Summary**")
947
+ click.echo("=" * 40)
948
+ click.echo(f"šŸ”— Session ID: {summary_data.get('session_id', 'Unknown')}")
949
+ click.echo(f"šŸ‘¤ User ID: {summary_data.get('user_id', 'Unknown')}")
950
+ click.echo(f"šŸ’¬ Total Messages: {summary_data.get('total_messages', 0)}")
951
+ click.echo(f"šŸ—£ļø User Messages: {summary_data.get('user_messages', 0)}")
952
+ click.echo(f"šŸ¤– Agent Messages: {summary_data.get('agent_messages', 0)}")
953
+ click.echo(f"ā±ļø Session Duration: {summary_data.get('session_duration', 'Unknown')}")
954
+
955
+ if summary_data.get('first_message'):
956
+ click.echo(f"\nšŸ“ First Message:")
957
+ click.echo(f" {summary_data['first_message']}")
958
+
959
+ if summary_data.get('last_message'):
960
+ click.echo(f"\nšŸ“ Last Message:")
961
+ click.echo(f" {summary_data['last_message']}")
962
+
963
+ except Exception as e:
964
+ click.echo(f"āŒ Error retrieving summary: {e}")
965
+
966
+ asyncio.run(show_summary())
967
+
968
+
969
+ @session.command()
970
+ @click.confirmation_option(prompt='Are you sure you want to clear the conversation history?')
971
+ def clear():
972
+ """Clear conversation history for the current session"""
973
+ current_session = cli_instance.get_current_session()
974
+
975
+ if not current_session:
976
+ click.echo("āŒ No active session")
977
+ return
978
+
979
+ async def clear_history():
980
+ try:
981
+ # šŸ”§ UPDATED: Use cached agent if available
982
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
983
+ {"operation": "history_clear"})
984
+
985
+ # Clear conversation history
986
+ success = await agent.clear_conversation_history()
987
+
988
+ if success:
989
+ click.echo(f"āœ… Cleared conversation history for session: {current_session}")
990
+ else:
991
+ click.echo(f"āŒ Failed to clear conversation history")
992
+
993
+ except Exception as e:
994
+ click.echo(f"āŒ Error clearing history: {e}")
995
+
996
+ asyncio.run(clear_history())
997
+
998
+
999
+ @session.command()
1000
+ def agents():
1001
+ """šŸ†• Show cached agent information"""
1002
+ current_session = cli_instance.get_current_session()
1003
+
1004
+ if not current_session:
1005
+ click.echo("āŒ No active session")
1006
+ return
1007
+
1008
+ agents_info = cli_instance.get_cached_agents_info()
1009
+
1010
+ click.echo(f"šŸ¤– **Cached Agents** (Session: {current_session[:8]}...)")
1011
+ click.echo(f"šŸ“Š Total: {agents_info['total_agents']}")
1012
+ click.echo("=" * 50)
1013
+
1014
+ if agents_info['agents']:
1015
+ for agent_info in agents_info['agents']:
1016
+ click.echo(f"\nšŸ“„ **{agent_info['agent_type']}**")
1017
+ click.echo(f" ID: {agent_info['agent_id']}")
1018
+ click.echo(f" Session: {agent_info['session_id'][:8]}...")
1019
+ click.echo(f" Memory: {'āœ…' if agent_info['memory_available'] else 'āŒ'}")
1020
+ click.echo(f" Created: {agent_info['created_at']}")
1021
+ click.echo(f" Cache Key: {agent_info['cache_key']}")
1022
+ else:
1023
+ click.echo("\nšŸ“­ No cached agents")
1024
+ click.echo("šŸ’” Agents will be created when you send messages")
1025
+
1026
+
1027
+ @cli.command()
1028
+ def shell():
1029
+ """Start Ambivo Agents interactive shell (default mode)"""
1030
+
1031
+ # Show welcome message with configuration info
1032
+ click.echo("šŸš€ Ambivo Agents Shell v1.0.0 (Default Mode)")
1033
+ click.echo("šŸ’” YAML configuration support with auto-sessions and agent caching")
1034
+
1035
+ if cli_instance.config.config_path:
1036
+ click.echo(f"šŸ“‹ Config: {cli_instance.config.config_path}")
1037
+ else:
1038
+ click.echo("šŸ“‹ Config: Using defaults")
1039
+
1040
+ # Show current session
1041
+ current_session = cli_instance.get_current_session()
1042
+ if current_session:
1043
+ session_display = current_session[:8] + "..." if len(current_session) > 8 else current_session
1044
+ click.echo(f"šŸ”— Session: {session_display}")
1045
+
1046
+ # Show cached agents
1047
+ if hasattr(cli_instance, '_session_agents'):
1048
+ agents_info = cli_instance.get_cached_agents_info()
1049
+ if agents_info['total_agents'] > 0:
1050
+ click.echo(f"šŸ¤– Cached agents: {agents_info['total_agents']}")
1051
+
1052
+ click.echo("šŸ’” Type 'help' for commands, 'exit' to quit")
1053
+ click.echo("-" * 60)
1054
+
1055
+ def get_prompt():
1056
+ """Generate dynamic prompt based on session state and theme"""
1057
+ current_session = cli_instance.get_current_session()
1058
+ theme = cli_instance.config.get('cli.theme', 'default')
1059
+
1060
+ if current_session:
1061
+ # Show shortened session ID in prompt
1062
+ session_short = current_session[:8] if len(current_session) > 8 else current_session
1063
+ if theme == 'minimal':
1064
+ return f"({session_short})> "
1065
+ else:
1066
+ return f"ambivo-agents ({session_short})> "
1067
+ else:
1068
+ if theme == 'minimal':
1069
+ return "> "
1070
+ else:
1071
+ return "ambivo-agents> "
1072
+
1073
+ def process_shell_command(command_line: str):
1074
+ """Process a command line in shell mode"""
1075
+ if not command_line.strip():
1076
+ return True
1077
+
1078
+ # Clean up command line - remove leading colons and extra whitespace
1079
+ cleaned_command = command_line.strip()
1080
+ if cleaned_command.startswith(':'):
1081
+ cleaned_command = cleaned_command[1:].strip()
1082
+
1083
+ # Parse command line
1084
+ parts = cleaned_command.split()
1085
+ if not parts:
1086
+ return True
1087
+
1088
+ cmd = parts[0].lower()
1089
+ args = parts[1:] if len(parts) > 1 else []
1090
+
1091
+ # Handle shell-specific commands
1092
+ if cmd in ['exit', 'quit', 'bye']:
1093
+ click.echo("šŸ‘‹ Goodbye!")
1094
+ return False
1095
+
1096
+ elif cmd == 'help':
1097
+ click.echo("""
1098
+ 🌟 Ambivo Agents Shell Commands:
1099
+
1100
+ šŸ“‹ **Configuration:**
1101
+ config show - Show current configuration
1102
+ config get <key> - Get configuration value
1103
+ config set <key> <value> - Set configuration value (runtime)
1104
+ config save-sample <path> - Save sample config file
1105
+
1106
+ šŸ“‹ **Session Management:**
1107
+ session create [name] - Create session (UUID4 if no name)
1108
+ session current - Show current session
1109
+ session status - Full session info
1110
+ session use <name> - Switch to session
1111
+ session end - End current session
1112
+ session history - Show conversation history
1113
+ session summary - Show session summary
1114
+ session clear - Clear conversation history
1115
+ session agents - Show cached agents
1116
+
1117
+ šŸ’¬ **Chat Commands:**
1118
+ chat <message> - Send message (uses active session)
1119
+ <message> - Direct message (shortcut)
1120
+
1121
+ šŸŽ¬ **YouTube Commands:**
1122
+ youtube download <url> - Download video/audio (config-aware)
1123
+ youtube info <url> - Get video information
1124
+
1125
+ šŸ¤– **Agent Management:**
1126
+ agents - Show cached agents
1127
+ debug agents - Debug agent memory status
1128
+
1129
+ šŸ”„ **Modes:**
1130
+ interactive - Start chat-only interactive mode
1131
+ shell - This shell mode (default)
1132
+
1133
+ šŸ› ļø **Utilities:**
1134
+ health - System health check
1135
+ demo - Run demo
1136
+ examples - Show usage examples
1137
+
1138
+ 🚪 **Exit:**
1139
+ exit, quit, bye - Exit shell
1140
+
1141
+ šŸ’” **Features:**
1142
+ - Auto-session creation with UUID4
1143
+ - agent_config.yaml support
1144
+ - Configuration-aware agents
1145
+ - Agent caching and reuse
1146
+ - Persistent conversation history
1147
+ - Customizable themes and behavior
1148
+ """)
1149
+ return True
1150
+
1151
+ elif cmd == 'clear':
1152
+ click.clear()
1153
+ return True
1154
+
1155
+ # Handle configuration commands
1156
+ elif cmd == 'config':
1157
+ return handle_config_command(args)
1158
+
1159
+ # Handle regular commands by routing to appropriate CLI commands
1160
+ try:
1161
+ if cmd == 'session':
1162
+ return handle_session_command(args)
1163
+ elif cmd == 'chat':
1164
+ return handle_chat_command(args)
1165
+ elif cmd == 'youtube':
1166
+ return handle_youtube_command(args)
1167
+ elif cmd == 'interactive':
1168
+ return handle_interactive_command()
1169
+ elif cmd == 'health':
1170
+ return handle_health_command()
1171
+ elif cmd == 'demo':
1172
+ return handle_demo_command()
1173
+ elif cmd == 'examples':
1174
+ return handle_examples_command()
1175
+ elif cmd == 'agents':
1176
+ return handle_agents_command()
1177
+ elif cmd == 'debug':
1178
+ if args and args[0] == 'agents':
1179
+ return handle_debug_agents_command()
1180
+ else:
1181
+ click.echo("āŒ Available debug commands: debug agents")
1182
+ return True
1183
+ else:
1184
+ # Try to interpret as chat message
1185
+ return handle_chat_command([command_line])
1186
+
1187
+ except Exception as e:
1188
+ click.echo(f"āŒ Error executing command: {e}")
1189
+ return True
1190
+
1191
+ def handle_config_command(args):
1192
+ """Handle configuration commands in shell"""
1193
+ if not args:
1194
+ click.echo("āŒ Usage: config <show|get|set|save-sample> [args]")
1195
+ return True
1196
+
1197
+ subcmd = args[0].lower()
1198
+
1199
+ if subcmd == 'show':
1200
+ click.echo("šŸ“‹ Current Configuration (Key Settings):")
1201
+ key_settings = [
1202
+ 'cli.default_mode',
1203
+ 'cli.auto_session',
1204
+ 'cli.theme',
1205
+ 'agents.youtube.default_audio_only',
1206
+ 'agents.web_search.default_max_results',
1207
+ 'session.auto_cleanup'
1208
+ ]
1209
+ for key in key_settings:
1210
+ value = cli_instance.config.get(key)
1211
+ click.echo(f" {key}: {value}")
1212
+
1213
+ elif subcmd == 'get':
1214
+ if len(args) < 2:
1215
+ click.echo("āŒ Usage: config get <key>")
1216
+ return True
1217
+ key = args[1]
1218
+ value = cli_instance.config.get(key)
1219
+ if value is not None:
1220
+ click.echo(f"{key}: {value}")
1221
+ else:
1222
+ click.echo(f"āŒ Configuration key '{key}' not found")
1223
+
1224
+ elif subcmd == 'set':
1225
+ if len(args) < 3:
1226
+ click.echo("āŒ Usage: config set <key> <value>")
1227
+ return True
1228
+ key = args[1]
1229
+ value = args[2]
1230
+
1231
+ # Parse value
1232
+ try:
1233
+ if value.lower() in ['true', 'false']:
1234
+ parsed_value = value.lower() == 'true'
1235
+ elif value.isdigit():
1236
+ parsed_value = int(value)
1237
+ elif '.' in value and value.replace('.', '').isdigit():
1238
+ parsed_value = float(value)
1239
+ else:
1240
+ parsed_value = value
1241
+ except:
1242
+ parsed_value = value
1243
+
1244
+ # Set the value
1245
+ keys = key.split('.')
1246
+ current = cli_instance.config.config
1247
+ for k in keys[:-1]:
1248
+ if k not in current:
1249
+ current[k] = {}
1250
+ current = current[k]
1251
+
1252
+ current[keys[-1]] = parsed_value
1253
+ click.echo(f"āœ… Set {key} = {parsed_value}")
1254
+
1255
+ elif subcmd == 'save-sample':
1256
+ if len(args) < 2:
1257
+ path = "./agent_config.yaml"
1258
+ else:
1259
+ path = args[1]
1260
+
1261
+ if cli_instance.config.save_sample_config(path):
1262
+ click.echo(f"āœ… Sample configuration saved to: {path}")
1263
+ else:
1264
+ click.echo("āŒ Failed to save sample configuration")
1265
+
1266
+ else:
1267
+ click.echo(f"āŒ Unknown config command: {subcmd}")
1268
+
1269
+ return True
1270
+
1271
+ # Helper functions for session commands
1272
+ def handle_session_history(limit: int = 20):
1273
+ """Handle session history command with database debugging"""
1274
+
1275
+ async def show_history():
1276
+ current_session = cli_instance.get_current_session()
1277
+
1278
+ if not current_session:
1279
+ click.echo("āŒ No active session")
1280
+ return
1281
+
1282
+ try:
1283
+ # šŸ”§ UPDATED: Use cached agent
1284
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
1285
+ {"operation": "history_access"})
1286
+
1287
+ # Show database info if verbose
1288
+ if cli_instance.config.get('cli.verbose', False):
1289
+ if agent.memory and hasattr(agent.memory, 'redis_client'):
1290
+ redis_client = agent.memory.redis_client
1291
+ current_db = redis_client.connection_pool.connection_kwargs.get('db', 'Unknown')
1292
+ expected_key = f"session:{context.conversation_id}:messages"
1293
+
1294
+ click.echo(f"šŸ” History Database: {current_db}")
1295
+ click.echo(f"šŸ” History Key: {expected_key}")
1296
+
1297
+ # Check key existence and length
1298
+ exists = redis_client.exists(expected_key)
1299
+ length = redis_client.llen(expected_key) if exists else 0
1300
+ click.echo(f"šŸ” Key exists: {'āœ…' if exists else 'āŒ'}")
1301
+ click.echo(f"šŸ” Key length: {length}")
1302
+
1303
+ # Get conversation history
1304
+ history_data = await agent.get_conversation_history(limit=limit, include_metadata=True)
1305
+
1306
+ if not history_data:
1307
+ click.echo(f"šŸ“‹ No conversation history found for current session")
1308
+
1309
+ # Enhanced debug for empty history
1310
+ if cli_instance.config.get('cli.verbose', False) and agent.memory:
1311
+ click.echo(f"\nšŸ” **Debugging Empty History**")
1312
+
1313
+ # Test retrieval directly
1314
+ try:
1315
+ direct_messages = agent.memory.get_recent_messages(limit=10,
1316
+ conversation_id=context.conversation_id)
1317
+ click.echo(f"Direct get_recent_messages: {len(direct_messages)} messages")
1318
+
1319
+ if direct_messages:
1320
+ click.echo(f"Sample direct message:")
1321
+ sample = direct_messages[0]
1322
+ if isinstance(sample, dict):
1323
+ click.echo(f" Content: {sample.get('content', 'No content')[:50]}...")
1324
+ click.echo(f" Type: {sample.get('message_type', 'No type')}")
1325
+ except Exception as e:
1326
+ click.echo(f"Direct retrieval error: {e}")
1327
+
1328
+ return
1329
+
1330
+ # Display history (rest stays the same)
1331
+ session_display = current_session[:12] + "..." if len(current_session) > 12 else current_session
1332
+ click.echo(f"šŸ“‹ **Conversation History** (Session: {session_display})")
1333
+ click.echo(f"šŸ“Š Total Messages: {len(history_data)}")
1334
+ click.echo("=" * 50)
1335
+
1336
+ recent_messages = history_data[-10:] if len(history_data) > 10 else history_data
1337
+
1338
+ for i, msg in enumerate(recent_messages, 1):
1339
+ content = msg.get('content', '')
1340
+ if len(content) > 100:
1341
+ content = content[:100] + "..."
1342
+
1343
+ message_type = msg.get('message_type', 'unknown')
1344
+ timestamp = msg.get('timestamp', 'Unknown time')
1345
+
1346
+ if isinstance(timestamp, str):
1347
+ try:
1348
+ from datetime import datetime
1349
+ dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
1350
+ timestamp = dt.strftime('%H:%M:%S')
1351
+ except:
1352
+ timestamp = str(timestamp)[:8]
1353
+
1354
+ if message_type == 'user_input':
1355
+ click.echo(f"{i}. šŸ—£ļø You ({timestamp}): {content}")
1356
+ elif message_type == 'agent_response':
1357
+ sender = msg.get('sender_id', 'agent')[:10]
1358
+ click.echo(f"{i}. šŸ¤– {sender} ({timestamp}): {content}")
1359
+ else:
1360
+ click.echo(f"{i}. ā„¹ļø {message_type} ({timestamp}): {content}")
1361
+
1362
+ if len(history_data) > 10:
1363
+ click.echo(f"\nšŸ’” Showing last 10 of {len(history_data)} messages")
1364
+
1365
+ except Exception as e:
1366
+ click.echo(f"āŒ Error retrieving history: {e}")
1367
+ if cli_instance.config.get('cli.verbose', False):
1368
+ import traceback
1369
+ click.echo(f"šŸ” Debug traceback: {traceback.format_exc()}")
1370
+
1371
+ asyncio.run(show_history())
1372
+ return True
1373
+
1374
+ def handle_session_summary():
1375
+ """Handle session summary command in shell"""
1376
+
1377
+ async def show_summary():
1378
+ current_session = cli_instance.get_current_session()
1379
+
1380
+ if not current_session:
1381
+ click.echo("āŒ No active session")
1382
+ return
1383
+
1384
+ try:
1385
+ # šŸ”§ UPDATED: Use cached agent
1386
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
1387
+ {"operation": "summary_access"})
1388
+
1389
+ # Get conversation summary
1390
+ summary_data = await agent.get_conversation_summary()
1391
+
1392
+ if 'error' in summary_data:
1393
+ click.echo(f"āŒ Error getting summary: {summary_data['error']}")
1394
+ else:
1395
+ click.echo(f"šŸ“Š **Session Summary**")
1396
+ click.echo("=" * 30)
1397
+ click.echo(f"šŸ’¬ Messages: {summary_data.get('total_messages', 0)} total")
1398
+ click.echo(f"ā±ļø Duration: {summary_data.get('session_duration', 'Unknown')}")
1399
+ click.echo(f"šŸ”— Session: {current_session[:8]}...")
1400
+
1401
+ except Exception as e:
1402
+ click.echo(f"āŒ Error retrieving summary: {e}")
1403
+
1404
+ asyncio.run(show_summary())
1405
+ return True
1406
+
1407
+ def handle_session_clear():
1408
+ """Handle session clear command in shell"""
1409
+
1410
+ async def clear_history():
1411
+ current_session = cli_instance.get_current_session()
1412
+
1413
+ if not current_session:
1414
+ click.echo("āŒ No active session")
1415
+ return
1416
+
1417
+ try:
1418
+ # šŸ”§ UPDATED: Use cached agent
1419
+ agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
1420
+ {"operation": "history_clear"})
1421
+
1422
+ # Clear conversation history
1423
+ success = await agent.clear_conversation_history()
1424
+
1425
+ if success:
1426
+ click.echo(f"āœ… Cleared conversation history")
1427
+ else:
1428
+ click.echo(f"āŒ Failed to clear conversation history")
1429
+
1430
+ except Exception as e:
1431
+ click.echo(f"āŒ Error clearing history: {e}")
1432
+
1433
+ asyncio.run(clear_history())
1434
+ return True
1435
+
1436
+ def handle_session_command(args):
1437
+ """šŸ”§ UPDATED: Handle session subcommands with agent cleanup"""
1438
+ if not args:
1439
+ click.echo("āŒ Usage: session <create|current|status|use|end|history|summary|clear|agents> [args]")
1440
+ return True
1441
+
1442
+ subcmd = args[0].lower()
1443
+
1444
+ if subcmd == 'create':
1445
+ # Clear any existing cached agents before creating new session
1446
+ cli_instance.clear_all_agents()
1447
+
1448
+ if len(args) > 1:
1449
+ session_name = args[1]
1450
+ session_prefix = cli_instance.config.get('cli.session_prefix', 'ambivo')
1451
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
1452
+ full_session_id = f"{session_prefix}_{session_name}_{timestamp}"
1453
+ else:
1454
+ # Generate UUID4 session
1455
+ full_session_id = str(uuid.uuid4())
1456
+
1457
+ if cli_instance.set_current_session(full_session_id):
1458
+ click.echo(f"āœ… Created and activated session: {full_session_id}")
1459
+ click.echo(f"🧹 Cleared all cached agents")
1460
+ else:
1461
+ click.echo("āŒ Failed to create session")
1462
+
1463
+ elif subcmd == 'current':
1464
+ current = cli_instance.get_current_session()
1465
+ if current:
1466
+ click.echo(f"šŸ“‹ Current session: {current}")
1467
+
1468
+ # Show cached agents info
1469
+ agents_info = cli_instance.get_cached_agents_info()
1470
+ click.echo(f"šŸ¤– Cached agents: {agents_info['total_agents']}")
1471
+ else:
1472
+ click.echo("āŒ No active session")
1473
+
1474
+ elif subcmd == 'status':
1475
+ current = cli_instance.get_current_session()
1476
+ auto_session = cli_instance.config.get('cli.auto_session', True)
1477
+ agents_info = cli_instance.get_cached_agents_info()
1478
+
1479
+ click.echo("šŸ“Š Session Status:")
1480
+ if current:
1481
+ click.echo(f" āœ… Active session: {current}")
1482
+ click.echo(f" šŸ“ Session file: {cli_instance.session_file}")
1483
+ else:
1484
+ click.echo(" āŒ No active session")
1485
+ click.echo(f" šŸ”„ Auto-session: {auto_session}")
1486
+ click.echo(f" šŸ¤– Cached agents: {agents_info['total_agents']}")
1487
+
1488
+ elif subcmd == 'use':
1489
+ if len(args) < 2:
1490
+ click.echo("āŒ Usage: session use <name>")
1491
+ return True
1492
+ session_name = args[1]
1493
+
1494
+ # Clear cached agents when switching
1495
+ cli_instance.clear_all_agents()
1496
+
1497
+ if cli_instance.set_current_session(session_name):
1498
+ click.echo(f"āœ… Switched to session: {session_name}")
1499
+ click.echo(f"🧹 Cleared cached agents from previous session")
1500
+ else:
1501
+ click.echo("āŒ Failed to switch session")
1502
+
1503
+ elif subcmd == 'end':
1504
+ current = cli_instance.get_current_session()
1505
+ if current:
1506
+ # Clear cached agents for this session
1507
+ cli_instance.clear_session_agents(current)
1508
+
1509
+ if cli_instance.clear_current_session():
1510
+ click.echo(f"āœ… Ended session: {current}")
1511
+ click.echo(f"🧹 Cleared cached agents for session")
1512
+
1513
+ # Auto-create replacement session
1514
+ cli_instance._ensure_auto_session()
1515
+ new_session = cli_instance.get_current_session()
1516
+ if new_session:
1517
+ click.echo(f"šŸ”„ Auto-created replacement session: {new_session}")
1518
+ else:
1519
+ click.echo("āŒ Failed to end session")
1520
+ else:
1521
+ click.echo("āŒ No active session to end")
1522
+
1523
+ elif subcmd == 'history':
1524
+ limit = 20 # default
1525
+ if len(args) > 1 and args[1].isdigit():
1526
+ limit = int(args[1])
1527
+ return handle_session_history(limit)
1528
+
1529
+ elif subcmd == 'summary':
1530
+ return handle_session_summary()
1531
+
1532
+ elif subcmd == 'clear':
1533
+ # Add confirmation in shell mode
1534
+ if click.confirm('Are you sure you want to clear the conversation history?'):
1535
+ return handle_session_clear()
1536
+ else:
1537
+ click.echo("āŒ Operation cancelled")
1538
+
1539
+ elif subcmd == 'agents':
1540
+ # Show cached agents for current session
1541
+ current_session = cli_instance.get_current_session()
1542
+ agents_info = cli_instance.get_cached_agents_info()
1543
+
1544
+ click.echo(f"šŸ¤– **Cached Agents** (Session: {current_session[:8] if current_session else 'None'}...)")
1545
+ click.echo(f"šŸ“Š Total: {agents_info['total_agents']}")
1546
+ click.echo("=" * 40)
1547
+
1548
+ if agents_info['agents']:
1549
+ for agent_info in agents_info['agents']:
1550
+ click.echo(f"\nšŸ“„ {agent_info['agent_type']}")
1551
+ click.echo(f" ID: {agent_info['agent_id']}")
1552
+ click.echo(f" Memory: {'āœ…' if agent_info['memory_available'] else 'āŒ'}")
1553
+ click.echo(f" Created: {agent_info['created_at']}")
1554
+ else:
1555
+ click.echo("\nšŸ“­ No cached agents")
1556
+
1557
+ else:
1558
+ click.echo(f"āŒ Unknown session command: {subcmd}")
1559
+ click.echo("šŸ’” Available: create, current, status, use, end, history, summary, clear, agents")
1560
+
1561
+ return True
1562
+
1563
+ def handle_chat_command(args):
1564
+ """Handle chat command"""
1565
+ if not args:
1566
+ click.echo("āŒ Usage: chat <message>")
1567
+ return True
1568
+
1569
+ message = ' '.join(args)
1570
+
1571
+ # Process chat message
1572
+ async def process_chat():
1573
+ # Determine conversation ID
1574
+ active_session = cli_instance.get_current_session()
1575
+ if active_session:
1576
+ conv_id = active_session
1577
+ session_display = active_session[:8] + "..." if len(active_session) > 8 else active_session
1578
+ session_source = f"session: {session_display}"
1579
+ else:
1580
+ conv_id = "shell"
1581
+ session_source = "default: shell"
1582
+
1583
+ verbose = cli_instance.config.get('cli.verbose', False)
1584
+ if verbose:
1585
+ click.echo(f"šŸ’¬ Processing: {message}")
1586
+ click.echo(f"šŸ“‹ {session_source}")
1587
+
1588
+ try:
1589
+ response = await cli_instance.smart_message_routing(message)
1590
+ click.echo(f"\nšŸ¤– Response:\n{response}")
1591
+ except Exception as e:
1592
+ click.echo(f"āŒ Error: {e}")
1593
+
1594
+ asyncio.run(process_chat())
1595
+ return True
1596
+
1597
+ def handle_youtube_command(args):
1598
+ """Handle YouTube commands"""
1599
+ if not args:
1600
+ click.echo("āŒ Usage: youtube <download|info> <url>")
1601
+ return True
1602
+
1603
+ subcmd = args[0].lower()
1604
+ if len(args) < 2:
1605
+ click.echo(f"āŒ Usage: youtube {subcmd} <url>")
1606
+ return True
1607
+
1608
+ url = args[1]
1609
+
1610
+ if subcmd == 'download':
1611
+ click.echo(f"šŸ“ŗ Downloading: {url}")
1612
+ # Route to chat system
1613
+ return handle_chat_command([f"download video from {url}"])
1614
+ elif subcmd == 'info':
1615
+ click.echo(f"šŸ“‹ Getting info: {url}")
1616
+ return handle_chat_command([f"get info about {url}"])
1617
+ else:
1618
+ click.echo(f"āŒ Unknown YouTube command: {subcmd}")
1619
+
1620
+ return True
1621
+
1622
+ def handle_interactive_command():
1623
+ """Handle interactive mode"""
1624
+ click.echo("šŸ”„ Switching to interactive chat mode...")
1625
+ click.echo("šŸ’” Type 'quit' to return to shell")
1626
+
1627
+ # Start interactive chat mode
1628
+ async def interactive_chat():
1629
+ while True:
1630
+ try:
1631
+ current_session = cli_instance.get_current_session()
1632
+ if current_session:
1633
+ session_short = current_session[:8] if len(current_session) > 8 else current_session
1634
+ prompt_text = f"šŸ—£ļø You ({session_short})"
1635
+ else:
1636
+ prompt_text = "šŸ—£ļø You"
1637
+
1638
+ user_input = click.prompt(f"\n{prompt_text}", type=str)
1639
+
1640
+ if user_input.lower() in ['quit', 'exit', 'bye']:
1641
+ click.echo("šŸ”„ Returning to shell...")
1642
+ break
1643
+
1644
+ # Process as chat
1645
+ response = await cli_instance.smart_message_routing(user_input)
1646
+ click.echo(f"šŸ¤– Agent: {response}")
1647
+
1648
+ except KeyboardInterrupt:
1649
+ click.echo("\nšŸ”„ Returning to shell...")
1650
+ break
1651
+ except EOFError:
1652
+ click.echo("\nšŸ”„ Returning to shell...")
1653
+ break
1654
+
1655
+ asyncio.run(interactive_chat())
1656
+ return True
1657
+
1658
+ def handle_health_command():
1659
+ """Handle health check"""
1660
+ click.echo("šŸ„ Running health check...")
1661
+ click.echo("āœ… CLI is working")
1662
+ click.echo("āœ… Session management available")
1663
+ click.echo("āœ… Smart routing available")
1664
+ click.echo("āœ… Agent caching enabled")
1665
+ click.echo(f"āœ… Configuration loaded: {cli_instance.config.config_path or 'defaults'}")
1666
+
1667
+ # Show cached agents
1668
+ agents_info = cli_instance.get_cached_agents_info()
1669
+ click.echo(f"šŸ¤– Cached agents: {agents_info['total_agents']}")
1670
+
1671
+ return True
1672
+
1673
+ def handle_demo_command():
1674
+ """Handle demo"""
1675
+ click.echo("šŸŽŖ Running demo...")
1676
+ click.echo("šŸ’” This would show a demo of the system")
1677
+ return True
1678
+
1679
+ def handle_examples_command():
1680
+ """Handle examples"""
1681
+ click.echo("šŸ“š Usage Examples:")
1682
+ click.echo(" config set cli.theme minimal")
1683
+ click.echo(" session create my_project")
1684
+ click.echo(" chat 'Hello, I need help with video processing'")
1685
+ click.echo(" youtube download https://youtube.com/watch?v=example")
1686
+ click.echo(" session history")
1687
+ click.echo(" session agents")
1688
+ click.echo(" config show")
1689
+ click.echo(" session end")
1690
+ return True
1691
+
1692
+ def handle_agents_command():
1693
+ """šŸ†• Handle agents command - show cached agents"""
1694
+ agents_info = cli_instance.get_cached_agents_info()
1695
+ current_session = cli_instance.get_current_session()
1696
+
1697
+ click.echo(f"šŸ¤– **Cached Agents** (Session: {current_session[:8] if current_session else 'None'}...)")
1698
+ click.echo(f"šŸ“Š Total: {agents_info['total_agents']}")
1699
+ click.echo("=" * 40)
1700
+
1701
+ if agents_info['agents']:
1702
+ for agent_info in agents_info['agents']:
1703
+ click.echo(f"\nšŸ“„ {agent_info['agent_type']}")
1704
+ click.echo(f" ID: {agent_info['agent_id']}")
1705
+ click.echo(f" Memory: {'āœ…' if agent_info['memory_available'] else 'āŒ'}")
1706
+ click.echo(f" Created: {agent_info['created_at']}")
1707
+ else:
1708
+ click.echo("\nšŸ“­ No cached agents")
1709
+ click.echo("šŸ’” Agents will be created when you send messages")
1710
+
1711
+ return True
1712
+
1713
+ def handle_debug_agents_command():
1714
+ """šŸ†• Handle debug agents command"""
1715
+ click.echo(f"\nšŸ” CACHED AGENTS DEBUG")
1716
+ click.echo("=" * 50)
1717
+
1718
+ agents_info = cli_instance.get_cached_agents_info()
1719
+ current_session = cli_instance.get_current_session()
1720
+
1721
+ click.echo(f"Current session: {current_session}")
1722
+ click.echo(f"Total cached agents: {agents_info['total_agents']}")
1723
+
1724
+ if agents_info['agents']:
1725
+ for agent_info in agents_info['agents']:
1726
+ click.echo(f"\nšŸ¤– {agent_info['agent_type']}:")
1727
+ click.echo(f" Cache Key: {agent_info['cache_key']}")
1728
+ click.echo(f" Agent ID: {agent_info['agent_id']}")
1729
+ click.echo(f" Session: {agent_info['session_id']}")
1730
+ click.echo(f" Memory: {'āœ…' if agent_info['memory_available'] else 'āŒ'}")
1731
+ click.echo(f" Created: {agent_info['created_at']}")
1732
+ else:
1733
+ click.echo("\nšŸ“­ No cached agents")
1734
+
1735
+ return True
1736
+
1737
+ # Main shell loop
1738
+ try:
1739
+ while True:
1740
+ try:
1741
+ prompt = get_prompt()
1742
+
1743
+ # Use input() instead of click.prompt() for better control
1744
+ try:
1745
+ command_line = input(prompt)
1746
+ except (KeyboardInterrupt, EOFError):
1747
+ click.echo("\nšŸ‘‹ Goodbye!")
1748
+ break
1749
+ except Exception as e:
1750
+ click.echo(f"\nāš ļø Input error: {e}")
1751
+ continue
1752
+
1753
+ # Process command
1754
+ if not process_shell_command(command_line):
1755
+ break
1756
+
1757
+ except KeyboardInterrupt:
1758
+ click.echo("\nšŸ’” Use 'exit' to quit")
1759
+ continue
1760
+ except EOFError:
1761
+ click.echo("\nšŸ‘‹ Goodbye!")
1762
+ break
1763
+
1764
+ except Exception as e:
1765
+ click.echo(f"āŒ Shell error: {e}")
1766
+
1767
+
1768
+ @cli.command()
1769
+ @click.argument('message')
1770
+ @click.option('--conversation', '-conv', help='Conversation ID (overrides active session)')
1771
+ @click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
1772
+ def chat(message: str, conversation: Optional[str], format: str):
1773
+ """Send a message using smart agent routing with configuration support"""
1774
+
1775
+ # Determine conversation ID
1776
+ if conversation:
1777
+ conv_id = conversation
1778
+ session_source = f"explicit: {conversation}"
1779
+ else:
1780
+ active_session = cli_instance.get_current_session()
1781
+ if active_session:
1782
+ conv_id = active_session
1783
+ session_source = f"active session: {active_session}"
1784
+ else:
1785
+ conv_id = "cli"
1786
+ session_source = "default: cli"
1787
+
1788
+ verbose = cli_instance.config.get('cli.verbose', False)
1789
+ if verbose:
1790
+ click.echo(f"šŸ’¬ Processing: {message}")
1791
+ click.echo(f"šŸ“‹ Session: {session_source}")
1792
+
1793
+ async def process():
1794
+ start_time = time.time()
1795
+ response = await cli_instance.smart_message_routing(message)
1796
+ processing_time = time.time() - start_time
1797
+
1798
+ if format == 'json':
1799
+ result = {
1800
+ 'success': True,
1801
+ 'response': response,
1802
+ 'processing_time': processing_time,
1803
+ 'message': message,
1804
+ 'conversation_id': conv_id,
1805
+ 'session_source': session_source,
1806
+ 'paradigm': 'cached_agent_reuse',
1807
+ 'config_loaded': cli_instance.config.config_path is not None
1808
+ }
1809
+ click.echo(json.dumps(result, indent=2))
1810
+ else:
1811
+ click.echo(f"\nšŸ¤– Response:\n{response}")
1812
+ if verbose:
1813
+ click.echo(f"\nā±ļø Processing time: {processing_time:.2f}s")
1814
+ click.echo(f"šŸ“‹ Conversation: {conv_id}")
1815
+ click.echo(f"🌟 Using cached agent reuse")
1816
+
1817
+ asyncio.run(process())
1818
+
1819
+
1820
+ @cli.command()
1821
+ def interactive():
1822
+ """Interactive chat mode"""
1823
+
1824
+ click.echo("šŸ¤– Starting interactive chat mode...")
1825
+
1826
+ # Check for active session
1827
+ active_session = cli_instance.get_current_session()
1828
+
1829
+ if active_session:
1830
+ session_display = active_session[:8] + "..." if len(active_session) > 8 else active_session
1831
+ click.echo(f"šŸ“‹ Using active session: {session_display}")
1832
+ else:
1833
+ click.echo("šŸ“‹ No active session - using default conversation")
1834
+
1835
+ click.echo("Type 'quit', 'exit', or 'bye' to exit")
1836
+ click.echo("-" * 60)
1837
+
1838
+ async def interactive_loop():
1839
+ # Use active session or generate a unique one for this interactive session
1840
+ if active_session:
1841
+ conversation_id = active_session
1842
+ else:
1843
+ conversation_id = f"interactive_{int(time.time())}"
1844
+
1845
+ while True:
1846
+ try:
1847
+ user_input = click.prompt("\nšŸ—£ļø You", type=str)
1848
+
1849
+ if user_input.lower() in ['quit', 'exit', 'bye']:
1850
+ click.echo("šŸ‘‹ Goodbye!")
1851
+ break
1852
+
1853
+ # Process with smart routing using conversation_id
1854
+ response = await cli_instance.smart_message_routing(user_input)
1855
+
1856
+ click.echo(f"šŸ¤– Agent: {response}")
1857
+ session_display = conversation_id[:8] + "..." if len(conversation_id) > 8 else conversation_id
1858
+ click.echo(f"šŸ“‹ Session: {session_display}")
1859
+
1860
+ except KeyboardInterrupt:
1861
+ click.echo("\nšŸ‘‹ Goodbye!")
1862
+ break
1863
+ except EOFError:
1864
+ click.echo("\nšŸ‘‹ Goodbye!")
1865
+ break
1866
+
1867
+ asyncio.run(interactive_loop())
1868
+
1869
+
1870
+ if __name__ == '__main__':
1871
+ cli()