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.
- quasar_ai-1.0.0/LICENSE +21 -0
- quasar_ai-1.0.0/PKG-INFO +126 -0
- quasar_ai-1.0.0/README.md +84 -0
- quasar_ai-1.0.0/backend/__init__.py +5 -0
- quasar_ai-1.0.0/backend/cli.py +248 -0
- quasar_ai-1.0.0/backend/logging_config.py +199 -0
- quasar_ai-1.0.0/backend/main.py +63 -0
- quasar_ai-1.0.0/backend/routers/__init__.py +1 -0
- quasar_ai-1.0.0/backend/routers/agent.py +489 -0
- quasar_ai-1.0.0/backend/routers/execute.py +188 -0
- quasar_ai-1.0.0/backend/routers/files.py +313 -0
- quasar_ai-1.0.0/backend/routers/terminal.py +389 -0
- quasar_ai-1.0.0/backend/services/__init__.py +1 -0
- quasar_ai-1.0.0/backend/services/agent/__init__.py +2 -0
- quasar_ai-1.0.0/backend/services/agent/config.py +213 -0
- quasar_ai-1.0.0/backend/services/agent/context/__init__.py +33 -0
- quasar_ai-1.0.0/backend/services/agent/context/manager.py +303 -0
- quasar_ai-1.0.0/backend/services/agent/context/summarizer.py +114 -0
- quasar_ai-1.0.0/backend/services/agent/logger.py +156 -0
- quasar_ai-1.0.0/backend/services/agent/models/__init__.py +4 -0
- quasar_ai-1.0.0/backend/services/agent/models/credentials.py +249 -0
- quasar_ai-1.0.0/backend/services/agent/models/providers.py +214 -0
- quasar_ai-1.0.0/backend/services/agent/models/router.py +196 -0
- quasar_ai-1.0.0/backend/services/agent/orchestrator.py +1489 -0
- quasar_ai-1.0.0/backend/services/agent/specialists/__init__.py +357 -0
- quasar_ai-1.0.0/backend/services/agent/tools/__init__.py +98 -0
- quasar_ai-1.0.0/backend/services/agent/tools/executor.py +332 -0
- quasar_ai-1.0.0/backend/services/agent/tools/file_tools.py +757 -0
- quasar_ai-1.0.0/backend/services/agent/tools/terminal_tools.py +322 -0
- quasar_ai-1.0.0/backend/services/agent/tools/web_tools.py +130 -0
- quasar_ai-1.0.0/backend/test_endpoints.py +45 -0
- quasar_ai-1.0.0/pyproject.toml +68 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/PKG-INFO +126 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/SOURCES.txt +37 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/dependency_links.txt +1 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/entry_points.txt +2 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/requires.txt +18 -0
- quasar_ai-1.0.0/quasar_ai.egg-info/top_level.txt +1 -0
- quasar_ai-1.0.0/setup.cfg +4 -0
quasar_ai-1.0.0/LICENSE
ADDED
|
@@ -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.
|
quasar_ai-1.0.0/PKG-INFO
ADDED
|
@@ -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,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
|
+
]
|