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
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
+ )