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
groknroll/cli/main.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
groknroll CLI - Main entry point
|
|
3
|
+
|
|
4
|
+
The Ultimate CLI Coding Agent - Better than Claude Code!
|
|
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.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
|
|
18
|
+
from groknroll.core.agent import GroknrollAgent, AgentConfig
|
|
19
|
+
from groknroll import __version__
|
|
20
|
+
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group(invoke_without_command=True)
|
|
25
|
+
@click.option('--version', is_flag=True, help='Show version')
|
|
26
|
+
@click.pass_context
|
|
27
|
+
def main(ctx, version):
|
|
28
|
+
"""
|
|
29
|
+
🎸 groknroll - The Ultimate CLI Coding Agent
|
|
30
|
+
|
|
31
|
+
Better than Claude Code. Local, unlimited context, autonomous.
|
|
32
|
+
"""
|
|
33
|
+
if version:
|
|
34
|
+
console.print(f"[bold cyan]groknroll[/bold cyan] version {__version__}")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if ctx.invoked_subcommand is None:
|
|
38
|
+
# No subcommand - show help
|
|
39
|
+
console.print(ctx.get_help())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@main.command()
|
|
43
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
44
|
+
def init(path: Optional[str]):
|
|
45
|
+
"""Initialize groknroll for a project"""
|
|
46
|
+
project_path = Path(path) if path else Path.cwd()
|
|
47
|
+
|
|
48
|
+
with Progress(
|
|
49
|
+
SpinnerColumn(),
|
|
50
|
+
TextColumn("[progress.description]{task.description}"),
|
|
51
|
+
console=console
|
|
52
|
+
) as progress:
|
|
53
|
+
task = progress.add_task("Initializing groknroll...", total=None)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
agent = GroknrollAgent(project_path)
|
|
57
|
+
stats = agent.reindex_project()
|
|
58
|
+
|
|
59
|
+
progress.update(task, completed=True)
|
|
60
|
+
|
|
61
|
+
# Show results
|
|
62
|
+
console.print()
|
|
63
|
+
console.print(Panel.fit(
|
|
64
|
+
f"[bold green]✓ Project initialized![/bold green]\n\n"
|
|
65
|
+
f"[cyan]Files indexed:[/cyan] {stats['indexed']}\n"
|
|
66
|
+
f"[cyan]Lines of code:[/cyan] {stats['total_lines']:,}\n"
|
|
67
|
+
f"[cyan]Files skipped:[/cyan] {stats['skipped']}",
|
|
68
|
+
title="[bold]groknroll[/bold]",
|
|
69
|
+
border_style="green"
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@main.command()
|
|
78
|
+
@click.argument('message', nargs=-1, required=True)
|
|
79
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
80
|
+
def chat(message: tuple, path: Optional[str]):
|
|
81
|
+
"""Chat with groknroll about your code"""
|
|
82
|
+
project_path = Path(path) if path else Path.cwd()
|
|
83
|
+
message_str = " ".join(message)
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
with console.status("[bold cyan]Thinking...") as status:
|
|
87
|
+
agent = GroknrollAgent(project_path)
|
|
88
|
+
response = agent.chat(message_str)
|
|
89
|
+
|
|
90
|
+
# Display response
|
|
91
|
+
console.print()
|
|
92
|
+
console.print(Panel(
|
|
93
|
+
Markdown(response),
|
|
94
|
+
title="[bold cyan]groknroll[/bold cyan]",
|
|
95
|
+
border_style="cyan"
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@main.command()
|
|
104
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
105
|
+
@click.option('--detailed', is_flag=True, help='Detailed analysis')
|
|
106
|
+
def analyze(path: Optional[str], detailed: bool):
|
|
107
|
+
"""Analyze project codebase"""
|
|
108
|
+
project_path = Path(path) if path else Path.cwd()
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
with console.status("[bold cyan]Analyzing project...") as status:
|
|
112
|
+
agent = GroknrollAgent(project_path)
|
|
113
|
+
results = agent.analyze_project(detailed=detailed)
|
|
114
|
+
|
|
115
|
+
# Display results
|
|
116
|
+
console.print()
|
|
117
|
+
console.print(Panel.fit(
|
|
118
|
+
Markdown(results.get("analysis", "No analysis available")),
|
|
119
|
+
title="[bold cyan]Project Analysis[/bold cyan]",
|
|
120
|
+
border_style="cyan"
|
|
121
|
+
))
|
|
122
|
+
|
|
123
|
+
# Show metrics
|
|
124
|
+
metrics = results.get("metrics", {})
|
|
125
|
+
console.print(f"\n[dim]Analysis completed in {metrics.get('time', 0):.1f}s "
|
|
126
|
+
f"(cost: ${metrics.get('cost', 0):.4f})[/dim]")
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
130
|
+
sys.exit(1)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@main.command()
|
|
134
|
+
@click.argument('file_path', type=click.Path(exists=True))
|
|
135
|
+
@click.option('--type', 'analysis_type', default='review',
|
|
136
|
+
type=click.Choice(['review', 'security', 'complexity', 'explain']),
|
|
137
|
+
help='Analysis type')
|
|
138
|
+
def review(file_path: str, analysis_type: str):
|
|
139
|
+
"""Review a code file"""
|
|
140
|
+
file_path = Path(file_path)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with console.status(f"[bold cyan]Analyzing {file_path.name}...") as status:
|
|
144
|
+
agent = GroknrollAgent(file_path.parent)
|
|
145
|
+
results = agent.analyze_code(file_path=file_path, analysis_type=analysis_type)
|
|
146
|
+
|
|
147
|
+
# Display results
|
|
148
|
+
console.print()
|
|
149
|
+
console.print(Panel(
|
|
150
|
+
Markdown(results.get("analysis", "No analysis available")),
|
|
151
|
+
title=f"[bold cyan]{analysis_type.title()}: {file_path.name}[/bold cyan]",
|
|
152
|
+
border_style="cyan"
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
157
|
+
sys.exit(1)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@main.command()
|
|
161
|
+
@click.argument('query')
|
|
162
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
163
|
+
@click.option('--language', help='Filter by language')
|
|
164
|
+
def search(query: str, path: Optional[str], language: Optional[str]):
|
|
165
|
+
"""Search codebase"""
|
|
166
|
+
project_path = Path(path) if path else Path.cwd()
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
agent = GroknrollAgent(project_path)
|
|
170
|
+
results = agent.search_code(query, language)
|
|
171
|
+
|
|
172
|
+
if not results:
|
|
173
|
+
console.print(f"[yellow]No files found matching '{query}'[/yellow]")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Display results in table
|
|
177
|
+
table = Table(title=f"Search Results: '{query}'", show_header=True)
|
|
178
|
+
table.add_column("File", style="cyan")
|
|
179
|
+
table.add_column("Language", style="green")
|
|
180
|
+
table.add_column("Lines", justify="right", style="blue")
|
|
181
|
+
|
|
182
|
+
for result in results[:20]: # Limit to 20 results
|
|
183
|
+
table.add_row(
|
|
184
|
+
result["relative_path"],
|
|
185
|
+
result["language"],
|
|
186
|
+
str(result["lines_of_code"])
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
console.print()
|
|
190
|
+
console.print(table)
|
|
191
|
+
|
|
192
|
+
if len(results) > 20:
|
|
193
|
+
console.print(f"\n[dim]Showing 20 of {len(results)} results[/dim]")
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
197
|
+
sys.exit(1)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@main.command()
|
|
201
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
202
|
+
def stats(path: Optional[str]):
|
|
203
|
+
"""Show project and usage statistics"""
|
|
204
|
+
project_path = Path(path) if path else Path.cwd()
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
agent = GroknrollAgent(project_path)
|
|
208
|
+
stats = agent.get_stats()
|
|
209
|
+
|
|
210
|
+
# Project stats
|
|
211
|
+
project = stats["project"]
|
|
212
|
+
console.print()
|
|
213
|
+
console.print(Panel.fit(
|
|
214
|
+
f"[bold cyan]Project:[/bold cyan] {project['name']}\n"
|
|
215
|
+
f"[cyan]Files:[/cyan] {project['files']}\n"
|
|
216
|
+
f"[cyan]Lines of Code:[/cyan] {project['lines']:,}",
|
|
217
|
+
title="[bold]Project Statistics[/bold]",
|
|
218
|
+
border_style="cyan"
|
|
219
|
+
))
|
|
220
|
+
|
|
221
|
+
# Language breakdown
|
|
222
|
+
if project['languages']:
|
|
223
|
+
console.print()
|
|
224
|
+
lang_table = Table(title="Languages", show_header=True)
|
|
225
|
+
lang_table.add_column("Language", style="cyan")
|
|
226
|
+
lang_table.add_column("Files", justify="right", style="green")
|
|
227
|
+
lang_table.add_column("Lines", justify="right", style="blue")
|
|
228
|
+
|
|
229
|
+
for lang, lang_stats in sorted(
|
|
230
|
+
project['languages'].items(),
|
|
231
|
+
key=lambda x: x[1]['lines'],
|
|
232
|
+
reverse=True
|
|
233
|
+
):
|
|
234
|
+
lang_table.add_row(
|
|
235
|
+
lang,
|
|
236
|
+
str(lang_stats['files']),
|
|
237
|
+
f"{lang_stats['lines']:,}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
console.print(lang_table)
|
|
241
|
+
|
|
242
|
+
# Execution stats
|
|
243
|
+
exec_stats = stats["executions"]
|
|
244
|
+
if exec_stats["total_executions"] > 0:
|
|
245
|
+
console.print()
|
|
246
|
+
console.print(Panel.fit(
|
|
247
|
+
f"[bold cyan]Total Executions:[/bold cyan] {exec_stats['total_executions']}\n"
|
|
248
|
+
f"[cyan]Successful:[/cyan] {exec_stats['successful']}\n"
|
|
249
|
+
f"[cyan]Failed:[/cyan] {exec_stats['failed']}\n"
|
|
250
|
+
f"[cyan]Total Cost:[/cyan] ${exec_stats['total_cost']:.4f}\n"
|
|
251
|
+
f"[cyan]Total Time:[/cyan] {exec_stats['total_time']:.1f}s\n"
|
|
252
|
+
f"[cyan]Avg Cost:[/cyan] ${exec_stats['avg_cost']:.4f}\n"
|
|
253
|
+
f"[cyan]Avg Time:[/cyan] {exec_stats['avg_time']:.1f}s",
|
|
254
|
+
title="[bold]RLM Usage Statistics[/bold]",
|
|
255
|
+
border_style="green"
|
|
256
|
+
))
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@main.command()
|
|
264
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
265
|
+
@click.option('--limit', default=10, help='Number of recent items')
|
|
266
|
+
def history(path: Optional[str], limit: int):
|
|
267
|
+
"""Show recent execution history"""
|
|
268
|
+
project_path = Path(path) if path else Path.cwd()
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
agent = GroknrollAgent(project_path)
|
|
272
|
+
history_items = agent.get_history(limit=limit)
|
|
273
|
+
|
|
274
|
+
if not history_items:
|
|
275
|
+
console.print("[yellow]No execution history yet[/yellow]")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
# Display history in table
|
|
279
|
+
table = Table(title="Recent Executions", show_header=True)
|
|
280
|
+
table.add_column("Time", style="dim")
|
|
281
|
+
table.add_column("Task", style="cyan")
|
|
282
|
+
table.add_column("Status", style="green")
|
|
283
|
+
table.add_column("Cost", justify="right", style="blue")
|
|
284
|
+
table.add_column("Time", justify="right", style="magenta")
|
|
285
|
+
|
|
286
|
+
for item in history_items:
|
|
287
|
+
status_color = "green" if item["status"] == "success" else "red"
|
|
288
|
+
table.add_row(
|
|
289
|
+
item["timestamp"].strftime("%Y-%m-%d %H:%M"),
|
|
290
|
+
item["task"],
|
|
291
|
+
f"[{status_color}]{item['status']}[/{status_color}]",
|
|
292
|
+
f"${item['cost']:.4f}" if item['cost'] else "N/A",
|
|
293
|
+
f"{item['time']:.1f}s" if item['time'] else "N/A"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
console.print()
|
|
297
|
+
console.print(table)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@main.command()
|
|
305
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
306
|
+
def info(path: Optional[str]):
|
|
307
|
+
"""Show project information"""
|
|
308
|
+
project_path = Path(path) if path else Path.cwd()
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
agent = GroknrollAgent(project_path)
|
|
312
|
+
info = agent.get_project_info()
|
|
313
|
+
|
|
314
|
+
console.print()
|
|
315
|
+
console.print(Panel.fit(
|
|
316
|
+
f"[bold cyan]Name:[/bold cyan] {info['name']}\n"
|
|
317
|
+
f"[cyan]Path:[/cyan] {info['path']}\n"
|
|
318
|
+
f"[cyan]Files:[/cyan] {info['total_files']}\n"
|
|
319
|
+
f"[cyan]Lines:[/cyan] {info['total_lines']:,}\n"
|
|
320
|
+
f"[cyan]Last Indexed:[/cyan] {info['last_indexed']}",
|
|
321
|
+
title="[bold]Project Information[/bold]",
|
|
322
|
+
border_style="cyan"
|
|
323
|
+
))
|
|
324
|
+
|
|
325
|
+
except Exception as e:
|
|
326
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
327
|
+
sys.exit(1)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if __name__ == "__main__":
|
|
331
|
+
main()
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RLM Commands for Large Codebases
|
|
3
|
+
|
|
4
|
+
Simple commands that leverage RLM's unlimited context.
|
|
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.markdown import Markdown
|
|
15
|
+
|
|
16
|
+
from groknroll.core.rlm_codebase import RLMCodebaseAnalyzer
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.group()
|
|
22
|
+
def rlm():
|
|
23
|
+
"""RLM-powered commands for unlimited context (large codebases)"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@rlm.command()
|
|
28
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
29
|
+
@click.option('--focus', help='Focus area (security, architecture, performance)')
|
|
30
|
+
@click.option('--max-cost', type=float, default=10.0, help='Max cost in dollars')
|
|
31
|
+
def analyze_all(path: Optional[str], focus: Optional[str], max_cost: float):
|
|
32
|
+
"""
|
|
33
|
+
Analyze ENTIRE codebase with RLM (unlimited context!)
|
|
34
|
+
|
|
35
|
+
RLM will recursively explore your entire project.
|
|
36
|
+
No context limits. No chunking needed.
|
|
37
|
+
"""
|
|
38
|
+
project_path = Path(path) if path else Path.cwd()
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
console.print()
|
|
42
|
+
console.print(Panel.fit(
|
|
43
|
+
f"[bold cyan]Analyzing entire codebase with RLM[/bold cyan]\n\n"
|
|
44
|
+
f"[yellow]Note:[/yellow] RLM has unlimited context!\n"
|
|
45
|
+
f"It will recursively explore ALL files as needed.\n\n"
|
|
46
|
+
f"[dim]Max cost: ${max_cost}[/dim]",
|
|
47
|
+
title="[bold]RLM Analysis[/bold]",
|
|
48
|
+
border_style="cyan"
|
|
49
|
+
))
|
|
50
|
+
|
|
51
|
+
with console.status("[bold cyan]RLM exploring codebase..."):
|
|
52
|
+
analyzer = RLMCodebaseAnalyzer(
|
|
53
|
+
project_path,
|
|
54
|
+
max_cost=max_cost
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
console.print(f"\n[dim]Project: {analyzer.context.project_path.name}[/dim]")
|
|
58
|
+
console.print(f"[dim]Files: {analyzer.context.total_files:,}[/dim]")
|
|
59
|
+
console.print(f"[dim]Lines: {analyzer.context.total_lines:,}[/dim]\n")
|
|
60
|
+
|
|
61
|
+
result = analyzer.analyze_entire_codebase(focus=focus)
|
|
62
|
+
|
|
63
|
+
# Display results
|
|
64
|
+
console.print()
|
|
65
|
+
console.print(Panel(
|
|
66
|
+
Markdown(result),
|
|
67
|
+
title="[bold green]✓ RLM Analysis Complete[/bold green]",
|
|
68
|
+
border_style="green"
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
73
|
+
import traceback
|
|
74
|
+
traceback.print_exc()
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@rlm.command()
|
|
79
|
+
@click.argument('issue')
|
|
80
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
81
|
+
@click.option('--max-cost', type=float, default=5.0, help='Max cost in dollars')
|
|
82
|
+
def fix(issue: str, path: Optional[str], max_cost: float):
|
|
83
|
+
"""
|
|
84
|
+
Find and fix issue across ENTIRE codebase
|
|
85
|
+
|
|
86
|
+
RLM will search through all files to find and fix the issue.
|
|
87
|
+
"""
|
|
88
|
+
project_path = Path(path) if path else Path.cwd()
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
console.print()
|
|
92
|
+
console.print(Panel.fit(
|
|
93
|
+
f"[bold cyan]RLM will find and fix:[/bold cyan]\n{issue}\n\n"
|
|
94
|
+
f"[yellow]Searching entire codebase...[/yellow]",
|
|
95
|
+
title="[bold]Find & Fix[/bold]",
|
|
96
|
+
border_style="cyan"
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
with console.status("[bold cyan]RLM working..."):
|
|
100
|
+
analyzer = RLMCodebaseAnalyzer(project_path, max_cost=max_cost)
|
|
101
|
+
result = analyzer.find_and_fix(issue)
|
|
102
|
+
|
|
103
|
+
console.print()
|
|
104
|
+
console.print(Panel(
|
|
105
|
+
Markdown(result),
|
|
106
|
+
title="[bold green]✓ Fix Complete[/bold green]",
|
|
107
|
+
border_style="green"
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@rlm.command()
|
|
116
|
+
@click.argument('feature')
|
|
117
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
118
|
+
@click.option('--max-cost', type=float, default=10.0, help='Max cost in dollars')
|
|
119
|
+
def implement(feature: str, path: Optional[str], max_cost: float):
|
|
120
|
+
"""
|
|
121
|
+
Implement feature across ENTIRE codebase
|
|
122
|
+
|
|
123
|
+
RLM understands the full architecture and implements consistently.
|
|
124
|
+
"""
|
|
125
|
+
project_path = Path(path) if path else Path.cwd()
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(Panel.fit(
|
|
130
|
+
f"[bold cyan]RLM will implement:[/bold cyan]\n{feature}\n\n"
|
|
131
|
+
f"[yellow]Analyzing architecture first...[/yellow]",
|
|
132
|
+
title="[bold]Feature Implementation[/bold]",
|
|
133
|
+
border_style="cyan"
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
with console.status("[bold cyan]RLM implementing..."):
|
|
137
|
+
analyzer = RLMCodebaseAnalyzer(project_path, max_cost=max_cost)
|
|
138
|
+
result = analyzer.implement_feature(feature)
|
|
139
|
+
|
|
140
|
+
console.print()
|
|
141
|
+
console.print(Panel(
|
|
142
|
+
Markdown(result),
|
|
143
|
+
title="[bold green]✓ Implementation Complete[/bold green]",
|
|
144
|
+
border_style="green"
|
|
145
|
+
))
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@rlm.command()
|
|
153
|
+
@click.argument('refactoring')
|
|
154
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
155
|
+
@click.option('--max-cost', type=float, default=10.0, help='Max cost in dollars')
|
|
156
|
+
def refactor(refactoring: str, path: Optional[str], max_cost: float):
|
|
157
|
+
"""
|
|
158
|
+
Refactor across ENTIRE codebase
|
|
159
|
+
|
|
160
|
+
RLM will find and refactor all matching code consistently.
|
|
161
|
+
"""
|
|
162
|
+
project_path = Path(path) if path else Path.cwd()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
console.print()
|
|
166
|
+
console.print(Panel.fit(
|
|
167
|
+
f"[bold cyan]RLM will refactor:[/bold cyan]\n{refactoring}\n\n"
|
|
168
|
+
f"[yellow]Finding all affected code...[/yellow]",
|
|
169
|
+
title="[bold]Refactoring[/bold]",
|
|
170
|
+
border_style="cyan"
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
with console.status("[bold cyan]RLM refactoring..."):
|
|
174
|
+
analyzer = RLMCodebaseAnalyzer(project_path, max_cost=max_cost)
|
|
175
|
+
result = analyzer.refactor(refactoring)
|
|
176
|
+
|
|
177
|
+
console.print()
|
|
178
|
+
console.print(Panel(
|
|
179
|
+
Markdown(result),
|
|
180
|
+
title="[bold green]✓ Refactoring Complete[/bold green]",
|
|
181
|
+
border_style="green"
|
|
182
|
+
))
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@rlm.command()
|
|
190
|
+
@click.argument('question')
|
|
191
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
192
|
+
@click.option('--max-cost', type=float, default=3.0, help='Max cost in dollars')
|
|
193
|
+
def ask(question: str, path: Optional[str], max_cost: float):
|
|
194
|
+
"""
|
|
195
|
+
Ask question about ENTIRE codebase
|
|
196
|
+
|
|
197
|
+
RLM will explore files to find the answer.
|
|
198
|
+
"""
|
|
199
|
+
project_path = Path(path) if path else Path.cwd()
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
with console.status(f"[bold cyan]RLM searching for answer..."):
|
|
203
|
+
analyzer = RLMCodebaseAnalyzer(project_path, max_cost=max_cost)
|
|
204
|
+
result = analyzer.answer_question(question)
|
|
205
|
+
|
|
206
|
+
console.print()
|
|
207
|
+
console.print(Panel(
|
|
208
|
+
Markdown(result),
|
|
209
|
+
title=f"[bold cyan]Q: {question}[/bold cyan]",
|
|
210
|
+
border_style="cyan"
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
215
|
+
sys.exit(1)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@rlm.command()
|
|
219
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
220
|
+
@click.option('--type', 'doc_type', default='overview',
|
|
221
|
+
type=click.Choice(['overview', 'api', 'architecture']),
|
|
222
|
+
help='Documentation type')
|
|
223
|
+
@click.option('--max-cost', type=float, default=5.0, help='Max cost in dollars')
|
|
224
|
+
def document(path: Optional[str], doc_type: str, max_cost: float):
|
|
225
|
+
"""
|
|
226
|
+
Generate documentation for ENTIRE codebase
|
|
227
|
+
|
|
228
|
+
RLM will explore and document the full project.
|
|
229
|
+
"""
|
|
230
|
+
project_path = Path(path) if path else Path.cwd()
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
console.print()
|
|
234
|
+
console.print(Panel.fit(
|
|
235
|
+
f"[bold cyan]Generating {doc_type} documentation[/bold cyan]\n\n"
|
|
236
|
+
f"[yellow]RLM will explore entire codebase...[/yellow]",
|
|
237
|
+
title="[bold]Documentation[/bold]",
|
|
238
|
+
border_style="cyan"
|
|
239
|
+
))
|
|
240
|
+
|
|
241
|
+
with console.status("[bold cyan]RLM generating docs..."):
|
|
242
|
+
analyzer = RLMCodebaseAnalyzer(project_path, max_cost=max_cost)
|
|
243
|
+
result = analyzer.generate_documentation(doc_type)
|
|
244
|
+
|
|
245
|
+
console.print()
|
|
246
|
+
console.print(Panel(
|
|
247
|
+
Markdown(result),
|
|
248
|
+
title=f"[bold green]✓ {doc_type.title()} Documentation[/bold green]",
|
|
249
|
+
border_style="green"
|
|
250
|
+
))
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
rlm()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
from groknroll.clients.base_lm import BaseLM
|
|
6
|
+
from groknroll.core.types import ClientBackend
|
|
7
|
+
|
|
8
|
+
load_dotenv()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_client(
|
|
12
|
+
backend: ClientBackend,
|
|
13
|
+
backend_kwargs: dict[str, Any],
|
|
14
|
+
) -> BaseLM:
|
|
15
|
+
"""
|
|
16
|
+
Routes a specific backend and the args (as a dict) to the appropriate client if supported.
|
|
17
|
+
Currently supported backends: ['openai']
|
|
18
|
+
"""
|
|
19
|
+
if backend == "openai":
|
|
20
|
+
from groknroll.clients.openai import OpenAIClient
|
|
21
|
+
|
|
22
|
+
return OpenAIClient(**backend_kwargs)
|
|
23
|
+
elif backend == "vllm":
|
|
24
|
+
from groknroll.clients.openai import OpenAIClient
|
|
25
|
+
|
|
26
|
+
assert "base_url" in backend_kwargs, (
|
|
27
|
+
"base_url is required to be set to local vLLM server address for vLLM"
|
|
28
|
+
)
|
|
29
|
+
return OpenAIClient(**backend_kwargs)
|
|
30
|
+
elif backend == "portkey":
|
|
31
|
+
from groknroll.clients.portkey import PortkeyClient
|
|
32
|
+
|
|
33
|
+
return PortkeyClient(**backend_kwargs)
|
|
34
|
+
elif backend == "openrouter":
|
|
35
|
+
from groknroll.clients.openai import OpenAIClient
|
|
36
|
+
|
|
37
|
+
backend_kwargs.setdefault("base_url", "https://openrouter.ai/api/v1")
|
|
38
|
+
return OpenAIClient(**backend_kwargs)
|
|
39
|
+
elif backend == "vercel":
|
|
40
|
+
from groknroll.clients.openai import OpenAIClient
|
|
41
|
+
|
|
42
|
+
backend_kwargs.setdefault("base_url", "https://ai-gateway.vercel.sh/v1")
|
|
43
|
+
return OpenAIClient(**backend_kwargs)
|
|
44
|
+
elif backend == "litellm":
|
|
45
|
+
from groknroll.clients.litellm import LiteLLMClient
|
|
46
|
+
|
|
47
|
+
return LiteLLMClient(**backend_kwargs)
|
|
48
|
+
elif backend == "anthropic":
|
|
49
|
+
from groknroll.clients.anthropic import AnthropicClient
|
|
50
|
+
|
|
51
|
+
return AnthropicClient(**backend_kwargs)
|
|
52
|
+
elif backend == "gemini":
|
|
53
|
+
from groknroll.clients.gemini import GeminiClient
|
|
54
|
+
|
|
55
|
+
return GeminiClient(**backend_kwargs)
|
|
56
|
+
elif backend == "azure_openai":
|
|
57
|
+
from groknroll.clients.azure_openai import AzureOpenAIClient
|
|
58
|
+
|
|
59
|
+
return AzureOpenAIClient(**backend_kwargs)
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Unknown backend: {backend}. Supported backends: ['openai', 'vllm', 'portkey', 'openrouter', 'litellm', 'anthropic', 'azure_openai', 'gemini', 'vercel']"
|
|
63
|
+
)
|