Coverage for src / osiris_cli / agent_selector.py: 0%
122 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
1#!/usr/bin/env python3
2"""
3Agent Selector - Letta-style agent management
4Allows users to select, create, and switch between agents
5"""
7import json
8from pathlib import Path
9from datetime import datetime
10from typing import Optional, List, Dict
11from rich.console import Console
12from rich.table import Table
13from rich.panel import Panel
14from rich.prompt import Prompt, Confirm
15from rich.text import Text
17console = Console()
20class AgentSelector:
21 """
22 Interactive agent selector (Letta-style)
23 """
25 COLORS = {
26 "primary": "#00D9FF",
27 "success": "#00FF9F",
28 "warning": "#FFB800",
29 "muted": "#6B7280",
30 "accent": "#A78BFA",
31 }
33 def __init__(self):
34 self.agents_dir = Path.home() / ".osiris" / "agents"
35 self.agents_dir.mkdir(parents=True, exist_ok=True)
37 def list_agents(self) -> List[Dict]:
38 """List all available agents"""
39 agents = []
41 for agent_file in self.agents_dir.glob("*.json"):
42 try:
43 with open(agent_file, 'r') as f:
44 data = json.load(f)
46 # Calculate last used
47 last_used = data.get("last_used", "never")
48 last_used_dt = None
49 if last_used != "never":
50 try:
51 dt = datetime.fromisoformat(last_used)
52 last_used_dt = dt
53 now = datetime.now()
54 delta = now - dt
56 if delta.seconds < 60:
57 last_used = "just now"
58 elif delta.seconds < 3600:
59 last_used = f"{delta.seconds // 60}min ago"
60 elif delta.days == 0:
61 last_used = f"{delta.seconds // 3600}h ago"
62 elif delta.days == 1:
63 last_used = "yesterday"
64 else:
65 last_used = f"{delta.days}d ago"
66 except:
67 pass
69 agents.append({
70 "name": agent_file.stem,
71 "persona": data.get("persona", "General assistant")[:40],
72 "skills": data.get("skills", []),
73 "facts": len(data.get("core_memory", {})),
74 "last_used": last_used,
75 "last_used_dt": last_used_dt,
76 "file": agent_file
77 })
78 except:
79 continue
81 # Sort by last used (most recent first)
82 agents.sort(key=lambda x: x.get("last_used_dt") or datetime.min, reverse=True)
83 return agents
85 def select_agent(self) -> Optional[str]:
86 """Interactive agent selector"""
87 agents = self.list_agents()
89 console.clear()
90 console.print()
92 # Title
93 title = Text()
94 title.append("Select Agent", style=f"bold {self.COLORS['primary']}")
95 console.print(title, justify="center")
96 console.print()
98 # Create table
99 table = Table(show_header=True, header_style=f"bold {self.COLORS['accent']}")
100 table.add_column("#", style="dim", width=3)
101 table.add_column("Name", style=f"bold {self.COLORS['primary']}")
102 table.add_column("Persona", style=self.COLORS["muted"])
103 table.add_column("Skills", style=self.COLORS["success"])
104 table.add_column("Facts", style=self.COLORS["warning"], justify="right")
105 table.add_column("Last Used", style="dim")
107 for i, agent in enumerate(agents, 1):
108 skills_str = ", ".join(agent["skills"][:2])
109 if len(agent["skills"]) > 2:
110 skills_str += f" +{len(agent['skills']) - 2}"
112 table.add_row(
113 str(i),
114 agent["name"],
115 agent["persona"],
116 skills_str or "none",
117 str(agent["facts"]),
118 agent["last_used"]
119 )
121 # Add "create new" option
122 table.add_row(
123 str(len(agents) + 1),
124 Text("+ Create new agent", style=f"bold {self.COLORS['success']}"),
125 "",
126 "",
127 "",
128 ""
129 )
131 console.print(table)
132 console.print()
134 # Get selection
135 try:
136 choice = Prompt.ask(
137 f"[{self.COLORS['primary']}]Choose agent[/{self.COLORS['primary']}]",
138 default="1" if agents else str(len(agents) + 1)
139 )
141 if choice.isdigit():
142 idx = int(choice) - 1
143 if 0 <= idx < len(agents):
144 selected = agents[idx]["name"]
145 console.print(f"\n[{self.COLORS['success']}]✓ Selected: {selected}[/{self.COLORS['success']}]\n")
146 return selected
147 elif idx == len(agents):
148 return self.create_agent()
150 console.print(f"\n[{self.COLORS['warning']}]Invalid choice. Using 'main'[/{self.COLORS['warning']}]\n")
151 return "main"
153 except (ValueError, KeyboardInterrupt):
154 console.print(f"\n[{self.COLORS['warning']}]Cancelled. Using 'main'[/{self.COLORS['warning']}]\n")
155 return "main"
157 def create_agent(self) -> Optional[str]:
158 """Create a new agent interactively"""
159 console.print()
160 console.print(f"[bold {self.COLORS['primary']}]Create New Agent[/bold {self.COLORS['primary']}]\n")
162 try:
163 # Get agent name
164 name = Prompt.ask(
165 f"[{self.COLORS['primary']}]Agent name[/{self.COLORS['primary']}]",
166 default="assistant"
167 )
169 # Get persona
170 console.print(f"\n[{self.COLORS['accent']}]Choose a persona:[/{self.COLORS['accent']}]")
172 personas = {
173 "1": ("General Assistant", "A helpful general-purpose AI assistant"),
174 "2": ("Code Expert", "Senior software engineer specializing in code"),
175 "3": ("Code Reviewer", "Focused on code quality and best practices"),
176 "4": ("Debugger", "Expert at finding and fixing bugs"),
177 "5": ("DevOps Engineer", "Infrastructure and deployment specialist"),
178 "6": ("Data Analyst", "Data science and analytics expert"),
179 "7": ("Technical Writer", "Documentation specialist"),
180 "8": ("System Architect", "Software architecture and design expert"),
181 "9": ("Custom", "Define your own persona")
182 }
184 for key, (title, desc) in personas.items():
185 console.print(f" {key}. [{self.COLORS['primary']}]{title}[/{self.COLORS['primary']}] - [{self.COLORS['muted']}]{desc}[/{self.COLORS['muted']}]")
187 persona_choice = Prompt.ask(
188 f"\n[{self.COLORS['primary']}]Choose persona[/{self.COLORS['primary']}]",
189 default="1"
190 )
192 if persona_choice == "9":
193 persona = Prompt.ask(f"[{self.COLORS['primary']}]Enter custom persona[/{self.COLORS['primary']}]")
194 elif persona_choice in personas:
195 persona = personas[persona_choice][1]
196 else:
197 persona = personas["1"][1]
199 # Get skills
200 console.print(f"\n[{self.COLORS['muted']}]Add skills (comma-separated, or press Enter to skip):[/{self.COLORS['muted']}]")
201 skills_input = Prompt.ask(
202 f"[{self.COLORS['primary']}]Skills[/{self.COLORS['primary']}]",
203 default=""
204 )
205 skills = [s.strip() for s in skills_input.split(",") if s.strip()]
207 # Create agent data
208 agent_data = {
209 "name": name,
210 "persona": persona,
211 "skills": skills,
212 "core_memory": {},
213 "created": datetime.now().isoformat(),
214 "last_used": datetime.now().isoformat()
215 }
217 # Save agent
218 agent_file = self.agents_dir / f"{name}.json"
219 with open(agent_file, 'w') as f:
220 json.dump(agent_data, f, indent=2)
222 console.print(f"\n[{self.COLORS['success']}]✓ Created agent: {name}[/{self.COLORS['success']}]\n")
223 return name
225 except KeyboardInterrupt:
226 console.print(f"\n[{self.COLORS['warning']}]Cancelled[/{self.COLORS['warning']}]\n")
227 return "main"
229 def update_last_used(self, agent_name: str):
230 """Update agent's last used timestamp"""
231 agent_file = self.agents_dir / f"{agent_name}.json"
232 if agent_file.exists():
233 try:
234 with open(agent_file, 'r') as f:
235 data = json.load(f)
237 data["last_used"] = datetime.now().isoformat()
239 with open(agent_file, 'w') as f:
240 json.dump(data, f, indent=2)
241 except:
242 pass
245# Global instance
246agent_selector = AgentSelector()