kairo-code 0.1.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.
- image-service/main.py +178 -0
- infra/chat/app/main.py +84 -0
- kairo/backend/__init__.py +0 -0
- kairo/backend/api/__init__.py +0 -0
- kairo/backend/api/admin/__init__.py +23 -0
- kairo/backend/api/admin/audit.py +54 -0
- kairo/backend/api/admin/content.py +142 -0
- kairo/backend/api/admin/incidents.py +148 -0
- kairo/backend/api/admin/stats.py +125 -0
- kairo/backend/api/admin/system.py +87 -0
- kairo/backend/api/admin/users.py +279 -0
- kairo/backend/api/agents.py +94 -0
- kairo/backend/api/api_keys.py +85 -0
- kairo/backend/api/auth.py +116 -0
- kairo/backend/api/billing.py +41 -0
- kairo/backend/api/chat.py +72 -0
- kairo/backend/api/conversations.py +125 -0
- kairo/backend/api/device_auth.py +100 -0
- kairo/backend/api/files.py +83 -0
- kairo/backend/api/health.py +36 -0
- kairo/backend/api/images.py +80 -0
- kairo/backend/api/openai_compat.py +225 -0
- kairo/backend/api/projects.py +102 -0
- kairo/backend/api/usage.py +32 -0
- kairo/backend/api/webhooks.py +79 -0
- kairo/backend/app.py +297 -0
- kairo/backend/config.py +179 -0
- kairo/backend/core/__init__.py +0 -0
- kairo/backend/core/admin_auth.py +24 -0
- kairo/backend/core/api_key_auth.py +55 -0
- kairo/backend/core/database.py +28 -0
- kairo/backend/core/dependencies.py +70 -0
- kairo/backend/core/logging.py +23 -0
- kairo/backend/core/rate_limit.py +73 -0
- kairo/backend/core/security.py +29 -0
- kairo/backend/models/__init__.py +19 -0
- kairo/backend/models/agent.py +30 -0
- kairo/backend/models/api_key.py +25 -0
- kairo/backend/models/api_usage.py +29 -0
- kairo/backend/models/audit_log.py +26 -0
- kairo/backend/models/conversation.py +48 -0
- kairo/backend/models/device_code.py +30 -0
- kairo/backend/models/feature_flag.py +21 -0
- kairo/backend/models/image_generation.py +24 -0
- kairo/backend/models/incident.py +28 -0
- kairo/backend/models/project.py +28 -0
- kairo/backend/models/uptime_record.py +24 -0
- kairo/backend/models/usage.py +24 -0
- kairo/backend/models/user.py +49 -0
- kairo/backend/schemas/__init__.py +0 -0
- kairo/backend/schemas/admin/__init__.py +0 -0
- kairo/backend/schemas/admin/audit.py +28 -0
- kairo/backend/schemas/admin/content.py +53 -0
- kairo/backend/schemas/admin/stats.py +77 -0
- kairo/backend/schemas/admin/system.py +44 -0
- kairo/backend/schemas/admin/users.py +48 -0
- kairo/backend/schemas/agent.py +42 -0
- kairo/backend/schemas/api_key.py +30 -0
- kairo/backend/schemas/auth.py +57 -0
- kairo/backend/schemas/chat.py +26 -0
- kairo/backend/schemas/conversation.py +39 -0
- kairo/backend/schemas/device_auth.py +40 -0
- kairo/backend/schemas/image.py +15 -0
- kairo/backend/schemas/openai_compat.py +76 -0
- kairo/backend/schemas/project.py +21 -0
- kairo/backend/schemas/status.py +81 -0
- kairo/backend/schemas/usage.py +15 -0
- kairo/backend/services/__init__.py +0 -0
- kairo/backend/services/admin/__init__.py +0 -0
- kairo/backend/services/admin/audit_service.py +78 -0
- kairo/backend/services/admin/content_service.py +119 -0
- kairo/backend/services/admin/incident_service.py +94 -0
- kairo/backend/services/admin/stats_service.py +281 -0
- kairo/backend/services/admin/system_service.py +126 -0
- kairo/backend/services/admin/user_service.py +157 -0
- kairo/backend/services/agent_service.py +107 -0
- kairo/backend/services/api_key_service.py +66 -0
- kairo/backend/services/api_usage_service.py +126 -0
- kairo/backend/services/auth_service.py +101 -0
- kairo/backend/services/chat_service.py +501 -0
- kairo/backend/services/conversation_service.py +264 -0
- kairo/backend/services/device_auth_service.py +193 -0
- kairo/backend/services/email_service.py +55 -0
- kairo/backend/services/image_service.py +181 -0
- kairo/backend/services/llm_service.py +186 -0
- kairo/backend/services/project_service.py +109 -0
- kairo/backend/services/status_service.py +167 -0
- kairo/backend/services/stripe_service.py +78 -0
- kairo/backend/services/usage_service.py +150 -0
- kairo/backend/services/web_search_service.py +96 -0
- kairo/migrations/env.py +60 -0
- kairo/migrations/versions/001_initial.py +55 -0
- kairo/migrations/versions/002_usage_tracking_and_indexes.py +66 -0
- kairo/migrations/versions/003_username_to_email.py +21 -0
- kairo/migrations/versions/004_add_plans_and_verification.py +67 -0
- kairo/migrations/versions/005_add_projects.py +52 -0
- kairo/migrations/versions/006_add_image_generation.py +63 -0
- kairo/migrations/versions/007_add_admin_portal.py +107 -0
- kairo/migrations/versions/008_add_device_code_auth.py +76 -0
- kairo/migrations/versions/009_add_status_page.py +65 -0
- kairo/tools/extract_claude_data.py +465 -0
- kairo/tools/filter_claude_data.py +303 -0
- kairo/tools/generate_curated_data.py +157 -0
- kairo/tools/mix_training_data.py +295 -0
- kairo_code/__init__.py +3 -0
- kairo_code/agents/__init__.py +25 -0
- kairo_code/agents/architect.py +98 -0
- kairo_code/agents/audit.py +100 -0
- kairo_code/agents/base.py +463 -0
- kairo_code/agents/coder.py +155 -0
- kairo_code/agents/database.py +77 -0
- kairo_code/agents/docs.py +88 -0
- kairo_code/agents/explorer.py +62 -0
- kairo_code/agents/guardian.py +80 -0
- kairo_code/agents/planner.py +66 -0
- kairo_code/agents/reviewer.py +91 -0
- kairo_code/agents/security.py +94 -0
- kairo_code/agents/terraform.py +88 -0
- kairo_code/agents/testing.py +97 -0
- kairo_code/agents/uiux.py +88 -0
- kairo_code/auth.py +232 -0
- kairo_code/config.py +172 -0
- kairo_code/conversation.py +173 -0
- kairo_code/heartbeat.py +63 -0
- kairo_code/llm.py +291 -0
- kairo_code/logging_config.py +156 -0
- kairo_code/main.py +818 -0
- kairo_code/router.py +217 -0
- kairo_code/sandbox.py +248 -0
- kairo_code/settings.py +183 -0
- kairo_code/tools/__init__.py +51 -0
- kairo_code/tools/analysis.py +509 -0
- kairo_code/tools/base.py +417 -0
- kairo_code/tools/code.py +58 -0
- kairo_code/tools/definitions.py +617 -0
- kairo_code/tools/files.py +315 -0
- kairo_code/tools/review.py +390 -0
- kairo_code/tools/search.py +185 -0
- kairo_code/ui.py +418 -0
- kairo_code-0.1.0.dist-info/METADATA +13 -0
- kairo_code-0.1.0.dist-info/RECORD +144 -0
- kairo_code-0.1.0.dist-info/WHEEL +5 -0
- kairo_code-0.1.0.dist-info/entry_points.txt +2 -0
- kairo_code-0.1.0.dist-info/top_level.txt +4 -0
kairo_code/main.py
ADDED
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
"""Kairo Code CLI - Main entry point"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.markdown import Markdown
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.live import Live
|
|
13
|
+
from rich.prompt import Confirm
|
|
14
|
+
from rich.status import Status
|
|
15
|
+
from prompt_toolkit import PromptSession
|
|
16
|
+
from prompt_toolkit.history import FileHistory
|
|
17
|
+
|
|
18
|
+
from .config import Config
|
|
19
|
+
from .llm import LLM
|
|
20
|
+
from .conversation import Conversation
|
|
21
|
+
from .router import Router, Intent
|
|
22
|
+
from .tools import create_default_registry, create_readonly_registry
|
|
23
|
+
from .agents import (
|
|
24
|
+
ExplorerAgent, CoderAgent, PlannerAgent, AgentEvent,
|
|
25
|
+
SecurityAgent, ReviewerAgent, AuditAgent, TestingAgent,
|
|
26
|
+
UiUxAgent, GuardianAgent, DocsAgent, ArchitectAgent,
|
|
27
|
+
DatabaseAgent, TerraformAgent,
|
|
28
|
+
)
|
|
29
|
+
from .sandbox import init_sandbox, get_sandbox
|
|
30
|
+
from .ui import OutputFormatter, print_tool_call, print_tool_result, print_status, print_welcome_banner
|
|
31
|
+
from .logging_config import setup_logging, get_logger
|
|
32
|
+
from .settings import (
|
|
33
|
+
load_user_settings, prompt_auto_approve_settings,
|
|
34
|
+
show_settings_menu, should_auto_approve
|
|
35
|
+
)
|
|
36
|
+
from .heartbeat import Heartbeat
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
console = Console()
|
|
40
|
+
logger = None # Initialized in main()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def check_python() -> dict:
|
|
44
|
+
"""
|
|
45
|
+
Check Python availability and return status info.
|
|
46
|
+
Returns dict with 'available', 'command', 'version', and 'install_hint'.
|
|
47
|
+
"""
|
|
48
|
+
result = {
|
|
49
|
+
"available": False,
|
|
50
|
+
"command": None,
|
|
51
|
+
"version": None,
|
|
52
|
+
"install_hint": None,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Try python3 first (preferred on Linux/macOS), then python
|
|
56
|
+
for cmd in ["python3", "python"]:
|
|
57
|
+
if shutil.which(cmd):
|
|
58
|
+
try:
|
|
59
|
+
proc = subprocess.run(
|
|
60
|
+
[cmd, "--version"],
|
|
61
|
+
capture_output=True,
|
|
62
|
+
text=True,
|
|
63
|
+
timeout=5,
|
|
64
|
+
)
|
|
65
|
+
if proc.returncode == 0:
|
|
66
|
+
result["available"] = True
|
|
67
|
+
result["command"] = cmd
|
|
68
|
+
result["version"] = proc.stdout.strip() or proc.stderr.strip()
|
|
69
|
+
return result
|
|
70
|
+
except Exception:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
# Python not found - provide install hints based on system
|
|
74
|
+
import platform
|
|
75
|
+
system = platform.system().lower()
|
|
76
|
+
|
|
77
|
+
if "linux" in system:
|
|
78
|
+
# Check for common distros
|
|
79
|
+
if shutil.which("apt-get"):
|
|
80
|
+
result["install_hint"] = "sudo apt-get install python3"
|
|
81
|
+
elif shutil.which("dnf"):
|
|
82
|
+
result["install_hint"] = "sudo dnf install python3"
|
|
83
|
+
elif shutil.which("pacman"):
|
|
84
|
+
result["install_hint"] = "sudo pacman -S python"
|
|
85
|
+
elif shutil.which("apk"):
|
|
86
|
+
result["install_hint"] = "apk add python3"
|
|
87
|
+
else:
|
|
88
|
+
result["install_hint"] = "Install Python 3 using your package manager"
|
|
89
|
+
elif "darwin" in system:
|
|
90
|
+
result["install_hint"] = "brew install python3"
|
|
91
|
+
elif "windows" in system:
|
|
92
|
+
result["install_hint"] = "Download from https://python.org or: winget install Python.Python.3"
|
|
93
|
+
else:
|
|
94
|
+
result["install_hint"] = "Download from https://python.org"
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def make_confirm_callback(user_settings: dict):
|
|
100
|
+
"""Create a confirmation callback that respects user settings."""
|
|
101
|
+
def confirm_action(message: str) -> bool:
|
|
102
|
+
"""Ask user to confirm an action (respects auto-approve settings)."""
|
|
103
|
+
# Extract tool name from message like "Execute write_file(...)?"
|
|
104
|
+
tool_name = None
|
|
105
|
+
if "write_file" in message:
|
|
106
|
+
tool_name = "write_file"
|
|
107
|
+
elif "bash" in message:
|
|
108
|
+
tool_name = "bash"
|
|
109
|
+
|
|
110
|
+
# Check if auto-approved
|
|
111
|
+
if tool_name and should_auto_approve(tool_name, user_settings):
|
|
112
|
+
console.print(f"[dim][auto-approved][/]")
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
return Confirm.ask(f"[yellow]{message}[/]", default=True)
|
|
116
|
+
|
|
117
|
+
return confirm_action
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class KairoCode:
|
|
121
|
+
"""Main Kairo Code application."""
|
|
122
|
+
|
|
123
|
+
def __init__(self):
|
|
124
|
+
self.config = Config()
|
|
125
|
+
self.llm = LLM()
|
|
126
|
+
|
|
127
|
+
# Check Python availability BEFORE creating tools (BashTool reads KAIRO_PYTHON_CMD)
|
|
128
|
+
self.python_status = check_python()
|
|
129
|
+
if self.python_status["available"]:
|
|
130
|
+
import os
|
|
131
|
+
os.environ["KAIRO_PYTHON_CMD"] = self.python_status["command"]
|
|
132
|
+
|
|
133
|
+
self.tools = create_default_registry()
|
|
134
|
+
self.conversation = Conversation()
|
|
135
|
+
self.router = Router(self.llm)
|
|
136
|
+
|
|
137
|
+
# Load user settings
|
|
138
|
+
self.user_settings = load_user_settings()
|
|
139
|
+
|
|
140
|
+
# Initialize sandbox for safe file operations
|
|
141
|
+
self.sandbox = init_sandbox()
|
|
142
|
+
|
|
143
|
+
# Create confirmation callback that respects user settings
|
|
144
|
+
self.confirm_callback = make_confirm_callback(self.user_settings)
|
|
145
|
+
|
|
146
|
+
# Create read-only registry for non-execution agents
|
|
147
|
+
self.readonly_tools = create_readonly_registry()
|
|
148
|
+
|
|
149
|
+
# Initialize agents with appropriate tool registries
|
|
150
|
+
self.agents = {
|
|
151
|
+
# Core agents
|
|
152
|
+
"explore": ExplorerAgent(self.llm, self.readonly_tools),
|
|
153
|
+
"code": CoderAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
154
|
+
"plan": PlannerAgent(self.llm, self.readonly_tools),
|
|
155
|
+
# Specialist agents (read-only analysis)
|
|
156
|
+
"security": SecurityAgent(self.llm, self.readonly_tools),
|
|
157
|
+
"review": ReviewerAgent(self.llm, self.readonly_tools),
|
|
158
|
+
"audit": AuditAgent(self.llm, self.readonly_tools),
|
|
159
|
+
"uiux": UiUxAgent(self.llm, self.readonly_tools),
|
|
160
|
+
"guardian": GuardianAgent(self.llm, self.readonly_tools),
|
|
161
|
+
# Specialist agents (can write files)
|
|
162
|
+
"test": TestingAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
163
|
+
"docs": DocsAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
164
|
+
"architect": ArchitectAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
165
|
+
"database": DatabaseAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
166
|
+
"terraform": TerraformAgent(self.llm, self.tools, on_confirm=self.confirm_callback),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Output formatter for clean display
|
|
170
|
+
self.formatter = OutputFormatter()
|
|
171
|
+
|
|
172
|
+
def print_welcome(self) -> None:
|
|
173
|
+
"""Print welcome banner in Claude Code style."""
|
|
174
|
+
sandbox_path = str(self.sandbox.root)
|
|
175
|
+
|
|
176
|
+
# Get resolved model names (handles "auto" selection)
|
|
177
|
+
try:
|
|
178
|
+
coder_model = self.llm.select_model("coder")
|
|
179
|
+
router_model = self.llm.select_model("router")
|
|
180
|
+
except RuntimeError as e:
|
|
181
|
+
console.print(f"[red]Error: {e}[/]")
|
|
182
|
+
coder_model = "none"
|
|
183
|
+
router_model = "none"
|
|
184
|
+
|
|
185
|
+
print_welcome_banner(coder_model, router_model, sandbox_path)
|
|
186
|
+
|
|
187
|
+
# Check backend connectivity
|
|
188
|
+
if not self.llm.check_health():
|
|
189
|
+
console.print("[yellow]Warning: Kairo backend unreachable. Check your connection or API key.[/]")
|
|
190
|
+
console.print(f"[dim]Endpoint: {self.config.cloud_endpoint}[/]\n")
|
|
191
|
+
|
|
192
|
+
def handle_command(self, user_input: str) -> bool:
|
|
193
|
+
"""
|
|
194
|
+
Handle special commands.
|
|
195
|
+
Returns True if command was handled.
|
|
196
|
+
"""
|
|
197
|
+
parts = user_input.strip().split(maxsplit=1)
|
|
198
|
+
cmd = parts[0].lower()
|
|
199
|
+
arg = parts[1] if len(parts) > 1 else ""
|
|
200
|
+
|
|
201
|
+
if cmd in ("/exit", "/quit", "/q"):
|
|
202
|
+
console.print("[dim]Goodbye![/]")
|
|
203
|
+
sys.exit(0)
|
|
204
|
+
|
|
205
|
+
if cmd == "/clear":
|
|
206
|
+
self.conversation.clear()
|
|
207
|
+
console.print("[dim]Conversation cleared.[/]")
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
if cmd == "/help":
|
|
211
|
+
self._print_help()
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
if cmd == "/login":
|
|
215
|
+
from kairo_code.auth import authenticate, clear_credentials
|
|
216
|
+
clear_credentials()
|
|
217
|
+
key = authenticate(self.config.cloud_endpoint)
|
|
218
|
+
if key:
|
|
219
|
+
Config._instance = None
|
|
220
|
+
self.config = Config()
|
|
221
|
+
console.print("[green]Re-authenticated successfully.[/]")
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
if cmd == "/logout":
|
|
225
|
+
from kairo_code.auth import clear_credentials
|
|
226
|
+
clear_credentials()
|
|
227
|
+
console.print("[dim]Logged out. Restart to re-authenticate.[/]")
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
if cmd == "/whoami":
|
|
231
|
+
from kairo_code.auth import load_credentials
|
|
232
|
+
creds = load_credentials()
|
|
233
|
+
if creds:
|
|
234
|
+
console.print(f"[cyan]{creds.get('email', 'Unknown')}[/] ({creds.get('plan', 'unknown')} plan)")
|
|
235
|
+
else:
|
|
236
|
+
console.print("[dim]Not authenticated.[/]")
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
if cmd == "/explore":
|
|
240
|
+
task = arg or "Explore this codebase and provide a summary"
|
|
241
|
+
self._run_agent("explore", task)
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
if cmd == "/code":
|
|
245
|
+
if not arg:
|
|
246
|
+
console.print("[red]Usage: /code <task>[/]")
|
|
247
|
+
return True
|
|
248
|
+
self._run_agent("code", arg)
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
if cmd == "/plan":
|
|
252
|
+
if not arg:
|
|
253
|
+
console.print("[red]Usage: /plan <task>[/]")
|
|
254
|
+
return True
|
|
255
|
+
self._run_agent("plan", arg)
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
# Specialist agent commands
|
|
259
|
+
specialist_cmds = {
|
|
260
|
+
"/security": ("security", "Run a security audit"),
|
|
261
|
+
"/review": ("review", "Review code for quality issues"),
|
|
262
|
+
"/audit": ("audit", "Comprehensive codebase health assessment"),
|
|
263
|
+
"/test": ("test", "Generate or review tests"),
|
|
264
|
+
"/uiux": ("uiux", "Evaluate UI/UX and accessibility"),
|
|
265
|
+
"/guardian": ("guardian", "Verify architectural alignment"),
|
|
266
|
+
"/docs": ("docs", "Create or review documentation"),
|
|
267
|
+
"/architect": ("architect", "Design modular architecture"),
|
|
268
|
+
"/database": ("database", "Database design and optimization"),
|
|
269
|
+
"/terraform": ("terraform", "Generate Terraform IaC"),
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if cmd in specialist_cmds:
|
|
273
|
+
agent_key, desc = specialist_cmds[cmd]
|
|
274
|
+
task = arg or f"{desc} for this project"
|
|
275
|
+
self._run_agent(agent_key, task)
|
|
276
|
+
return True
|
|
277
|
+
|
|
278
|
+
if cmd == "/search":
|
|
279
|
+
if not arg:
|
|
280
|
+
console.print("[red]Usage: /search <query>[/]")
|
|
281
|
+
return True
|
|
282
|
+
self._do_search(arg)
|
|
283
|
+
return True
|
|
284
|
+
|
|
285
|
+
if cmd == "/read":
|
|
286
|
+
if not arg:
|
|
287
|
+
console.print("[red]Usage: /read <file>[/]")
|
|
288
|
+
return True
|
|
289
|
+
self._read_file(arg)
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
if cmd == "/tree":
|
|
293
|
+
self._show_tree(arg or ".")
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
if cmd == "/models":
|
|
297
|
+
self._show_models()
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
if cmd == "/logs":
|
|
301
|
+
self._show_logs(arg)
|
|
302
|
+
return True
|
|
303
|
+
|
|
304
|
+
if cmd == "/settings":
|
|
305
|
+
self._show_settings()
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
if cmd == "/history":
|
|
309
|
+
self._show_history()
|
|
310
|
+
return True
|
|
311
|
+
|
|
312
|
+
if cmd == "/resume":
|
|
313
|
+
if not arg:
|
|
314
|
+
console.print("[red]Usage: /resume <conversation-id>[/]")
|
|
315
|
+
return True
|
|
316
|
+
self._resume_conversation(arg)
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
return False
|
|
320
|
+
|
|
321
|
+
def _print_help(self) -> None:
|
|
322
|
+
"""Print help information."""
|
|
323
|
+
help_text = """
|
|
324
|
+
## How to Use
|
|
325
|
+
|
|
326
|
+
**Just describe what you want!** Kairo Code will automatically:
|
|
327
|
+
- Plan first if it's a complex task
|
|
328
|
+
- Then implement the solution
|
|
329
|
+
|
|
330
|
+
## Core Agents
|
|
331
|
+
|
|
332
|
+
| Agent | Description |
|
|
333
|
+
|-------|-------------|
|
|
334
|
+
| `/explore [task]` | Explore and analyze codebase |
|
|
335
|
+
| `/code <task>` | Write or modify code (skips auto-planning) |
|
|
336
|
+
| `/plan <task>` | Create a plan only (no implementation) |
|
|
337
|
+
|
|
338
|
+
## Specialist Agents
|
|
339
|
+
|
|
340
|
+
| Agent | Description |
|
|
341
|
+
|-------|-------------|
|
|
342
|
+
| `/security [task]` | Security audit — vulnerabilities, OWASP, secrets |
|
|
343
|
+
| `/review [task]` | Code review — bugs, quality, architecture |
|
|
344
|
+
| `/audit [task]` | Codebase health assessment (KEEP/REFACTOR/REWRITE) |
|
|
345
|
+
| `/test [task]` | Generate or review tests |
|
|
346
|
+
| `/uiux [task]` | UI/UX and accessibility evaluation |
|
|
347
|
+
| `/guardian [task]` | Verify architectural alignment |
|
|
348
|
+
| `/docs [task]` | Create or review documentation |
|
|
349
|
+
| `/architect [task]` | Design modular architecture |
|
|
350
|
+
| `/database [task]` | Schema design, query optimization |
|
|
351
|
+
| `/terraform [task]` | Generate Terraform infrastructure code |
|
|
352
|
+
|
|
353
|
+
## Utilities
|
|
354
|
+
|
|
355
|
+
| Command | Description |
|
|
356
|
+
|---------|-------------|
|
|
357
|
+
| `/search <query>` | Search the web |
|
|
358
|
+
| `/read <file>` | Read a file |
|
|
359
|
+
| `/tree [path]` | Show directory tree |
|
|
360
|
+
| `/models` | Show available models |
|
|
361
|
+
| `/history` | List saved conversations |
|
|
362
|
+
| `/resume <id>` | Resume a past conversation |
|
|
363
|
+
| `/settings` | Configure auto-approve |
|
|
364
|
+
| `/login` | Re-authenticate with Kairo |
|
|
365
|
+
| `/logout` | Clear saved credentials |
|
|
366
|
+
| `/whoami` | Show current account |
|
|
367
|
+
| `/clear` | Clear conversation |
|
|
368
|
+
| `/exit` | Exit Kairo Code |
|
|
369
|
+
|
|
370
|
+
## Examples
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
I want to create an app that tracks my tasks with a UI
|
|
374
|
+
/security audit the authentication module
|
|
375
|
+
/review check the code I wrote today
|
|
376
|
+
/test generate tests for the API endpoints
|
|
377
|
+
/architect refactor utils.py into modules
|
|
378
|
+
/terraform create an S3 bucket with versioning
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Tips
|
|
382
|
+
|
|
383
|
+
- Complex tasks (apps, APIs, UIs) → auto-plans first, then implements
|
|
384
|
+
- Simple tasks (fix bug, add function) → implements directly
|
|
385
|
+
- Use specialist agents for focused analysis (security, review, testing, etc.)
|
|
386
|
+
"""
|
|
387
|
+
console.print(Markdown(help_text))
|
|
388
|
+
|
|
389
|
+
def _run_agent(self, agent_name: str, task: str) -> None:
|
|
390
|
+
"""Run a specialized agent with clean event-based output."""
|
|
391
|
+
agent = self.agents.get(agent_name)
|
|
392
|
+
if not agent:
|
|
393
|
+
console.print(f"[red]Unknown agent: {agent_name}[/]")
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
# Claude Code style header
|
|
397
|
+
max_iter = self.config.max_iterations or 20
|
|
398
|
+
console.print(f"\n[bold cyan]▶ {agent_name.title()}[/]\n")
|
|
399
|
+
|
|
400
|
+
# Tool display names
|
|
401
|
+
tool_names = {
|
|
402
|
+
"read_file": "Read", "write_file": "Write", "edit_file": "Edit",
|
|
403
|
+
"list_files": "Glob", "search_files": "Grep", "tree": "Tree",
|
|
404
|
+
"bash": "Bash", "web_search": "WebSearch", "web_fetch": "WebFetch",
|
|
405
|
+
"git_status": "Git_Status", "git_diff": "Git_Diff",
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
full_response = ""
|
|
409
|
+
start_time = time.time()
|
|
410
|
+
tool_count = 0
|
|
411
|
+
spinner = None # Active Status spinner, if any
|
|
412
|
+
|
|
413
|
+
try:
|
|
414
|
+
for event in agent.run_events(task):
|
|
415
|
+
if event.type == "thinking":
|
|
416
|
+
# Show a spinner while waiting for LLM
|
|
417
|
+
if spinner:
|
|
418
|
+
spinner.stop()
|
|
419
|
+
spinner = Status(f"[dim]{event.content}[/]", console=console, spinner="dots")
|
|
420
|
+
spinner.start()
|
|
421
|
+
|
|
422
|
+
elif event.type == "tool_start":
|
|
423
|
+
if spinner:
|
|
424
|
+
spinner.stop()
|
|
425
|
+
spinner = None
|
|
426
|
+
tool_count += 1
|
|
427
|
+
# Show tool call with parameter preview and progress
|
|
428
|
+
display_name = tool_names.get(event.tool_name, event.tool_name)
|
|
429
|
+
param_preview = self._get_param_preview(event.tool_name, event.tool_params or {})
|
|
430
|
+
console.print(f"\n[dim][{tool_count}][/] [cyan]● {display_name}[/][dim]({param_preview})[/]")
|
|
431
|
+
|
|
432
|
+
elif event.type == "tool_result":
|
|
433
|
+
# Show result summary
|
|
434
|
+
if event.success:
|
|
435
|
+
# Truncate long results
|
|
436
|
+
content = event.content
|
|
437
|
+
if len(content) > 200:
|
|
438
|
+
lines = content.split('\n')
|
|
439
|
+
if len(lines) > 3:
|
|
440
|
+
content = '\n'.join(lines[:3]) + f"\n ... +{len(lines)-3} more lines"
|
|
441
|
+
else:
|
|
442
|
+
content = content[:200] + "..."
|
|
443
|
+
console.print(f" [dim]⎿ {content}[/]")
|
|
444
|
+
else:
|
|
445
|
+
console.print(f" [red]⎿ Error: {event.content[:150]}[/]")
|
|
446
|
+
|
|
447
|
+
elif event.type == "text":
|
|
448
|
+
if spinner:
|
|
449
|
+
spinner.stop()
|
|
450
|
+
spinner = None
|
|
451
|
+
# Final response - print as a clean block
|
|
452
|
+
full_response = event.content
|
|
453
|
+
if event.content.strip():
|
|
454
|
+
console.print() # Blank line before text
|
|
455
|
+
console.print(Markdown(event.content))
|
|
456
|
+
|
|
457
|
+
elif event.type == "error":
|
|
458
|
+
if spinner:
|
|
459
|
+
spinner.stop()
|
|
460
|
+
spinner = None
|
|
461
|
+
console.print(f"\n[red]⚠ {event.content}[/]")
|
|
462
|
+
|
|
463
|
+
elif event.type == "done":
|
|
464
|
+
if spinner:
|
|
465
|
+
spinner.stop()
|
|
466
|
+
spinner = None
|
|
467
|
+
elapsed = time.time() - start_time
|
|
468
|
+
if elapsed < 60:
|
|
469
|
+
console.print(f"\n[dim]✻ Completed in {elapsed:.1f}s[/]")
|
|
470
|
+
else:
|
|
471
|
+
mins = int(elapsed // 60)
|
|
472
|
+
secs = int(elapsed % 60)
|
|
473
|
+
console.print(f"\n[dim]✻ Completed in {mins}m {secs}s[/]")
|
|
474
|
+
|
|
475
|
+
except KeyboardInterrupt:
|
|
476
|
+
if spinner:
|
|
477
|
+
spinner.stop()
|
|
478
|
+
console.print("\n[yellow]⚠ Interrupted[/]")
|
|
479
|
+
|
|
480
|
+
# Add to conversation history
|
|
481
|
+
self.conversation.add_user(f"[{agent_name}] {task}")
|
|
482
|
+
self.conversation.add_assistant(full_response)
|
|
483
|
+
|
|
484
|
+
def _get_param_preview(self, tool_name: str, params: dict) -> str:
|
|
485
|
+
"""Get a concise parameter preview for tool display."""
|
|
486
|
+
return self.formatter._get_param_preview(tool_name, params)
|
|
487
|
+
|
|
488
|
+
def _do_search(self, query: str) -> None:
|
|
489
|
+
"""Perform a web search and display results."""
|
|
490
|
+
from .tools import web_search, format_search_results
|
|
491
|
+
|
|
492
|
+
console.print(f"[cyan]Searching: {query}[/]\n")
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
results = web_search(query)
|
|
496
|
+
formatted = format_search_results(results)
|
|
497
|
+
console.print(Markdown(formatted))
|
|
498
|
+
except Exception as e:
|
|
499
|
+
console.print(f"[red]Search error: {e}[/]")
|
|
500
|
+
|
|
501
|
+
def _read_file(self, path: str) -> None:
|
|
502
|
+
"""Read and display a file."""
|
|
503
|
+
from .tools import read_file
|
|
504
|
+
|
|
505
|
+
try:
|
|
506
|
+
content = read_file(path)
|
|
507
|
+
console.print(Markdown(f"```\n{content}\n```"))
|
|
508
|
+
except Exception as e:
|
|
509
|
+
console.print(f"[red]Error: {e}[/]")
|
|
510
|
+
|
|
511
|
+
def _show_tree(self, path: str) -> None:
|
|
512
|
+
"""Show directory tree."""
|
|
513
|
+
from .tools import tree
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
output = tree(path)
|
|
517
|
+
console.print(output)
|
|
518
|
+
except Exception as e:
|
|
519
|
+
console.print(f"[red]Error: {e}[/]")
|
|
520
|
+
|
|
521
|
+
def _show_models(self) -> None:
|
|
522
|
+
"""Show available Ollama models."""
|
|
523
|
+
try:
|
|
524
|
+
models = self.llm.list_models()
|
|
525
|
+
coder = self.llm.select_model("coder")
|
|
526
|
+
router = self.llm.select_model("router")
|
|
527
|
+
console.print("[bold]Available Models:[/]")
|
|
528
|
+
for model in models:
|
|
529
|
+
marker = ""
|
|
530
|
+
if model == coder or coder in model:
|
|
531
|
+
marker = " [cyan](coder)[/]"
|
|
532
|
+
elif model == router or router in model:
|
|
533
|
+
marker = " [yellow](router)[/]"
|
|
534
|
+
console.print(f" - {model}{marker}")
|
|
535
|
+
except Exception as e:
|
|
536
|
+
console.print(f"[red]Error listing models: {e}[/]")
|
|
537
|
+
|
|
538
|
+
def _show_logs(self, lines_arg: str = "") -> None:
|
|
539
|
+
"""Show recent log entries."""
|
|
540
|
+
from .logging_config import LOG_DIR
|
|
541
|
+
import subprocess
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
# Find most recent log file
|
|
545
|
+
log_files = sorted(LOG_DIR.glob("kairo_*.log"), reverse=True)
|
|
546
|
+
if not log_files:
|
|
547
|
+
console.print("[yellow]No log files found.[/]")
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
current_log = log_files[0]
|
|
551
|
+
lines = int(lines_arg) if lines_arg.isdigit() else 50
|
|
552
|
+
|
|
553
|
+
console.print(f"[bold]Recent logs ({current_log.name}):[/]")
|
|
554
|
+
result = subprocess.run(
|
|
555
|
+
["tail", "-n", str(lines), str(current_log)],
|
|
556
|
+
capture_output=True,
|
|
557
|
+
text=True
|
|
558
|
+
)
|
|
559
|
+
console.print(result.stdout)
|
|
560
|
+
console.print(f"\n[dim]Full log: {current_log}[/]")
|
|
561
|
+
except Exception as e:
|
|
562
|
+
console.print(f"[red]Error reading logs: {e}[/]")
|
|
563
|
+
|
|
564
|
+
def _show_settings(self) -> None:
|
|
565
|
+
"""Show and modify settings."""
|
|
566
|
+
self.user_settings = show_settings_menu()
|
|
567
|
+
# Update the confirm callback with new settings
|
|
568
|
+
self.confirm_callback = make_confirm_callback(self.user_settings)
|
|
569
|
+
# Update all agents that have confirmation callbacks
|
|
570
|
+
for agent in self.agents.values():
|
|
571
|
+
if hasattr(agent, 'on_confirm'):
|
|
572
|
+
agent.on_confirm = self.confirm_callback
|
|
573
|
+
|
|
574
|
+
def _show_history(self) -> None:
|
|
575
|
+
"""Show saved conversations."""
|
|
576
|
+
conversations = Conversation.list_saved()
|
|
577
|
+
if not conversations:
|
|
578
|
+
console.print("[dim]No saved conversations.[/]")
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
console.print("[bold]Saved Conversations:[/]\n")
|
|
582
|
+
for conv in conversations[:20]: # Show last 20
|
|
583
|
+
preview = conv["preview"] or "(empty)"
|
|
584
|
+
console.print(
|
|
585
|
+
f" [cyan]{conv['id']}[/] "
|
|
586
|
+
f"[dim]{conv['created_at'][:10]}[/] "
|
|
587
|
+
f"{conv['message_count']} msgs "
|
|
588
|
+
f"[dim]{preview}[/]"
|
|
589
|
+
)
|
|
590
|
+
console.print(f"\n[dim]Use /resume <id> to continue a conversation.[/]")
|
|
591
|
+
|
|
592
|
+
def _resume_conversation(self, conversation_id: str) -> None:
|
|
593
|
+
"""Resume a saved conversation."""
|
|
594
|
+
try:
|
|
595
|
+
self.conversation = Conversation.load(conversation_id)
|
|
596
|
+
count = len(self.conversation.messages)
|
|
597
|
+
console.print(f"[green]Resumed conversation {conversation_id} ({count} messages)[/]")
|
|
598
|
+
except FileNotFoundError:
|
|
599
|
+
console.print(f"[red]Conversation {conversation_id} not found.[/]")
|
|
600
|
+
except Exception as e:
|
|
601
|
+
console.print(f"[red]Error loading conversation: {e}[/]")
|
|
602
|
+
|
|
603
|
+
def _prompt_first_run_settings(self) -> None:
|
|
604
|
+
"""Prompt for settings on first run."""
|
|
605
|
+
if not self.user_settings["auto_approve"].get("prompted"):
|
|
606
|
+
self.user_settings = prompt_auto_approve_settings()
|
|
607
|
+
# Update the confirm callback with new settings
|
|
608
|
+
self.confirm_callback = make_confirm_callback(self.user_settings)
|
|
609
|
+
# Update all agents that have confirmation callbacks
|
|
610
|
+
for agent in self.agents.values():
|
|
611
|
+
if hasattr(agent, 'on_confirm'):
|
|
612
|
+
agent.on_confirm = self.confirm_callback
|
|
613
|
+
|
|
614
|
+
def _is_complex_task(self, task: str) -> bool:
|
|
615
|
+
"""Check if a task is complex enough to need planning first."""
|
|
616
|
+
task_lower = task.lower()
|
|
617
|
+
# Complex indicators
|
|
618
|
+
complex_keywords = [
|
|
619
|
+
"app", "application", "create", "build", "implement",
|
|
620
|
+
"multiple", "features", "ui", "interface", "api",
|
|
621
|
+
"database", "authentication", "with a", "that can",
|
|
622
|
+
"schedule", "daily", "weekly", "notifications"
|
|
623
|
+
]
|
|
624
|
+
# Count how many complex indicators are present
|
|
625
|
+
matches = sum(1 for kw in complex_keywords if kw in task_lower)
|
|
626
|
+
return matches >= 2 # 2 or more indicators = complex
|
|
627
|
+
|
|
628
|
+
def _run_autonomous(self, task: str) -> None:
|
|
629
|
+
"""Run autonomous workflow: plan if complex, then implement."""
|
|
630
|
+
if self._is_complex_task(task):
|
|
631
|
+
console.print("[dim]Complex task detected - planning first...[/]\n")
|
|
632
|
+
|
|
633
|
+
# Step 1: Plan
|
|
634
|
+
self._run_agent("plan", task)
|
|
635
|
+
|
|
636
|
+
# Step 2: Implement (pass the original task, coder will see the plan context)
|
|
637
|
+
console.print("\n[dim]Implementing plan...[/]\n")
|
|
638
|
+
self._run_agent("code", f"Implement the following task based on the plan above: {task}")
|
|
639
|
+
else:
|
|
640
|
+
# Simple task - just code directly
|
|
641
|
+
self._run_agent("code", task)
|
|
642
|
+
|
|
643
|
+
def chat(self, user_input: str) -> None:
|
|
644
|
+
"""Handle a regular chat message with smart routing."""
|
|
645
|
+
# Route the intent
|
|
646
|
+
routed = self.router.route(user_input)
|
|
647
|
+
console.print(f"[dim][{routed.intent.value}][/]")
|
|
648
|
+
|
|
649
|
+
# Handle based on intent
|
|
650
|
+
if routed.intent == Intent.SEARCH:
|
|
651
|
+
self._do_search(user_input)
|
|
652
|
+
return
|
|
653
|
+
|
|
654
|
+
if routed.intent == Intent.EXPLORE:
|
|
655
|
+
self._run_agent("explore", user_input)
|
|
656
|
+
return
|
|
657
|
+
|
|
658
|
+
if routed.intent == Intent.PLAN:
|
|
659
|
+
# Explicit /plan - just plan, don't implement
|
|
660
|
+
self._run_agent("plan", user_input)
|
|
661
|
+
return
|
|
662
|
+
|
|
663
|
+
if routed.intent == Intent.CODE:
|
|
664
|
+
# Autonomous: plan if complex, then code
|
|
665
|
+
self._run_autonomous(user_input)
|
|
666
|
+
return
|
|
667
|
+
|
|
668
|
+
if routed.intent == Intent.FILE_READ:
|
|
669
|
+
path = routed.extracted_params.get("path", "")
|
|
670
|
+
if path:
|
|
671
|
+
self._read_file(path)
|
|
672
|
+
else:
|
|
673
|
+
# Ask LLM to help find the file
|
|
674
|
+
self._run_agent("code", user_input)
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
if routed.intent == Intent.FILE_LIST:
|
|
678
|
+
pattern = routed.extracted_params.get("pattern", "**/*")
|
|
679
|
+
self._show_tree(".")
|
|
680
|
+
return
|
|
681
|
+
|
|
682
|
+
if routed.intent == Intent.FILE_WRITE:
|
|
683
|
+
self._run_agent("code", user_input)
|
|
684
|
+
return
|
|
685
|
+
|
|
686
|
+
# Default: regular chat
|
|
687
|
+
self.conversation.add_user(user_input)
|
|
688
|
+
messages = self.conversation.to_messages()
|
|
689
|
+
self._stream_response(messages)
|
|
690
|
+
|
|
691
|
+
def _stream_response(self, messages: list[dict], model: str | None = None) -> None:
|
|
692
|
+
"""Stream and display LLM response with a thinking spinner."""
|
|
693
|
+
full_response = ""
|
|
694
|
+
first_chunk = True
|
|
695
|
+
spinner = Status("[dim]Thinking...[/]", console=console, spinner="dots")
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
spinner.start()
|
|
699
|
+
|
|
700
|
+
with Live(console=console, refresh_per_second=10, auto_refresh=False) as live:
|
|
701
|
+
buffer = ""
|
|
702
|
+
for chunk in self.llm.chat(messages, model=model, stream=True):
|
|
703
|
+
if first_chunk:
|
|
704
|
+
spinner.stop()
|
|
705
|
+
first_chunk = False
|
|
706
|
+
buffer += chunk
|
|
707
|
+
full_response += chunk
|
|
708
|
+
live.update(Markdown(buffer))
|
|
709
|
+
live.refresh()
|
|
710
|
+
|
|
711
|
+
if first_chunk:
|
|
712
|
+
spinner.stop()
|
|
713
|
+
|
|
714
|
+
self.conversation.add_assistant(full_response)
|
|
715
|
+
console.print()
|
|
716
|
+
|
|
717
|
+
except Exception as e:
|
|
718
|
+
if first_chunk:
|
|
719
|
+
spinner.stop()
|
|
720
|
+
console.print(f"[red]Error: {e}[/]")
|
|
721
|
+
|
|
722
|
+
def run(self) -> None:
|
|
723
|
+
"""Main run loop."""
|
|
724
|
+
self.print_welcome()
|
|
725
|
+
|
|
726
|
+
# Prompt for auto-approve settings on first run
|
|
727
|
+
self._prompt_first_run_settings()
|
|
728
|
+
|
|
729
|
+
# Authenticate with Kairo cloud
|
|
730
|
+
from kairo_code.auth import ensure_authenticated
|
|
731
|
+
api_key = self.config.cloud_api_key
|
|
732
|
+
if not api_key or api_key == "not-needed":
|
|
733
|
+
api_key = ensure_authenticated(self.config.cloud_endpoint)
|
|
734
|
+
if not api_key:
|
|
735
|
+
console.print("[red]Authentication required to use Kairo Code.[/]")
|
|
736
|
+
console.print("[dim]Visit https://app.kaironlabs.io/pricing to upgrade to Max plan.[/]")
|
|
737
|
+
sys.exit(1)
|
|
738
|
+
# Reset config singleton so the new key is picked up
|
|
739
|
+
Config._instance = None
|
|
740
|
+
self.config = Config()
|
|
741
|
+
|
|
742
|
+
# Check if models were resolved (auto-selection already handles this, but warn if no models)
|
|
743
|
+
try:
|
|
744
|
+
coder_model = self.llm.select_model("coder")
|
|
745
|
+
if not self.llm.check_model(coder_model):
|
|
746
|
+
console.print(f"[yellow]Warning: Model {coder_model} not found.[/]")
|
|
747
|
+
console.print(f"[yellow]Run: ollama pull {coder_model}[/]\n")
|
|
748
|
+
except RuntimeError as e:
|
|
749
|
+
console.print(f"[red]{e}[/]")
|
|
750
|
+
console.print("[yellow]Run: ollama pull llama3 to download a model[/]\n")
|
|
751
|
+
|
|
752
|
+
# Check if Python is available
|
|
753
|
+
if not self.python_status["available"]:
|
|
754
|
+
console.print("[yellow]Warning: Python not found on this system.[/]")
|
|
755
|
+
console.print(f"[yellow]Install: {self.python_status['install_hint']}[/]")
|
|
756
|
+
console.print("[dim]Python is required to run generated scripts.[/]\n")
|
|
757
|
+
|
|
758
|
+
# Start heartbeat if API key is configured
|
|
759
|
+
self._heartbeat = None
|
|
760
|
+
api_key = self.config.cloud_api_key
|
|
761
|
+
if api_key and api_key != "not-needed":
|
|
762
|
+
agent_id = self.config.get("cloud.agent_id", "")
|
|
763
|
+
if agent_id:
|
|
764
|
+
self._heartbeat = Heartbeat(api_key, agent_id)
|
|
765
|
+
self._heartbeat.start()
|
|
766
|
+
|
|
767
|
+
# Setup prompt with history
|
|
768
|
+
history_file = Path.home() / ".kairo_code_history"
|
|
769
|
+
session = PromptSession(history=FileHistory(str(history_file)))
|
|
770
|
+
|
|
771
|
+
while True:
|
|
772
|
+
try:
|
|
773
|
+
user_input = session.prompt("\n> ").strip()
|
|
774
|
+
|
|
775
|
+
if not user_input:
|
|
776
|
+
continue
|
|
777
|
+
|
|
778
|
+
# Check for commands
|
|
779
|
+
if user_input.startswith("/"):
|
|
780
|
+
if self.handle_command(user_input):
|
|
781
|
+
continue
|
|
782
|
+
|
|
783
|
+
# Regular chat with smart routing
|
|
784
|
+
self.chat(user_input)
|
|
785
|
+
|
|
786
|
+
except KeyboardInterrupt:
|
|
787
|
+
console.print("\n[dim]Use /exit to quit[/]")
|
|
788
|
+
except EOFError:
|
|
789
|
+
break
|
|
790
|
+
except Exception as e:
|
|
791
|
+
console.print(f"[red]Error: {e}[/]")
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def main():
|
|
795
|
+
"""Entry point."""
|
|
796
|
+
global logger
|
|
797
|
+
|
|
798
|
+
# Initialize logging first
|
|
799
|
+
logger = setup_logging()
|
|
800
|
+
logger.info("Kairo Code starting up...")
|
|
801
|
+
|
|
802
|
+
try:
|
|
803
|
+
logger.info("Initializing Kairo Code application...")
|
|
804
|
+
app = KairoCode()
|
|
805
|
+
logger.info("Starting main run loop...")
|
|
806
|
+
app.run()
|
|
807
|
+
except KeyboardInterrupt:
|
|
808
|
+
logger.info("User interrupted with Ctrl+C")
|
|
809
|
+
console.print("\n[dim]Goodbye![/]")
|
|
810
|
+
except Exception as e:
|
|
811
|
+
logger.exception(f"Fatal error: {e}")
|
|
812
|
+
console.print(f"[red]Fatal error: {e}[/]")
|
|
813
|
+
console.print(f"[dim]Check logs at: {logger.log_file}[/]")
|
|
814
|
+
sys.exit(1)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
if __name__ == "__main__":
|
|
818
|
+
main()
|