groknroll 2.0.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.
- groknroll/__init__.py +36 -0
- groknroll/__main__.py +9 -0
- groknroll/agents/__init__.py +18 -0
- groknroll/agents/agent_manager.py +187 -0
- groknroll/agents/base_agent.py +118 -0
- groknroll/agents/build_agent.py +231 -0
- groknroll/agents/plan_agent.py +215 -0
- groknroll/cli/__init__.py +7 -0
- groknroll/cli/enhanced_cli.py +372 -0
- groknroll/cli/large_codebase_cli.py +413 -0
- groknroll/cli/main.py +331 -0
- groknroll/cli/rlm_commands.py +258 -0
- groknroll/clients/__init__.py +63 -0
- groknroll/clients/anthropic.py +112 -0
- groknroll/clients/azure_openai.py +142 -0
- groknroll/clients/base_lm.py +33 -0
- groknroll/clients/gemini.py +162 -0
- groknroll/clients/litellm.py +105 -0
- groknroll/clients/openai.py +129 -0
- groknroll/clients/portkey.py +94 -0
- groknroll/core/__init__.py +9 -0
- groknroll/core/agent.py +339 -0
- groknroll/core/comms_utils.py +264 -0
- groknroll/core/context.py +251 -0
- groknroll/core/exceptions.py +181 -0
- groknroll/core/large_codebase.py +564 -0
- groknroll/core/lm_handler.py +206 -0
- groknroll/core/rlm.py +446 -0
- groknroll/core/rlm_codebase.py +448 -0
- groknroll/core/rlm_integration.py +256 -0
- groknroll/core/types.py +276 -0
- groknroll/environments/__init__.py +34 -0
- groknroll/environments/base_env.py +182 -0
- groknroll/environments/constants.py +32 -0
- groknroll/environments/docker_repl.py +336 -0
- groknroll/environments/local_repl.py +388 -0
- groknroll/environments/modal_repl.py +502 -0
- groknroll/environments/prime_repl.py +588 -0
- groknroll/logger/__init__.py +4 -0
- groknroll/logger/rlm_logger.py +63 -0
- groknroll/logger/verbose.py +393 -0
- groknroll/operations/__init__.py +15 -0
- groknroll/operations/bash_ops.py +447 -0
- groknroll/operations/file_ops.py +473 -0
- groknroll/operations/git_ops.py +620 -0
- groknroll/oracle/__init__.py +11 -0
- groknroll/oracle/codebase_indexer.py +238 -0
- groknroll/oracle/oracle_agent.py +278 -0
- groknroll/setup.py +34 -0
- groknroll/storage/__init__.py +14 -0
- groknroll/storage/database.py +272 -0
- groknroll/storage/models.py +128 -0
- groknroll/utils/__init__.py +0 -0
- groknroll/utils/parsing.py +168 -0
- groknroll/utils/prompts.py +146 -0
- groknroll/utils/rlm_utils.py +19 -0
- groknroll-2.0.0.dist-info/METADATA +246 -0
- groknroll-2.0.0.dist-info/RECORD +62 -0
- groknroll-2.0.0.dist-info/WHEEL +5 -0
- groknroll-2.0.0.dist-info/entry_points.txt +3 -0
- groknroll-2.0.0.dist-info/licenses/LICENSE +21 -0
- groknroll-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plan Agent
|
|
3
|
+
|
|
4
|
+
Read-only agent for planning and analysis without making changes.
|
|
5
|
+
This agent can read files and analyze code but cannot modify anything.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from groknroll.agents.base_agent import (
|
|
13
|
+
BaseAgent, AgentConfig, AgentCapability, AgentResponse
|
|
14
|
+
)
|
|
15
|
+
from groknroll.core.rlm_integration import RLMIntegration, RLMConfig
|
|
16
|
+
from groknroll.operations.file_ops import FileOperations
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PlanAgent(BaseAgent):
|
|
20
|
+
"""
|
|
21
|
+
Plan Agent - Read-Only
|
|
22
|
+
|
|
23
|
+
Capabilities:
|
|
24
|
+
- Read files
|
|
25
|
+
- Analyze code
|
|
26
|
+
- Search code
|
|
27
|
+
|
|
28
|
+
Use for:
|
|
29
|
+
- Planning implementations
|
|
30
|
+
- Code reviews
|
|
31
|
+
- Architecture analysis
|
|
32
|
+
- Understanding codebases
|
|
33
|
+
- Generating documentation
|
|
34
|
+
|
|
35
|
+
CANNOT:
|
|
36
|
+
- Write/edit/delete files
|
|
37
|
+
- Execute bash commands
|
|
38
|
+
- Make git commits
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
project_path: Path,
|
|
44
|
+
model: str = "gpt-4o-mini",
|
|
45
|
+
max_cost: float = 5.0,
|
|
46
|
+
timeout: int = 300
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Initialize plan agent
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
project_path: Project root path
|
|
53
|
+
model: LLM model to use
|
|
54
|
+
max_cost: Maximum cost per execution
|
|
55
|
+
timeout: Timeout in seconds
|
|
56
|
+
"""
|
|
57
|
+
# Plan agent has read-only capabilities
|
|
58
|
+
config = AgentConfig(
|
|
59
|
+
name="plan",
|
|
60
|
+
description="Read-only agent for planning and analysis",
|
|
61
|
+
capabilities=[
|
|
62
|
+
AgentCapability.READ_FILES,
|
|
63
|
+
AgentCapability.ANALYZE_CODE,
|
|
64
|
+
AgentCapability.SEARCH_CODE,
|
|
65
|
+
],
|
|
66
|
+
model=model,
|
|
67
|
+
max_cost=max_cost,
|
|
68
|
+
timeout=timeout
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
super().__init__(config, project_path)
|
|
72
|
+
|
|
73
|
+
# Initialize read-only file operations
|
|
74
|
+
self.file_ops = FileOperations(project_path, create_backups=False)
|
|
75
|
+
|
|
76
|
+
# Initialize RLM
|
|
77
|
+
rlm_config = RLMConfig(
|
|
78
|
+
model=model,
|
|
79
|
+
max_cost=max_cost,
|
|
80
|
+
timeout_seconds=timeout
|
|
81
|
+
)
|
|
82
|
+
self.rlm = RLMIntegration(rlm_config)
|
|
83
|
+
|
|
84
|
+
def execute(self, task: str, context: Optional[Dict[str, Any]] = None) -> AgentResponse:
|
|
85
|
+
"""
|
|
86
|
+
Execute planning task
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
task: Task description
|
|
90
|
+
context: Additional context
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
AgentResponse
|
|
94
|
+
"""
|
|
95
|
+
start_time = time.time()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Prepare context with available operations
|
|
99
|
+
exec_context = {
|
|
100
|
+
"agent": "plan",
|
|
101
|
+
"capabilities": self.get_capabilities(),
|
|
102
|
+
"project_path": str(self.project_path),
|
|
103
|
+
"operations": {
|
|
104
|
+
"file": "Read-only: can read files, cannot write/edit/delete",
|
|
105
|
+
"bash": "Not available (read-only agent)",
|
|
106
|
+
"git": "Not available (read-only agent)"
|
|
107
|
+
},
|
|
108
|
+
"mode": "READ-ONLY",
|
|
109
|
+
**(context or {})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# Add instructions for planning mode
|
|
113
|
+
enhanced_task = f"""Task: {task}
|
|
114
|
+
|
|
115
|
+
You are the PLAN agent with READ-ONLY access:
|
|
116
|
+
- Can read files
|
|
117
|
+
- Can analyze code
|
|
118
|
+
- Can search code
|
|
119
|
+
- CANNOT modify files
|
|
120
|
+
- CANNOT execute bash
|
|
121
|
+
- CANNOT use git
|
|
122
|
+
|
|
123
|
+
Your goal is to:
|
|
124
|
+
1. Understand the codebase
|
|
125
|
+
2. Analyze architecture
|
|
126
|
+
3. Plan implementations
|
|
127
|
+
4. Provide recommendations
|
|
128
|
+
5. Generate documentation
|
|
129
|
+
|
|
130
|
+
Available in context:
|
|
131
|
+
- file_ops: FileOperations instance (read-only)
|
|
132
|
+
|
|
133
|
+
Example usage in RLM code:
|
|
134
|
+
```python
|
|
135
|
+
# Read file
|
|
136
|
+
result = file_ops.read_file(Path("example.py"))
|
|
137
|
+
if result.success:
|
|
138
|
+
content = result.message
|
|
139
|
+
# Analyze content...
|
|
140
|
+
|
|
141
|
+
# List files
|
|
142
|
+
result = file_ops.list_files(pattern="*.py", recursive=True)
|
|
143
|
+
if result.success:
|
|
144
|
+
files = result.message.split("\\n")
|
|
145
|
+
# Process files...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Analyze and plan for the task. DO NOT attempt to modify files.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# Execute with RLM
|
|
152
|
+
result = self.rlm.complete(
|
|
153
|
+
task=enhanced_task,
|
|
154
|
+
context=exec_context
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
elapsed_time = time.time() - start_time
|
|
158
|
+
|
|
159
|
+
response = AgentResponse(
|
|
160
|
+
success=result.success,
|
|
161
|
+
message=result.response if result.success else result.error or "Unknown error",
|
|
162
|
+
agent_name=self.config.name,
|
|
163
|
+
task=task,
|
|
164
|
+
cost=result.total_cost,
|
|
165
|
+
time=elapsed_time,
|
|
166
|
+
metadata={
|
|
167
|
+
"iterations": result.iterations,
|
|
168
|
+
"rlm_success": result.success,
|
|
169
|
+
"mode": "read-only"
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
self._log_execution(response)
|
|
174
|
+
return response
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
elapsed_time = time.time() - start_time
|
|
178
|
+
|
|
179
|
+
response = AgentResponse(
|
|
180
|
+
success=False,
|
|
181
|
+
message=f"Plan agent error: {e}",
|
|
182
|
+
agent_name=self.config.name,
|
|
183
|
+
task=task,
|
|
184
|
+
time=elapsed_time
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._log_execution(response)
|
|
188
|
+
return response
|
|
189
|
+
|
|
190
|
+
# =========================================================================
|
|
191
|
+
# Convenience Methods (Read-Only Operations)
|
|
192
|
+
# =========================================================================
|
|
193
|
+
|
|
194
|
+
def read_file(self, path: Path) -> str:
|
|
195
|
+
"""Read file (convenience method)"""
|
|
196
|
+
self.require(AgentCapability.READ_FILES)
|
|
197
|
+
result = self.file_ops.read_file(path)
|
|
198
|
+
if not result.success:
|
|
199
|
+
raise RuntimeError(result.message)
|
|
200
|
+
return result.message
|
|
201
|
+
|
|
202
|
+
def list_files(self, pattern: str = "*.py", recursive: bool = True) -> list[str]:
|
|
203
|
+
"""List files matching pattern (convenience method)"""
|
|
204
|
+
self.require(AgentCapability.READ_FILES)
|
|
205
|
+
result = self.file_ops.list_files(pattern=pattern, recursive=recursive)
|
|
206
|
+
if not result.success:
|
|
207
|
+
raise RuntimeError(result.message)
|
|
208
|
+
return result.message.split("\n") if result.message else []
|
|
209
|
+
|
|
210
|
+
def analyze(self, task: str) -> str:
|
|
211
|
+
"""Analyze and plan (convenience method)"""
|
|
212
|
+
response = self.execute(task)
|
|
213
|
+
if not response.success:
|
|
214
|
+
raise RuntimeError(response.message)
|
|
215
|
+
return response.message
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced CLI with multi-agent support and new operations
|
|
3
|
+
|
|
4
|
+
Adds build/plan agent switching, file operations, bash execution, and git integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.markdown import Markdown
|
|
16
|
+
from rich.syntax import Syntax
|
|
17
|
+
|
|
18
|
+
from groknroll.agents.agent_manager import AgentManager
|
|
19
|
+
from groknroll.operations.file_ops import FileOperations, FileEdit
|
|
20
|
+
from groknroll.operations.bash_ops import BashOperations
|
|
21
|
+
from groknroll.operations.git_ops import GitOperations
|
|
22
|
+
from groknroll import __version__
|
|
23
|
+
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group(invoke_without_command=True)
|
|
28
|
+
@click.option('--version', is_flag=True, help='Show version')
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def main(ctx, version):
|
|
31
|
+
"""
|
|
32
|
+
🎸 groknroll 2.0 - The Ultimate CLI Coding Agent
|
|
33
|
+
|
|
34
|
+
Now with multi-agent support, file operations, bash execution, and git integration!
|
|
35
|
+
"""
|
|
36
|
+
if version:
|
|
37
|
+
console.print(f"[bold cyan]groknroll[/bold cyan] version {__version__}")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if ctx.invoked_subcommand is None:
|
|
41
|
+
console.print(ctx.get_help())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ============================================================================
|
|
45
|
+
# Agent Commands
|
|
46
|
+
# ============================================================================
|
|
47
|
+
|
|
48
|
+
@main.command()
|
|
49
|
+
@click.argument('task', nargs=-1, required=True)
|
|
50
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
51
|
+
@click.option('--agent', type=click.Choice(['build', 'plan']), default='build',
|
|
52
|
+
help='Agent to use (build=full access, plan=read-only)')
|
|
53
|
+
def build(task: tuple, path: Optional[str], agent: str):
|
|
54
|
+
"""Execute task with build or plan agent"""
|
|
55
|
+
project_path = Path(path) if path else Path.cwd()
|
|
56
|
+
task_str = " ".join(task)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with console.status(f"[bold cyan]{agent.title()} agent working...") as status:
|
|
60
|
+
manager = AgentManager(project_path)
|
|
61
|
+
response = manager.execute(task_str, agent=agent)
|
|
62
|
+
|
|
63
|
+
# Display response
|
|
64
|
+
console.print()
|
|
65
|
+
if response.success:
|
|
66
|
+
console.print(Panel(
|
|
67
|
+
Markdown(response.message),
|
|
68
|
+
title=f"[bold green]✓ {agent.title()} Agent[/bold green]",
|
|
69
|
+
border_style="green"
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
# Show metrics
|
|
73
|
+
console.print(f"\n[dim]Completed in {response.time:.1f}s "
|
|
74
|
+
f"(cost: ${response.cost:.4f})[/dim]")
|
|
75
|
+
else:
|
|
76
|
+
console.print(Panel(
|
|
77
|
+
response.message,
|
|
78
|
+
title=f"[bold red]✗ {agent.title()} Agent Failed[/bold red]",
|
|
79
|
+
border_style="red"
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@main.command()
|
|
88
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
89
|
+
def agents(path: Optional[str]):
|
|
90
|
+
"""List available agents"""
|
|
91
|
+
project_path = Path(path) if path else Path.cwd()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
manager = AgentManager(project_path)
|
|
95
|
+
agent_list = manager.list_agents()
|
|
96
|
+
|
|
97
|
+
# Create table
|
|
98
|
+
table = Table(title="Available Agents", show_header=True)
|
|
99
|
+
table.add_column("Name", style="cyan")
|
|
100
|
+
table.add_column("Description", style="white")
|
|
101
|
+
table.add_column("Active", style="green")
|
|
102
|
+
table.add_column("Capabilities", style="dim")
|
|
103
|
+
|
|
104
|
+
for agent_info in agent_list:
|
|
105
|
+
table.add_row(
|
|
106
|
+
agent_info.name,
|
|
107
|
+
agent_info.description,
|
|
108
|
+
"✓" if agent_info.is_active else "",
|
|
109
|
+
", ".join(agent_info.capabilities[:3]) + "..."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
console.print()
|
|
113
|
+
console.print(table)
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
117
|
+
sys.exit(1)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ============================================================================
|
|
121
|
+
# File Operations Commands
|
|
122
|
+
# ============================================================================
|
|
123
|
+
|
|
124
|
+
@main.command()
|
|
125
|
+
@click.argument('file_path', type=click.Path(exists=True))
|
|
126
|
+
@click.option('--syntax', is_flag=True, help='Syntax highlighting')
|
|
127
|
+
def read(file_path: str, syntax: bool):
|
|
128
|
+
"""Read file contents"""
|
|
129
|
+
path = Path(file_path)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
file_ops = FileOperations(Path.cwd())
|
|
133
|
+
result = file_ops.read_file(path)
|
|
134
|
+
|
|
135
|
+
if result.success:
|
|
136
|
+
console.print()
|
|
137
|
+
if syntax:
|
|
138
|
+
# Detect language from extension
|
|
139
|
+
extension = path.suffix.lstrip('.')
|
|
140
|
+
syntax_obj = Syntax(result.message, extension or "text",
|
|
141
|
+
theme="monokai", line_numbers=True)
|
|
142
|
+
console.print(syntax_obj)
|
|
143
|
+
else:
|
|
144
|
+
console.print(Panel(
|
|
145
|
+
result.message,
|
|
146
|
+
title=f"[cyan]{path.name}[/cyan]",
|
|
147
|
+
border_style="cyan"
|
|
148
|
+
))
|
|
149
|
+
else:
|
|
150
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@main.command()
|
|
159
|
+
@click.argument('file_path', type=click.Path())
|
|
160
|
+
@click.argument('content')
|
|
161
|
+
@click.option('--overwrite', is_flag=True, help='Overwrite if exists')
|
|
162
|
+
def write(file_path: str, content: str, overwrite: bool):
|
|
163
|
+
"""Write content to file"""
|
|
164
|
+
path = Path(file_path)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
file_ops = FileOperations(Path.cwd())
|
|
168
|
+
result = file_ops.write_file(path, content, overwrite=overwrite)
|
|
169
|
+
|
|
170
|
+
if result.success:
|
|
171
|
+
console.print(f"[bold green]✓[/bold green] {result.message}")
|
|
172
|
+
if result.backup_path:
|
|
173
|
+
console.print(f"[dim]Backup: {result.backup_path.name}[/dim]")
|
|
174
|
+
else:
|
|
175
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
180
|
+
sys.exit(1)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@main.command()
|
|
184
|
+
@click.argument('file_path', type=click.Path(exists=True))
|
|
185
|
+
@click.option('--backup/--no-backup', default=True, help='Create backup')
|
|
186
|
+
def delete(file_path: str, backup: bool):
|
|
187
|
+
"""Delete file"""
|
|
188
|
+
path = Path(file_path)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
file_ops = FileOperations(Path.cwd())
|
|
192
|
+
result = file_ops.delete_file(path, backup=backup)
|
|
193
|
+
|
|
194
|
+
if result.success:
|
|
195
|
+
console.print(f"[bold green]✓[/bold green] {result.message}")
|
|
196
|
+
if result.backup_path:
|
|
197
|
+
console.print(f"[dim]Backup: {result.backup_path.name}[/dim]")
|
|
198
|
+
else:
|
|
199
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
200
|
+
sys.exit(1)
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
204
|
+
sys.exit(1)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ============================================================================
|
|
208
|
+
# Bash Operations Commands
|
|
209
|
+
# ============================================================================
|
|
210
|
+
|
|
211
|
+
@main.command()
|
|
212
|
+
@click.argument('command')
|
|
213
|
+
@click.option('--cwd', type=click.Path(exists=True), help='Working directory')
|
|
214
|
+
@click.option('--timeout', type=int, default=300, help='Timeout in seconds')
|
|
215
|
+
def run(command: str, cwd: Optional[str], timeout: int):
|
|
216
|
+
"""Execute bash command"""
|
|
217
|
+
work_dir = Path(cwd) if cwd else Path.cwd()
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
bash_ops = BashOperations(Path.cwd())
|
|
221
|
+
result = bash_ops.execute(command, cwd=work_dir, timeout=timeout)
|
|
222
|
+
|
|
223
|
+
console.print()
|
|
224
|
+
if result.success:
|
|
225
|
+
console.print(Panel(
|
|
226
|
+
result.stdout or "[dim]No output[/dim]",
|
|
227
|
+
title=f"[bold green]✓ Command: {command}[/bold green]",
|
|
228
|
+
border_style="green"
|
|
229
|
+
))
|
|
230
|
+
else:
|
|
231
|
+
console.print(Panel(
|
|
232
|
+
result.stderr or result.stdout,
|
|
233
|
+
title=f"[bold red]✗ Command Failed (exit {result.exit_code})[/bold red]",
|
|
234
|
+
border_style="red"
|
|
235
|
+
))
|
|
236
|
+
sys.exit(result.exit_code)
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
240
|
+
sys.exit(1)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ============================================================================
|
|
244
|
+
# Git Operations Commands
|
|
245
|
+
# ============================================================================
|
|
246
|
+
|
|
247
|
+
@main.command()
|
|
248
|
+
@click.option('--path', type=click.Path(exists=True), help='Repository path')
|
|
249
|
+
def git_status(path: Optional[str]):
|
|
250
|
+
"""Show git status"""
|
|
251
|
+
repo_path = Path(path) if path else Path.cwd()
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
git_ops = GitOperations(repo_path)
|
|
255
|
+
status = git_ops.status()
|
|
256
|
+
|
|
257
|
+
# Create status display
|
|
258
|
+
console.print()
|
|
259
|
+
console.print(Panel.fit(
|
|
260
|
+
f"[bold cyan]Branch:[/bold cyan] {status.branch}\n"
|
|
261
|
+
f"[cyan]Ahead:[/cyan] {status.ahead} [cyan]Behind:[/cyan] {status.behind}\n"
|
|
262
|
+
f"[green]Staged:[/green] {len(status.staged)} "
|
|
263
|
+
f"[yellow]Unstaged:[/yellow] {len(status.unstaged)} "
|
|
264
|
+
f"[red]Untracked:[/red] {len(status.untracked)}",
|
|
265
|
+
title="[bold]Git Status[/bold]",
|
|
266
|
+
border_style="cyan"
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
# Show file lists
|
|
270
|
+
if status.staged:
|
|
271
|
+
console.print("\n[bold green]Staged files:[/bold green]")
|
|
272
|
+
for file in status.staged[:10]:
|
|
273
|
+
console.print(f" [green]+[/green] {file}")
|
|
274
|
+
|
|
275
|
+
if status.unstaged:
|
|
276
|
+
console.print("\n[bold yellow]Unstaged files:[/bold yellow]")
|
|
277
|
+
for file in status.unstaged[:10]:
|
|
278
|
+
console.print(f" [yellow]M[/yellow] {file}")
|
|
279
|
+
|
|
280
|
+
if status.untracked:
|
|
281
|
+
console.print("\n[bold red]Untracked files:[/bold red]")
|
|
282
|
+
for file in status.untracked[:10]:
|
|
283
|
+
console.print(f" [red]?[/red] {file}")
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@main.command()
|
|
291
|
+
@click.argument('message')
|
|
292
|
+
@click.option('--path', type=click.Path(exists=True), help='Repository path')
|
|
293
|
+
@click.option('--all', 'all_files', is_flag=True, help='Stage all files')
|
|
294
|
+
def git_commit(message: str, path: Optional[str], all_files: bool):
|
|
295
|
+
"""Create git commit"""
|
|
296
|
+
repo_path = Path(path) if path else Path.cwd()
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
git_ops = GitOperations(repo_path)
|
|
300
|
+
|
|
301
|
+
# Stage all if requested
|
|
302
|
+
if all_files:
|
|
303
|
+
add_result = git_ops.add(all_files=True)
|
|
304
|
+
if not add_result.success:
|
|
305
|
+
console.print(f"[bold red]Error staging files:[/bold red] {add_result.message}")
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
|
|
308
|
+
# Commit
|
|
309
|
+
result = git_ops.commit(message)
|
|
310
|
+
|
|
311
|
+
if result.success:
|
|
312
|
+
console.print(f"[bold green]✓[/bold green] {result.message}")
|
|
313
|
+
else:
|
|
314
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@main.command()
|
|
323
|
+
@click.option('--path', type=click.Path(exists=True), help='Repository path')
|
|
324
|
+
@click.option('--remote', default='origin', help='Remote name')
|
|
325
|
+
@click.option('--force', is_flag=True, help='Force push')
|
|
326
|
+
def git_push(path: Optional[str], remote: str, force: bool):
|
|
327
|
+
"""Push to remote"""
|
|
328
|
+
repo_path = Path(path) if path else Path.cwd()
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
git_ops = GitOperations(repo_path)
|
|
332
|
+
result = git_ops.push(remote=remote, force=force)
|
|
333
|
+
|
|
334
|
+
if result.success:
|
|
335
|
+
console.print(f"[bold green]✓[/bold green] {result.message}")
|
|
336
|
+
else:
|
|
337
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@main.command()
|
|
346
|
+
@click.argument('title')
|
|
347
|
+
@click.option('--body', help='PR description')
|
|
348
|
+
@click.option('--base', default='main', help='Base branch')
|
|
349
|
+
@click.option('--draft', is_flag=True, help='Create as draft')
|
|
350
|
+
@click.option('--path', type=click.Path(exists=True), help='Repository path')
|
|
351
|
+
def create_pr(title: str, body: Optional[str], base: str, draft: bool, path: Optional[str]):
|
|
352
|
+
"""Create pull request"""
|
|
353
|
+
repo_path = Path(path) if path else Path.cwd()
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
git_ops = GitOperations(repo_path)
|
|
357
|
+
result = git_ops.create_pr(title=title, body=body, base=base, draft=draft)
|
|
358
|
+
|
|
359
|
+
if result.success:
|
|
360
|
+
console.print(f"[bold green]✓ PR created![/bold green]")
|
|
361
|
+
console.print(f"\n{result.output}")
|
|
362
|
+
else:
|
|
363
|
+
console.print(f"[bold red]Error:[/bold red] {result.message}")
|
|
364
|
+
sys.exit(1)
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
368
|
+
sys.exit(1)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if __name__ == "__main__":
|
|
372
|
+
main()
|