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.
Files changed (62) hide show
  1. groknroll/__init__.py +36 -0
  2. groknroll/__main__.py +9 -0
  3. groknroll/agents/__init__.py +18 -0
  4. groknroll/agents/agent_manager.py +187 -0
  5. groknroll/agents/base_agent.py +118 -0
  6. groknroll/agents/build_agent.py +231 -0
  7. groknroll/agents/plan_agent.py +215 -0
  8. groknroll/cli/__init__.py +7 -0
  9. groknroll/cli/enhanced_cli.py +372 -0
  10. groknroll/cli/large_codebase_cli.py +413 -0
  11. groknroll/cli/main.py +331 -0
  12. groknroll/cli/rlm_commands.py +258 -0
  13. groknroll/clients/__init__.py +63 -0
  14. groknroll/clients/anthropic.py +112 -0
  15. groknroll/clients/azure_openai.py +142 -0
  16. groknroll/clients/base_lm.py +33 -0
  17. groknroll/clients/gemini.py +162 -0
  18. groknroll/clients/litellm.py +105 -0
  19. groknroll/clients/openai.py +129 -0
  20. groknroll/clients/portkey.py +94 -0
  21. groknroll/core/__init__.py +9 -0
  22. groknroll/core/agent.py +339 -0
  23. groknroll/core/comms_utils.py +264 -0
  24. groknroll/core/context.py +251 -0
  25. groknroll/core/exceptions.py +181 -0
  26. groknroll/core/large_codebase.py +564 -0
  27. groknroll/core/lm_handler.py +206 -0
  28. groknroll/core/rlm.py +446 -0
  29. groknroll/core/rlm_codebase.py +448 -0
  30. groknroll/core/rlm_integration.py +256 -0
  31. groknroll/core/types.py +276 -0
  32. groknroll/environments/__init__.py +34 -0
  33. groknroll/environments/base_env.py +182 -0
  34. groknroll/environments/constants.py +32 -0
  35. groknroll/environments/docker_repl.py +336 -0
  36. groknroll/environments/local_repl.py +388 -0
  37. groknroll/environments/modal_repl.py +502 -0
  38. groknroll/environments/prime_repl.py +588 -0
  39. groknroll/logger/__init__.py +4 -0
  40. groknroll/logger/rlm_logger.py +63 -0
  41. groknroll/logger/verbose.py +393 -0
  42. groknroll/operations/__init__.py +15 -0
  43. groknroll/operations/bash_ops.py +447 -0
  44. groknroll/operations/file_ops.py +473 -0
  45. groknroll/operations/git_ops.py +620 -0
  46. groknroll/oracle/__init__.py +11 -0
  47. groknroll/oracle/codebase_indexer.py +238 -0
  48. groknroll/oracle/oracle_agent.py +278 -0
  49. groknroll/setup.py +34 -0
  50. groknroll/storage/__init__.py +14 -0
  51. groknroll/storage/database.py +272 -0
  52. groknroll/storage/models.py +128 -0
  53. groknroll/utils/__init__.py +0 -0
  54. groknroll/utils/parsing.py +168 -0
  55. groknroll/utils/prompts.py +146 -0
  56. groknroll/utils/rlm_utils.py +19 -0
  57. groknroll-2.0.0.dist-info/METADATA +246 -0
  58. groknroll-2.0.0.dist-info/RECORD +62 -0
  59. groknroll-2.0.0.dist-info/WHEEL +5 -0
  60. groknroll-2.0.0.dist-info/entry_points.txt +3 -0
  61. groknroll-2.0.0.dist-info/licenses/LICENSE +21 -0
  62. 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,7 @@
1
+ """
2
+ groknroll CLI - Beautiful terminal interface
3
+ """
4
+
5
+ from groknroll.cli.main import main
6
+
7
+ __all__ = ["main"]
@@ -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()