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,447 @@
1
+ """
2
+ Bash Operations Module
3
+
4
+ Provides bash command execution with output capture and PTY support.
5
+ """
6
+
7
+ import subprocess
8
+ import os
9
+ import select
10
+ import pty
11
+ from pathlib import Path
12
+ from typing import Optional, Dict, Any, List
13
+ from dataclasses import dataclass
14
+ import shlex
15
+
16
+
17
+ @dataclass
18
+ class BashResult:
19
+ """Result of a bash command execution"""
20
+ success: bool
21
+ stdout: str
22
+ stderr: str
23
+ exit_code: int
24
+ command: str
25
+ cwd: Optional[Path] = None
26
+
27
+
28
+ class BashOperations:
29
+ """
30
+ Bash operations for groknroll agent
31
+
32
+ Features:
33
+ - Execute shell commands with output capture
34
+ - PTY support for interactive commands
35
+ - Working directory management
36
+ - Environment variable handling
37
+ - Command history and safety checks
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ project_path: Path,
43
+ default_shell: str = "/bin/bash",
44
+ timeout: int = 300
45
+ ):
46
+ """
47
+ Initialize bash operations
48
+
49
+ Args:
50
+ project_path: Root project path (default working directory)
51
+ default_shell: Shell to use for execution
52
+ timeout: Default command timeout in seconds
53
+ """
54
+ self.project_path = project_path.resolve()
55
+ self.default_shell = default_shell
56
+ self.default_timeout = timeout
57
+ self.command_history: List[BashResult] = []
58
+
59
+ # Dangerous commands that require confirmation
60
+ self.dangerous_commands = {
61
+ "rm -rf /",
62
+ "dd if=",
63
+ "mkfs",
64
+ ":(){ :|:& };:", # Fork bomb
65
+ }
66
+
67
+ def execute(
68
+ self,
69
+ command: str,
70
+ cwd: Optional[Path] = None,
71
+ env: Optional[Dict[str, str]] = None,
72
+ timeout: Optional[int] = None,
73
+ shell: bool = True,
74
+ capture_output: bool = True
75
+ ) -> BashResult:
76
+ """
77
+ Execute bash command
78
+
79
+ Args:
80
+ command: Command to execute
81
+ cwd: Working directory (defaults to project_path)
82
+ env: Environment variables (merged with os.environ)
83
+ timeout: Command timeout in seconds
84
+ shell: Execute in shell (required for pipes, redirects, etc.)
85
+ capture_output: Capture stdout/stderr
86
+
87
+ Returns:
88
+ BashResult
89
+ """
90
+ # Safety check
91
+ if self._is_dangerous(command):
92
+ return BashResult(
93
+ success=False,
94
+ stdout="",
95
+ stderr=f"Dangerous command blocked: {command}",
96
+ exit_code=-1,
97
+ command=command,
98
+ cwd=cwd
99
+ )
100
+
101
+ # Resolve working directory
102
+ work_dir = self._resolve_path(cwd) if cwd else self.project_path
103
+
104
+ # Prepare environment
105
+ exec_env = os.environ.copy()
106
+ if env:
107
+ exec_env.update(env)
108
+
109
+ # Set timeout
110
+ exec_timeout = timeout or self.default_timeout
111
+
112
+ try:
113
+ # Execute command
114
+ if shell:
115
+ result = subprocess.run(
116
+ command,
117
+ shell=True,
118
+ cwd=str(work_dir),
119
+ env=exec_env,
120
+ capture_output=capture_output,
121
+ text=True,
122
+ timeout=exec_timeout,
123
+ executable=self.default_shell
124
+ )
125
+ else:
126
+ # Parse command into args
127
+ args = shlex.split(command)
128
+ result = subprocess.run(
129
+ args,
130
+ cwd=str(work_dir),
131
+ env=exec_env,
132
+ capture_output=capture_output,
133
+ text=True,
134
+ timeout=exec_timeout
135
+ )
136
+
137
+ bash_result = BashResult(
138
+ success=result.returncode == 0,
139
+ stdout=result.stdout if capture_output else "",
140
+ stderr=result.stderr if capture_output else "",
141
+ exit_code=result.returncode,
142
+ command=command,
143
+ cwd=work_dir
144
+ )
145
+
146
+ # Add to history
147
+ self.command_history.append(bash_result)
148
+
149
+ return bash_result
150
+
151
+ except subprocess.TimeoutExpired:
152
+ return BashResult(
153
+ success=False,
154
+ stdout="",
155
+ stderr=f"Command timed out after {exec_timeout} seconds",
156
+ exit_code=-1,
157
+ command=command,
158
+ cwd=work_dir
159
+ )
160
+ except Exception as e:
161
+ return BashResult(
162
+ success=False,
163
+ stdout="",
164
+ stderr=f"Error executing command: {e}",
165
+ exit_code=-1,
166
+ command=command,
167
+ cwd=work_dir
168
+ )
169
+
170
+ def execute_interactive(
171
+ self,
172
+ command: str,
173
+ cwd: Optional[Path] = None,
174
+ env: Optional[Dict[str, str]] = None
175
+ ) -> BashResult:
176
+ """
177
+ Execute command with PTY (for interactive commands like vim, top, etc.)
178
+
179
+ Args:
180
+ command: Command to execute
181
+ cwd: Working directory
182
+ env: Environment variables
183
+
184
+ Returns:
185
+ BashResult
186
+ """
187
+ # Safety check
188
+ if self._is_dangerous(command):
189
+ return BashResult(
190
+ success=False,
191
+ stdout="",
192
+ stderr=f"Dangerous command blocked: {command}",
193
+ exit_code=-1,
194
+ command=command,
195
+ cwd=cwd
196
+ )
197
+
198
+ # Resolve working directory
199
+ work_dir = self._resolve_path(cwd) if cwd else self.project_path
200
+
201
+ # Prepare environment
202
+ exec_env = os.environ.copy()
203
+ if env:
204
+ exec_env.update(env)
205
+
206
+ try:
207
+ # Create PTY
208
+ master, slave = pty.openpty()
209
+
210
+ # Execute command in PTY
211
+ process = subprocess.Popen(
212
+ command,
213
+ shell=True,
214
+ cwd=str(work_dir),
215
+ env=exec_env,
216
+ stdin=slave,
217
+ stdout=slave,
218
+ stderr=slave,
219
+ executable=self.default_shell
220
+ )
221
+
222
+ # Close slave (parent doesn't need it)
223
+ os.close(slave)
224
+
225
+ # Wait for process to complete
226
+ process.wait()
227
+
228
+ # Read output
229
+ output = b""
230
+ while True:
231
+ try:
232
+ # Use select to check if data is available
233
+ ready, _, _ = select.select([master], [], [], 0.1)
234
+ if ready:
235
+ chunk = os.read(master, 4096)
236
+ if not chunk:
237
+ break
238
+ output += chunk
239
+ else:
240
+ break
241
+ except OSError:
242
+ break
243
+
244
+ os.close(master)
245
+
246
+ bash_result = BashResult(
247
+ success=process.returncode == 0,
248
+ stdout=output.decode('utf-8', errors='replace'),
249
+ stderr="",
250
+ exit_code=process.returncode,
251
+ command=command,
252
+ cwd=work_dir
253
+ )
254
+
255
+ self.command_history.append(bash_result)
256
+
257
+ return bash_result
258
+
259
+ except Exception as e:
260
+ return BashResult(
261
+ success=False,
262
+ stdout="",
263
+ stderr=f"Error executing interactive command: {e}",
264
+ exit_code=-1,
265
+ command=command,
266
+ cwd=work_dir
267
+ )
268
+
269
+ def execute_pipeline(
270
+ self,
271
+ commands: List[str],
272
+ cwd: Optional[Path] = None,
273
+ env: Optional[Dict[str, str]] = None,
274
+ timeout: Optional[int] = None
275
+ ) -> BashResult:
276
+ """
277
+ Execute pipeline of commands (command1 | command2 | ...)
278
+
279
+ Args:
280
+ commands: List of commands to pipe together
281
+ cwd: Working directory
282
+ env: Environment variables
283
+ timeout: Command timeout
284
+
285
+ Returns:
286
+ BashResult
287
+ """
288
+ # Create pipeline command
289
+ pipeline = " | ".join(commands)
290
+
291
+ return self.execute(
292
+ command=pipeline,
293
+ cwd=cwd,
294
+ env=env,
295
+ timeout=timeout,
296
+ shell=True
297
+ )
298
+
299
+ def execute_script(
300
+ self,
301
+ script_path: Path,
302
+ args: Optional[List[str]] = None,
303
+ cwd: Optional[Path] = None,
304
+ env: Optional[Dict[str, str]] = None,
305
+ timeout: Optional[int] = None
306
+ ) -> BashResult:
307
+ """
308
+ Execute bash script
309
+
310
+ Args:
311
+ script_path: Path to script file
312
+ args: Script arguments
313
+ cwd: Working directory
314
+ env: Environment variables
315
+ timeout: Command timeout
316
+
317
+ Returns:
318
+ BashResult
319
+ """
320
+ script_file = self._resolve_path(script_path)
321
+
322
+ if not script_file.exists():
323
+ return BashResult(
324
+ success=False,
325
+ stdout="",
326
+ stderr=f"Script not found: {script_path}",
327
+ exit_code=-1,
328
+ command=f"bash {script_path}",
329
+ cwd=cwd
330
+ )
331
+
332
+ # Build command
333
+ command_parts = [self.default_shell, str(script_file)]
334
+ if args:
335
+ command_parts.extend(args)
336
+
337
+ command = " ".join(shlex.quote(part) for part in command_parts)
338
+
339
+ return self.execute(
340
+ command=command,
341
+ cwd=cwd,
342
+ env=env,
343
+ timeout=timeout,
344
+ shell=False
345
+ )
346
+
347
+ def get_history(self, limit: int = 10) -> List[BashResult]:
348
+ """Get recent command history"""
349
+ return self.command_history[-limit:]
350
+
351
+ def clear_history(self) -> None:
352
+ """Clear command history"""
353
+ self.command_history.clear()
354
+
355
+ # =========================================================================
356
+ # Common Commands (Convenience Methods)
357
+ # =========================================================================
358
+
359
+ def ls(self, path: Optional[Path] = None, options: str = "-la") -> BashResult:
360
+ """List directory contents"""
361
+ target = self._resolve_path(path) if path else self.project_path
362
+ return self.execute(f"ls {options} {shlex.quote(str(target))}")
363
+
364
+ def pwd(self) -> BashResult:
365
+ """Print working directory"""
366
+ return self.execute("pwd", cwd=self.project_path)
367
+
368
+ def which(self, command: str) -> BashResult:
369
+ """Find command location"""
370
+ return self.execute(f"which {shlex.quote(command)}")
371
+
372
+ def env_vars(self) -> BashResult:
373
+ """List environment variables"""
374
+ return self.execute("env")
375
+
376
+ def ps(self, options: str = "aux") -> BashResult:
377
+ """List processes"""
378
+ return self.execute(f"ps {options}")
379
+
380
+ def grep(
381
+ self,
382
+ pattern: str,
383
+ path: Optional[Path] = None,
384
+ options: str = "-r"
385
+ ) -> BashResult:
386
+ """Search for pattern in files"""
387
+ target = self._resolve_path(path) if path else self.project_path
388
+ return self.execute(
389
+ f"grep {options} {shlex.quote(pattern)} {shlex.quote(str(target))}"
390
+ )
391
+
392
+ def find(
393
+ self,
394
+ path: Optional[Path] = None,
395
+ name: Optional[str] = None,
396
+ type_filter: Optional[str] = None
397
+ ) -> BashResult:
398
+ """Find files"""
399
+ target = self._resolve_path(path) if path else self.project_path
400
+ command_parts = ["find", shlex.quote(str(target))]
401
+
402
+ if type_filter:
403
+ command_parts.extend(["-type", type_filter])
404
+
405
+ if name:
406
+ command_parts.extend(["-name", shlex.quote(name)])
407
+
408
+ return self.execute(" ".join(command_parts))
409
+
410
+ # =========================================================================
411
+ # Helper Methods
412
+ # =========================================================================
413
+
414
+ def _resolve_path(self, path: Path) -> Path:
415
+ """Resolve path relative to project root"""
416
+ if path.is_absolute():
417
+ return path.resolve()
418
+ return (self.project_path / path).resolve()
419
+
420
+ def _is_dangerous(self, command: str) -> bool:
421
+ """Check if command is potentially dangerous"""
422
+ # Check against known dangerous patterns
423
+ for dangerous in self.dangerous_commands:
424
+ if dangerous in command:
425
+ return True
426
+
427
+ # Additional checks
428
+ dangerous_patterns = [
429
+ "rm -rf /",
430
+ "> /dev/sda",
431
+ "chmod -R 777 /",
432
+ ]
433
+
434
+ for pattern in dangerous_patterns:
435
+ if pattern in command:
436
+ return True
437
+
438
+ return False
439
+
440
+ def test_command_available(self, command: str) -> bool:
441
+ """Test if command is available in PATH"""
442
+ result = self.which(command)
443
+ return result.success
444
+
445
+ def get_shell_version(self) -> BashResult:
446
+ """Get shell version"""
447
+ return self.execute(f"{self.default_shell} --version")