quasar-ai 1.0.0__tar.gz

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.
Files changed (39) hide show
  1. quasar_ai-1.0.0/LICENSE +21 -0
  2. quasar_ai-1.0.0/PKG-INFO +126 -0
  3. quasar_ai-1.0.0/README.md +84 -0
  4. quasar_ai-1.0.0/backend/__init__.py +5 -0
  5. quasar_ai-1.0.0/backend/cli.py +248 -0
  6. quasar_ai-1.0.0/backend/logging_config.py +199 -0
  7. quasar_ai-1.0.0/backend/main.py +63 -0
  8. quasar_ai-1.0.0/backend/routers/__init__.py +1 -0
  9. quasar_ai-1.0.0/backend/routers/agent.py +489 -0
  10. quasar_ai-1.0.0/backend/routers/execute.py +188 -0
  11. quasar_ai-1.0.0/backend/routers/files.py +313 -0
  12. quasar_ai-1.0.0/backend/routers/terminal.py +389 -0
  13. quasar_ai-1.0.0/backend/services/__init__.py +1 -0
  14. quasar_ai-1.0.0/backend/services/agent/__init__.py +2 -0
  15. quasar_ai-1.0.0/backend/services/agent/config.py +213 -0
  16. quasar_ai-1.0.0/backend/services/agent/context/__init__.py +33 -0
  17. quasar_ai-1.0.0/backend/services/agent/context/manager.py +303 -0
  18. quasar_ai-1.0.0/backend/services/agent/context/summarizer.py +114 -0
  19. quasar_ai-1.0.0/backend/services/agent/logger.py +156 -0
  20. quasar_ai-1.0.0/backend/services/agent/models/__init__.py +4 -0
  21. quasar_ai-1.0.0/backend/services/agent/models/credentials.py +249 -0
  22. quasar_ai-1.0.0/backend/services/agent/models/providers.py +214 -0
  23. quasar_ai-1.0.0/backend/services/agent/models/router.py +196 -0
  24. quasar_ai-1.0.0/backend/services/agent/orchestrator.py +1489 -0
  25. quasar_ai-1.0.0/backend/services/agent/specialists/__init__.py +357 -0
  26. quasar_ai-1.0.0/backend/services/agent/tools/__init__.py +98 -0
  27. quasar_ai-1.0.0/backend/services/agent/tools/executor.py +332 -0
  28. quasar_ai-1.0.0/backend/services/agent/tools/file_tools.py +757 -0
  29. quasar_ai-1.0.0/backend/services/agent/tools/terminal_tools.py +322 -0
  30. quasar_ai-1.0.0/backend/services/agent/tools/web_tools.py +130 -0
  31. quasar_ai-1.0.0/backend/test_endpoints.py +45 -0
  32. quasar_ai-1.0.0/pyproject.toml +68 -0
  33. quasar_ai-1.0.0/quasar_ai.egg-info/PKG-INFO +126 -0
  34. quasar_ai-1.0.0/quasar_ai.egg-info/SOURCES.txt +37 -0
  35. quasar_ai-1.0.0/quasar_ai.egg-info/dependency_links.txt +1 -0
  36. quasar_ai-1.0.0/quasar_ai.egg-info/entry_points.txt +2 -0
  37. quasar_ai-1.0.0/quasar_ai.egg-info/requires.txt +18 -0
  38. quasar_ai-1.0.0/quasar_ai.egg-info/top_level.txt +1 -0
  39. quasar_ai-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sunil Kumawat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: quasar-ai
3
+ Version: 1.0.0
4
+ Summary: 🚀 QUASAR - AI-powered CLI code editor with agentic capabilities
5
+ Author: Sunil Kumawat
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sunilkumawat/quasar-ai
8
+ Project-URL: Documentation, https://github.com/sunilkumawat/quasar-ai#readme
9
+ Project-URL: Repository, https://github.com/sunilkumawat/quasar-ai
10
+ Keywords: ai,cli,code-editor,llm,agent,langchain
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Code Generators
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: typer>=0.9.0
25
+ Requires-Dist: rich>=13.0.0
26
+ Requires-Dist: fastapi>=0.109.0
27
+ Requires-Dist: uvicorn[standard]>=0.27.0
28
+ Requires-Dist: python-multipart>=0.0.6
29
+ Requires-Dist: pydantic>=2.5.0
30
+ Requires-Dist: python-dotenv>=1.0.0
31
+ Requires-Dist: langchain>=0.1.0
32
+ Requires-Dist: langchain-core>=0.1.0
33
+ Requires-Dist: langchain-openai>=0.0.5
34
+ Requires-Dist: langchain-groq>=0.0.1
35
+ Requires-Dist: langchain-ollama>=0.0.1
36
+ Requires-Dist: websockets>=12.0
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=7.0; extra == "dev"
39
+ Requires-Dist: build; extra == "dev"
40
+ Requires-Dist: twine; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # 🚀 QUASAR - AI-Powered CLI Code Editor
44
+
45
+ An intelligent command-line assistant that can understand your codebase, generate code, fix bugs, and execute tasks using AI.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install quasar-ai
51
+ ```
52
+
53
+ ## Setup API Keys
54
+
55
+ **IMPORTANT**: You must provide your own API keys. QUASAR does not include any API keys.
56
+
57
+ Set at least ONE of these environment variables:
58
+
59
+ ```bash
60
+ # Groq (recommended - fast inference)
61
+ export GROQ_API_KEY_1="gsk_your_key_here"
62
+
63
+ # Cerebras
64
+ export CEREBRAS_API_KEY_1="csk_your_key_here"
65
+
66
+ # OpenAI
67
+ export OPENAI_API_KEY="sk-your_key_here"
68
+ ```
69
+
70
+ **Get free API keys:**
71
+ - Groq: https://console.groq.com
72
+ - Cerebras: https://cloud.cerebras.ai
73
+ - OpenAI: https://platform.openai.com
74
+
75
+ ### Multiple Keys (Optional)
76
+ For higher rate limits, add multiple keys per provider:
77
+ ```bash
78
+ export GROQ_API_KEY_1="gsk_..."
79
+ export GROQ_API_KEY_2="gsk_..."
80
+ ```
81
+
82
+ ## Usage
83
+
84
+ ### Interactive Mode (REPL)
85
+ ```bash
86
+ quasar
87
+ # or
88
+ quasar --interactive
89
+ ```
90
+
91
+ ### Single Command
92
+ ```bash
93
+ quasar "create a hello.py file that prints Hello World"
94
+ quasar "explain main.py"
95
+ quasar "fix the bug in utils.py"
96
+ quasar "list files in current directory"
97
+ ```
98
+
99
+ ### Specify Workspace
100
+ ```bash
101
+ quasar --workspace /path/to/project "add tests for api.py"
102
+ ```
103
+
104
+ ## Supported Tasks
105
+
106
+ QUASAR automatically classifies your request and uses the best model:
107
+
108
+ | Task | Example |
109
+ |------|---------|
110
+ | Chat | "What is machine learning?" |
111
+ | Code Generation | "Create a REST API endpoint" |
112
+ | Bug Fixing | "Fix the TypeError in app.py" |
113
+ | Code Explanation | "Explain this function" |
114
+ | Refactoring | "Improve the structure of utils.py" |
115
+ | Documentation | "Add docstrings to main.py" |
116
+ | Test Generation | "Write tests for calculator.py" |
117
+
118
+ ## Updating
119
+
120
+ ```bash
121
+ pip install --upgrade quasar-ai
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT License
@@ -0,0 +1,84 @@
1
+ # 🚀 QUASAR - AI-Powered CLI Code Editor
2
+
3
+ An intelligent command-line assistant that can understand your codebase, generate code, fix bugs, and execute tasks using AI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install quasar-ai
9
+ ```
10
+
11
+ ## Setup API Keys
12
+
13
+ **IMPORTANT**: You must provide your own API keys. QUASAR does not include any API keys.
14
+
15
+ Set at least ONE of these environment variables:
16
+
17
+ ```bash
18
+ # Groq (recommended - fast inference)
19
+ export GROQ_API_KEY_1="gsk_your_key_here"
20
+
21
+ # Cerebras
22
+ export CEREBRAS_API_KEY_1="csk_your_key_here"
23
+
24
+ # OpenAI
25
+ export OPENAI_API_KEY="sk-your_key_here"
26
+ ```
27
+
28
+ **Get free API keys:**
29
+ - Groq: https://console.groq.com
30
+ - Cerebras: https://cloud.cerebras.ai
31
+ - OpenAI: https://platform.openai.com
32
+
33
+ ### Multiple Keys (Optional)
34
+ For higher rate limits, add multiple keys per provider:
35
+ ```bash
36
+ export GROQ_API_KEY_1="gsk_..."
37
+ export GROQ_API_KEY_2="gsk_..."
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Interactive Mode (REPL)
43
+ ```bash
44
+ quasar
45
+ # or
46
+ quasar --interactive
47
+ ```
48
+
49
+ ### Single Command
50
+ ```bash
51
+ quasar "create a hello.py file that prints Hello World"
52
+ quasar "explain main.py"
53
+ quasar "fix the bug in utils.py"
54
+ quasar "list files in current directory"
55
+ ```
56
+
57
+ ### Specify Workspace
58
+ ```bash
59
+ quasar --workspace /path/to/project "add tests for api.py"
60
+ ```
61
+
62
+ ## Supported Tasks
63
+
64
+ QUASAR automatically classifies your request and uses the best model:
65
+
66
+ | Task | Example |
67
+ |------|---------|
68
+ | Chat | "What is machine learning?" |
69
+ | Code Generation | "Create a REST API endpoint" |
70
+ | Bug Fixing | "Fix the TypeError in app.py" |
71
+ | Code Explanation | "Explain this function" |
72
+ | Refactoring | "Improve the structure of utils.py" |
73
+ | Documentation | "Add docstrings to main.py" |
74
+ | Test Generation | "Write tests for calculator.py" |
75
+
76
+ ## Updating
77
+
78
+ ```bash
79
+ pip install --upgrade quasar-ai
80
+ ```
81
+
82
+ ## License
83
+
84
+ MIT License
@@ -0,0 +1,5 @@
1
+ """
2
+ QUASAR AI - CLI Code Editor Package
3
+ """
4
+
5
+ __version__ = "1.0.0"
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ QUASAR CLI - Terminal-based AI Code Editor
4
+
5
+ Usage:
6
+ quasar "your prompt here" # Single command mode
7
+ quasar --interactive # REPL mode
8
+ quasar --help # Show help
9
+ """
10
+
11
+ import asyncio
12
+ import os
13
+ import sys
14
+ from pathlib import Path
15
+ from typing import Optional
16
+
17
+ import typer
18
+ from rich.console import Console
19
+ from rich.live import Live
20
+ from rich.markdown import Markdown
21
+ from rich.panel import Panel
22
+ from rich.progress import Progress, SpinnerColumn, TextColumn
23
+ from rich.syntax import Syntax
24
+ from rich.text import Text
25
+
26
+ # Add parent to path for imports
27
+ sys.path.insert(0, str(Path(__file__).parent))
28
+
29
+ from services.agent.orchestrator import Orchestrator
30
+ from services.agent.models import CredentialManager
31
+
32
+ app = typer.Typer(
33
+ name="quasar",
34
+ help="🚀 QUASAR - AI-powered CLI code editor",
35
+ add_completion=False,
36
+ )
37
+ console = Console()
38
+
39
+ # Global orchestrator
40
+ _orchestrator: Optional[Orchestrator] = None
41
+
42
+
43
+ def get_orchestrator() -> Orchestrator:
44
+ """Get or create orchestrator instance."""
45
+ global _orchestrator
46
+ if _orchestrator is None:
47
+ _orchestrator = Orchestrator()
48
+ return _orchestrator
49
+
50
+
51
+ def check_api_keys() -> bool:
52
+ """Check if any API keys are configured."""
53
+ cred_manager = CredentialManager()
54
+ status = cred_manager.get_status()
55
+
56
+ available = [name for name, info in status.items() if info.get("has_credentials")]
57
+
58
+ if not available or available == ["ollama"]:
59
+ console.print(Panel(
60
+ "[bold red]⚠️ No API keys found![/bold red]\n\n"
61
+ "Please set at least one of these environment variables:\n\n"
62
+ " [green]GROQ_API_KEY_1[/green]=gsk_...\n"
63
+ " [green]CEREBRAS_API_KEY_1[/green]=csk_...\n"
64
+ " [green]OPENAI_API_KEY_1[/green]=sk_...\n\n"
65
+ "You can add multiple keys: GROQ_API_KEY_2, etc.\n\n"
66
+ "[dim]Get API keys at:[/dim]\n"
67
+ " Groq: https://console.groq.com\n"
68
+ " Cerebras: https://cloud.cerebras.ai",
69
+ title="Setup Required",
70
+ border_style="red"
71
+ ))
72
+ return False
73
+
74
+ console.print(f"[dim]✓ Available providers: {', '.join(available)}[/dim]")
75
+ return True
76
+
77
+
78
+ async def process_query(query: str, workspace: str) -> None:
79
+ """Process a single query and stream the response."""
80
+ orchestrator = get_orchestrator()
81
+ orchestrator.set_workspace(workspace)
82
+
83
+ response_text = ""
84
+ current_tool = None
85
+
86
+ with Progress(
87
+ SpinnerColumn(),
88
+ TextColumn("[progress.description]{task.description}"),
89
+ console=console,
90
+ transient=True,
91
+ ) as progress:
92
+ task_id = progress.add_task("Thinking...", total=None)
93
+
94
+ try:
95
+ async for chunk in orchestrator.process_stream(query=query):
96
+ chunk_type = chunk.get("type", "")
97
+
98
+ if chunk_type == "classification":
99
+ task_type = chunk.get("task_type", "unknown")
100
+ progress.update(task_id, description=f"[cyan]Task: {task_type}[/cyan]")
101
+
102
+ elif chunk_type == "iteration":
103
+ current = chunk.get("current", 1)
104
+ max_iter = chunk.get("max", 30)
105
+ progress.update(task_id, description=f"[yellow]Iteration {current}/{max_iter}[/yellow]")
106
+
107
+ elif chunk_type == "tool_start":
108
+ tool_name = chunk.get("tool", "unknown")
109
+ current_tool = tool_name
110
+ progress.update(task_id, description=f"[blue]🔧 {tool_name}...[/blue]")
111
+
112
+ elif chunk_type == "tool_complete":
113
+ tool_name = chunk.get("tool", current_tool or "tool")
114
+ console.print(f" [green]✓[/green] {tool_name}")
115
+ current_tool = None
116
+
117
+ elif chunk_type == "message":
118
+ # Progress/observation messages
119
+ msg = chunk.get("content", "")
120
+ if msg:
121
+ progress.update(task_id, description=f"[dim]{msg[:60]}...[/dim]" if len(msg) > 60 else f"[dim]{msg}[/dim]")
122
+
123
+ elif chunk_type == "token":
124
+ # Streaming response text - collect it
125
+ token = chunk.get("content", "")
126
+ response_text += token
127
+ # Stop the spinner when we start getting response
128
+ if response_text and len(response_text) < 10:
129
+ progress.stop()
130
+
131
+ elif chunk_type == "error":
132
+ error_msg = chunk.get("message", "Unknown error")
133
+ console.print(f"[red]❌ Error: {error_msg}[/red]")
134
+ return
135
+
136
+ elif chunk_type == "done":
137
+ # Final summary
138
+ model = chunk.get("model", "unknown")
139
+ provider = chunk.get("provider", "unknown")
140
+ tools_used = chunk.get("tools_used", [])
141
+ tool_count = chunk.get("tool_calls_count", 0)
142
+
143
+ # Print the response
144
+ if response_text:
145
+ console.print()
146
+ console.print(Markdown(response_text))
147
+
148
+ # Print summary
149
+ console.print()
150
+ summary = f"[dim]Model: {provider}/{model}"
151
+ if tool_count > 0:
152
+ summary += f" | Tools: {tool_count}"
153
+ summary += "[/dim]"
154
+ console.print(summary)
155
+ return
156
+
157
+ except KeyboardInterrupt:
158
+ console.print("\n[yellow]Cancelled[/yellow]")
159
+ except Exception as e:
160
+ console.print(f"[red]Error: {e}[/red]")
161
+
162
+
163
+ def run_repl(workspace: str) -> None:
164
+ """Run interactive REPL mode."""
165
+ console.print(Panel(
166
+ "[bold cyan]🚀 QUASAR AI Editor[/bold cyan]\n\n"
167
+ f"[dim]Workspace: {workspace}[/dim]\n\n"
168
+ "Type your requests, or:\n"
169
+ " [green]/help[/green] - Show commands\n"
170
+ " [green]/quit[/green] - Exit\n"
171
+ " [green]/clear[/green] - Clear screen",
172
+ border_style="cyan"
173
+ ))
174
+
175
+ while True:
176
+ try:
177
+ console.print()
178
+ query = console.input("[bold green]>[/bold green] ").strip()
179
+
180
+ if not query:
181
+ continue
182
+
183
+ # Handle special commands
184
+ if query.lower() in ["/quit", "/exit", "/q"]:
185
+ console.print("[dim]Goodbye![/dim]")
186
+ break
187
+ elif query.lower() == "/clear":
188
+ console.clear()
189
+ continue
190
+ elif query.lower() == "/help":
191
+ console.print(Panel(
192
+ "[bold]Commands:[/bold]\n"
193
+ " /quit, /exit, /q - Exit REPL\n"
194
+ " /clear - Clear screen\n"
195
+ " /help - Show this help\n\n"
196
+ "[bold]Examples:[/bold]\n"
197
+ ' "Create a hello.py file"\n'
198
+ ' "Explain main.py"\n'
199
+ ' "Fix the bug in utils.py"\n'
200
+ ' "List files in current directory"',
201
+ title="Help",
202
+ border_style="blue"
203
+ ))
204
+ continue
205
+
206
+ # Process the query
207
+ asyncio.run(process_query(query, workspace))
208
+
209
+ except KeyboardInterrupt:
210
+ console.print("\n[dim]Use /quit to exit[/dim]")
211
+ except EOFError:
212
+ console.print("\n[dim]Goodbye![/dim]")
213
+ break
214
+
215
+
216
+ @app.command()
217
+ def main(
218
+ query: Optional[str] = typer.Argument(None, help="Query to process"),
219
+ interactive: bool = typer.Option(False, "--interactive", "-i", help="Run in interactive REPL mode"),
220
+ workspace: str = typer.Option(None, "--workspace", "-w", help="Workspace directory (default: current dir)"),
221
+ ):
222
+ """
223
+ 🚀 QUASAR - AI-powered CLI code editor
224
+
225
+ Examples:
226
+ quasar "create a hello.py file"
227
+ quasar "explain main.py"
228
+ quasar --interactive
229
+ """
230
+ # Set workspace
231
+ if workspace is None:
232
+ workspace = os.getcwd()
233
+ workspace = str(Path(workspace).resolve())
234
+
235
+ # Check for API keys
236
+ if not check_api_keys():
237
+ raise typer.Exit(1)
238
+
239
+ if interactive or query is None:
240
+ # REPL mode
241
+ run_repl(workspace)
242
+ else:
243
+ # Single command mode
244
+ asyncio.run(process_query(query, workspace))
245
+
246
+
247
+ if __name__ == "__main__":
248
+ app()
@@ -0,0 +1,199 @@
1
+ """
2
+ Global Logging Module for AI Code Editor Backend
3
+
4
+ Provides centralized logging for all backend components:
5
+ - API requests/responses
6
+ - File operations
7
+ - Terminal operations
8
+ - Agent operations
9
+ - WebSocket connections
10
+
11
+ All logs are written to: backend/logs/
12
+ """
13
+
14
+ import logging
15
+ import sys
16
+ import os
17
+ from pathlib import Path
18
+ from datetime import datetime
19
+ from functools import wraps
20
+ import time
21
+
22
+
23
+ # Create logs directory
24
+ LOGS_DIR = Path(__file__).parent / "logs"
25
+ LOGS_DIR.mkdir(exist_ok=True)
26
+
27
+ # Log files
28
+ MAIN_LOG = LOGS_DIR / f"backend_{datetime.now().strftime('%Y%m%d')}.log"
29
+ ERROR_LOG = LOGS_DIR / f"errors_{datetime.now().strftime('%Y%m%d')}.log"
30
+
31
+
32
+ def setup_logger(name: str, level: int = logging.DEBUG) -> logging.Logger:
33
+ """
34
+ Setup a logger with file and console handlers.
35
+
36
+ Args:
37
+ name: Logger name (e.g., 'files', 'terminal', 'agent')
38
+ level: Logging level
39
+
40
+ Returns:
41
+ Configured logger
42
+ """
43
+ logger = logging.getLogger(name)
44
+
45
+ # Avoid duplicate handlers
46
+ if logger.handlers:
47
+ return logger
48
+
49
+ logger.setLevel(level)
50
+
51
+ # File handler - detailed logs
52
+ file_handler = logging.FileHandler(MAIN_LOG, encoding='utf-8')
53
+ file_handler.setLevel(logging.DEBUG)
54
+ file_formatter = logging.Formatter(
55
+ '%(asctime)s | %(levelname)-8s | %(name)-15s | %(funcName)-20s | %(message)s',
56
+ datefmt='%Y-%m-%d %H:%M:%S'
57
+ )
58
+ file_handler.setFormatter(file_formatter)
59
+
60
+ # Error file handler - errors only
61
+ error_handler = logging.FileHandler(ERROR_LOG, encoding='utf-8')
62
+ error_handler.setLevel(logging.ERROR)
63
+ error_handler.setFormatter(file_formatter)
64
+
65
+ # Console handler - silent (CRITICAL only) to keep CLI clean
66
+ console_handler = logging.StreamHandler(sys.stdout)
67
+ console_handler.setLevel(logging.CRITICAL)
68
+ console_formatter = logging.Formatter(
69
+ '%(levelname)s: [%(name)s] %(message)s'
70
+ )
71
+ console_handler.setFormatter(console_formatter)
72
+
73
+ logger.addHandler(file_handler)
74
+ logger.addHandler(error_handler)
75
+ logger.addHandler(console_handler)
76
+
77
+ return logger
78
+
79
+
80
+ # Pre-configured loggers for each module
81
+ api_logger = setup_logger("api")
82
+ files_logger = setup_logger("files")
83
+ terminal_logger = setup_logger("terminal")
84
+ execute_logger = setup_logger("execute")
85
+ agent_logger = setup_logger("agent")
86
+ ws_logger = setup_logger("websocket")
87
+
88
+
89
+ def log_request(logger: logging.Logger):
90
+ """
91
+ Decorator to log API request/response.
92
+
93
+ Usage:
94
+ @log_request(api_logger)
95
+ async def my_endpoint():
96
+ ...
97
+ """
98
+ def decorator(func):
99
+ @wraps(func)
100
+ async def wrapper(*args, **kwargs):
101
+ start = time.time()
102
+ func_name = func.__name__
103
+
104
+ # Log request
105
+ logger.info(f"📥 Request: {func_name}")
106
+ if kwargs:
107
+ logger.debug(f"Parameters: {kwargs}")
108
+
109
+ try:
110
+ result = await func(*args, **kwargs)
111
+ elapsed = (time.time() - start) * 1000
112
+ logger.info(f"📤 Response: {func_name} ({elapsed:.0f}ms)")
113
+ return result
114
+
115
+ except Exception as e:
116
+ elapsed = (time.time() - start) * 1000
117
+ logger.error(f"❌ Error in {func_name} ({elapsed:.0f}ms): {type(e).__name__}: {e}")
118
+ raise
119
+
120
+ return wrapper
121
+ return decorator
122
+
123
+
124
+ def log_operation(logger: logging.Logger, operation: str):
125
+ """
126
+ Context manager for logging operations.
127
+
128
+ Usage:
129
+ with log_operation(files_logger, "read_file"):
130
+ contents = read_file(path)
131
+ """
132
+ class OperationLogger:
133
+ def __init__(self):
134
+ self.start = None
135
+
136
+ def __enter__(self):
137
+ self.start = time.time()
138
+ logger.debug(f"➡️ Starting: {operation}")
139
+ return self
140
+
141
+ def __exit__(self, exc_type, exc_val, exc_tb):
142
+ elapsed = (time.time() - self.start) * 1000
143
+ if exc_type:
144
+ logger.error(f"❌ Failed: {operation} ({elapsed:.0f}ms) - {exc_type.__name__}: {exc_val}")
145
+ else:
146
+ logger.debug(f"✅ Completed: {operation} ({elapsed:.0f}ms)")
147
+ return False
148
+
149
+ return OperationLogger()
150
+
151
+
152
+ # Convenience functions
153
+ def log_file_operation(operation: str, path: str, success: bool = True, error: str = None):
154
+ """Log file operation."""
155
+ if success:
156
+ files_logger.info(f"📁 {operation}: {path}")
157
+ else:
158
+ files_logger.error(f"❌ {operation} failed: {path} - {error}")
159
+
160
+
161
+ def log_terminal_command(command: str, exit_code: int = None):
162
+ """Log terminal command."""
163
+ if exit_code is None:
164
+ terminal_logger.info(f"💻 Running: {command[:100]}")
165
+ else:
166
+ status = "✅" if exit_code == 0 else "❌"
167
+ terminal_logger.info(f"{status} Command finished (exit {exit_code}): {command[:50]}")
168
+
169
+
170
+ def log_websocket_event(event: str, client_id: str = None, data: str = None):
171
+ """Log WebSocket event."""
172
+ ws_logger.info(f"🔌 WS {event}" + (f" [{client_id}]" if client_id else ""))
173
+ if data:
174
+ ws_logger.debug(f"Data: {data[:200]}")
175
+
176
+
177
+ def log_agent_event(event: str, details: str = None):
178
+ """Log agent event."""
179
+ agent_logger.info(f"🤖 {event}")
180
+ if details:
181
+ agent_logger.debug(f"Details: {details}")
182
+
183
+
184
+ # Export all
185
+ __all__ = [
186
+ "setup_logger",
187
+ "log_request",
188
+ "log_operation",
189
+ "api_logger",
190
+ "files_logger",
191
+ "terminal_logger",
192
+ "execute_logger",
193
+ "agent_logger",
194
+ "ws_logger",
195
+ "log_file_operation",
196
+ "log_terminal_command",
197
+ "log_websocket_event",
198
+ "log_agent_event"
199
+ ]