code-puppy 0.0.46__tar.gz → 0.0.48__tar.gz

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 (28) hide show
  1. {code_puppy-0.0.46 → code_puppy-0.0.48}/PKG-INFO +1 -1
  2. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/config.py +1 -1
  3. code_puppy-0.0.48/code_puppy/tools/command_runner.py +74 -0
  4. {code_puppy-0.0.46 → code_puppy-0.0.48}/pyproject.toml +1 -1
  5. code_puppy-0.0.46/code_puppy/tools/command_runner.py +0 -262
  6. {code_puppy-0.0.46 → code_puppy-0.0.48}/.gitignore +0 -0
  7. {code_puppy-0.0.46 → code_puppy-0.0.48}/LICENSE +0 -0
  8. {code_puppy-0.0.46 → code_puppy-0.0.48}/README.md +0 -0
  9. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/agent.py +0 -0
  11. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/agent_prompts.py +0 -0
  12. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/__init__.py +0 -0
  13. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/file_path_completion.py +0 -0
  14. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/meta_command_handler.py +0 -0
  15. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/model_picker_completion.py +0 -0
  16. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  17. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/command_line/utils.py +0 -0
  18. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/main.py +0 -0
  19. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/model_factory.py +0 -0
  20. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/models.json +0 -0
  21. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/session_memory.py +0 -0
  22. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/__init__.py +0 -0
  23. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/code_map.py +0 -0
  24. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/common.py +0 -0
  25. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/file_modifications.py +0 -0
  26. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/file_operations.py +0 -0
  27. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/tools/web_search.py +0 -0
  28. {code_puppy-0.0.46 → code_puppy-0.0.48}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.46
3
+ Version: 0.0.48
4
4
  Summary: Code generation agent
5
5
  Author: Michael Pfaffenberger
6
6
  License: MIT
@@ -55,7 +55,7 @@ def get_owner_name():
55
55
  # --- MODEL STICKY EXTENSION STARTS HERE ---
56
56
  def get_model_name():
57
57
  """Returns the last used model name stored in config, or None if unset."""
58
- return get_value("model") or "gpt-4o"
58
+ return get_value("model") or "gpt-4.1"
59
59
 
60
60
  def set_model_name(model: str):
61
61
  """Sets the model name in the persistent config file."""
@@ -0,0 +1,74 @@
1
+ # command_runner.py
2
+ import subprocess
3
+ import time
4
+ import os
5
+ from typing import Dict, Any
6
+ from code_puppy.tools.common import console
7
+ from pydantic_ai import RunContext
8
+ from rich.markdown import Markdown
9
+ from rich.syntax import Syntax
10
+
11
+ def register_command_runner_tools(agent):
12
+ @agent.tool
13
+ def run_shell_command(context: RunContext, command: str, cwd: str = None, timeout: int = 60) -> Dict[str, Any]:
14
+ if not command or not command.strip():
15
+ console.print("[bold red]Error:[/bold red] Command cannot be empty")
16
+ return {"error": "Command cannot be empty"}
17
+ console.print("\n[bold white on blue] SHELL COMMAND [/bold white on blue]")
18
+ console.print(f"[bold green]$ {command}[/bold green]")
19
+ if cwd:
20
+ console.print(f"[dim]Working directory: {cwd}[/dim]")
21
+ console.print("[dim]" + "-" * 60 + "[/dim]")
22
+ yolo_mode = os.getenv("YOLO_MODE", "false").lower() == "true"
23
+ if not yolo_mode:
24
+ user_input = input("Are you sure you want to run this command? (yes/no): ")
25
+ if user_input.strip().lower() not in {"yes", "y"}:
26
+ console.print("[bold yellow]Command execution canceled by user.[/bold yellow]")
27
+ return {"success": False, "command": command, "error": "User canceled command execution"}
28
+ try:
29
+ start_time = time.time()
30
+ process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd)
31
+ try:
32
+ stdout, stderr = process.communicate(timeout=timeout)
33
+ exit_code = process.returncode
34
+ execution_time = time.time() - start_time
35
+ if stdout.strip():
36
+ console.print("[bold white]STDOUT:[/bold white]")
37
+ console.print(Syntax(stdout.strip(), "bash", theme="monokai", background_color="default"))
38
+ if stderr.strip():
39
+ console.print("[bold yellow]STDERR:[/bold yellow]")
40
+ console.print(Syntax(stderr.strip(), "bash", theme="monokai", background_color="default"))
41
+ if exit_code == 0:
42
+ console.print(f"[bold green]✓ Command completed successfully[/bold green] [dim](took {execution_time:.2f}s)[/dim]")
43
+ else:
44
+ console.print(f"[bold red]✗ Command failed with exit code {exit_code}[/bold red] [dim](took {execution_time:.2f}s)[/dim]")
45
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
46
+ return {"success": exit_code == 0, "command": command, "stdout": stdout, "stderr": stderr, "exit_code": exit_code, "execution_time": execution_time, "timeout": False}
47
+ except subprocess.TimeoutExpired:
48
+ process.kill()
49
+ stdout, stderr = process.communicate()
50
+ execution_time = time.time() - start_time
51
+ if stdout.strip():
52
+ console.print("[bold white]STDOUT (incomplete due to timeout):[/bold white]")
53
+ console.print(Syntax(stdout.strip(), "bash", theme="monokai", background_color="default"))
54
+ if stderr.strip():
55
+ console.print("[bold yellow]STDERR:[/bold yellow]")
56
+ console.print(Syntax(stderr.strip(), "bash", theme="monokai", background_color="default"))
57
+ console.print(f"[bold red]⏱ Command timed out after {timeout} seconds[/bold red] [dim](ran for {execution_time:.2f}s)[/dim]")
58
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
59
+ return {"success": False,"command": command, "stdout": stdout[-1000:], "stderr": stderr[-1000:], "exit_code": None, "execution_time": execution_time, "timeout": True, "error": f"Command timed out after {timeout} seconds"}
60
+ except Exception as e:
61
+ console.print_exception(show_locals=True)
62
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
63
+ return {"success": False, "command": command, "error": f"Error executing command: {str(e)}", "stdout": "", "stderr": "", "exit_code": -1, "timeout": False}
64
+
65
+ @agent.tool
66
+ def share_your_reasoning(context: RunContext, reasoning: str, next_steps: str = None) -> Dict[str, Any]:
67
+ console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
68
+ console.print("[bold cyan]Current reasoning:[/bold cyan]")
69
+ console.print(Markdown(reasoning))
70
+ if next_steps and next_steps.strip():
71
+ console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
72
+ console.print(Markdown(next_steps))
73
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
74
+ return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.46"
7
+ version = "0.0.48"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,262 +0,0 @@
1
- # command_runner.py
2
- import subprocess
3
- import time
4
- import os
5
- from typing import Dict, Any, Optional
6
- from code_puppy.tools.common import console
7
- from pydantic_ai import RunContext
8
- from rich.markdown import Markdown
9
- from rich.syntax import Syntax
10
- import shlex
11
- import re
12
- import threading, queue, termios, tty, sys
13
- import select
14
-
15
- def register_command_runner_tools(agent):
16
- @agent.tool
17
- def run_shell_command(context: RunContext, command: str, cwd: str = None, timeout: int = 60) -> Dict[str, Any]:
18
- if not command or not command.strip():
19
- console.print("[bold red]Error:[/bold red] Command cannot be empty")
20
- return {"error": "Command cannot be empty"}
21
- console.print("\n[bold white on blue] SHELL COMMAND [/bold white on blue]")
22
- console.print(f"[bold green]$ {command}[/bold green]")
23
- if cwd:
24
- console.print(f"[dim]Working directory: {cwd}[/dim]")
25
- console.print("[dim]" + "-" * 60 + "[/dim]")
26
- yolo_mode = os.getenv("YOLO_MODE", "false").lower() == "true"
27
- if not yolo_mode:
28
- user_input = input("Are you sure you want to run this command? (yes/no): ")
29
- if user_input.strip().lower() not in {"yes", "y"}:
30
- console.print("[bold yellow]Command execution canceled by user.[/bold yellow]")
31
- return {"success": False, "command": command, "error": "User canceled command execution"}
32
-
33
- # ------------------------------------------------------------------
34
- # Basic safety guardrails
35
- # ------------------------------------------------------------------
36
- BLOCKED_PATTERNS = [
37
- r"\brm\b.*\*(?!(\.\w+))", # rm with wildcard
38
- r"\brm\s+-rf\s+/", # rm -rf /
39
- r"\bsudo\s+rm", # any sudo rm
40
- r"\breboot\b", # system reboot
41
- r"\bshutdown\b", # system shutdown
42
- ]
43
- lower_cmd = command.lower()
44
- for pattern in BLOCKED_PATTERNS:
45
- if re.search(pattern, lower_cmd):
46
- console.print(f"[bold red]Refused to run dangerous command:[/bold red] {command}")
47
- return {"success": False, "command": command, "error": "Command blocked by safety guard"}
48
-
49
- # Extra guard: prompt again if command starts with `rm` or uses `--force`
50
- tokens = shlex.split(command)
51
- if tokens and tokens[0] == "rm":
52
- console.print("[bold yellow]Warning:[/bold yellow] You are about to run an 'rm' command.")
53
- extra = input("Type 'I understand' to proceed: ")
54
- if extra.strip().lower() != "i understand":
55
- console.print("[bold yellow]Command execution canceled by user.[/bold yellow]")
56
- return {"success": False, "command": command, "error": "User canceled command execution"}
57
-
58
- try:
59
- start_time = time.time()
60
- process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd)
61
-
62
- # Use a queue to shuttle output from the reader thread
63
- q: "queue.Queue[str]" = queue.Queue()
64
-
65
- def _reader(pipe, tag):
66
- for line in iter(pipe.readline, ''):
67
- q.put((tag, line))
68
- pipe.close()
69
-
70
- stdout_thread = threading.Thread(target=_reader, args=(process.stdout, 'STDOUT'), daemon=True)
71
- stderr_thread = threading.Thread(target=_reader, args=(process.stderr, 'STDERR'), daemon=True)
72
- stdout_thread.start()
73
- stderr_thread.start()
74
-
75
- # Save terminal state and switch to cbreak to capture ESC presses.
76
- fd = sys.stdin.fileno()
77
- old_settings = termios.tcgetattr(fd)
78
- tty.setcbreak(fd)
79
- ESC_CODE = 27
80
- timed_out = False
81
- try:
82
- while True:
83
- try:
84
- tag, line = q.get_nowait()
85
- if line.strip():
86
- if tag == 'STDOUT':
87
- console.print(line.rstrip())
88
- else:
89
- console.print(f"[bold yellow]{line.rstrip()}[/bold yellow]")
90
- except queue.Empty:
91
- pass
92
-
93
- if process.poll() is not None:
94
- break # command finished
95
-
96
- # Check for ESC key press
97
- if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
98
- ch = sys.stdin.read(1)
99
- if ord(ch) == ESC_CODE:
100
- console.print("[bold red]⏹ ESC detected – terminating command...[/bold red]")
101
- process.terminate()
102
- timed_out = True
103
- break
104
-
105
- if time.time() - start_time > timeout:
106
- console.print(f"[bold red]⏱ Command timed out after {timeout} seconds – killing...[/bold red]")
107
- process.terminate()
108
- timed_out = True
109
- break
110
-
111
- finally:
112
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
113
-
114
- stdout_thread.join(timeout=1)
115
- stderr_thread.join(timeout=1)
116
- stdout_remaining = ''.join(line for tag, line in list(q.queue) if tag == 'STDOUT')
117
- stderr_remaining = ''.join(line for tag, line in list(q.queue) if tag == 'STDERR')
118
- exit_code = process.returncode
119
- execution_time = time.time() - start_time
120
- success = (exit_code == 0) and not timed_out
121
- return {
122
- "success": success,
123
- "command": command,
124
- "stdout": stdout_remaining,
125
- "stderr": stderr_remaining,
126
- "exit_code": exit_code,
127
- "execution_time": execution_time,
128
- "timeout": timed_out,
129
- "error": None if success else "Command interrupted" if timed_out else "Command failed",
130
- }
131
- except Exception as e:
132
- console.print_exception(show_locals=True)
133
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
134
- return {"success": False, "command": command, "error": f"Error executing command: {str(e)}", "stdout": "", "stderr": "", "exit_code": -1, "timeout": False}
135
-
136
- @agent.tool
137
- def share_your_reasoning(context: RunContext, reasoning: str, next_steps: str = None) -> Dict[str, Any]:
138
- console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
139
- console.print("[bold cyan]Current reasoning:[/bold cyan]")
140
- console.print(Markdown(reasoning))
141
- if next_steps and next_steps.strip():
142
- console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
143
- console.print(Markdown(next_steps))
144
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
145
- return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
146
-
147
- # ---------------------------------------------------------------------------
148
- # Module-level helper functions (exposed for unit tests _and_ used as tools)
149
- # ---------------------------------------------------------------------------
150
-
151
- def run_shell_command(context: Optional[RunContext], command: str, cwd: str = None, timeout: int = 60) -> Dict[str, Any]:
152
- import subprocess, time, os as _os
153
- from rich.syntax import Syntax
154
- from code_puppy.tools.common import console as _console
155
- if not command or not command.strip():
156
- _console.print("[bold red]Error:[/bold red] Command cannot be empty")
157
- return {"error": "Command cannot be empty"}
158
- _console.print("\n[bold white on blue] SHELL COMMAND [/bold white on blue]")
159
- _console.print(f"[bold green]$ {command}[/bold green]")
160
- if cwd:
161
- _console.print(f"[dim]Working directory: {cwd}[/dim]")
162
- _console.print("[dim]" + "-" * 60 + "[/dim]")
163
- yolo_mode = _os.getenv("YOLO_MODE", "false").lower() == "true"
164
- if not yolo_mode:
165
- user_input = input("Are you sure you want to run this command? (yes/no): ")
166
- if user_input.strip().lower() not in {"yes", "y"}:
167
- _console.print("[bold yellow]Command execution canceled by user.[/bold yellow]")
168
- return {"success": False, "command": command, "error": "User canceled command execution"}
169
- try:
170
- start_time = time.time()
171
- process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd)
172
-
173
- # Use a queue to shuttle output from the reader thread
174
- q: "queue.Queue[str]" = queue.Queue()
175
-
176
- def _reader(pipe, tag):
177
- for line in iter(pipe.readline, ''):
178
- q.put((tag, line))
179
- pipe.close()
180
-
181
- stdout_thread = threading.Thread(target=_reader, args=(process.stdout, 'STDOUT'), daemon=True)
182
- stderr_thread = threading.Thread(target=_reader, args=(process.stderr, 'STDERR'), daemon=True)
183
- stdout_thread.start()
184
- stderr_thread.start()
185
-
186
- # Save terminal state and switch to cbreak to capture ESC presses.
187
- fd = sys.stdin.fileno()
188
- old_settings = termios.tcgetattr(fd)
189
- tty.setcbreak(fd)
190
- ESC_CODE = 27
191
- timed_out = False
192
- try:
193
- while True:
194
- try:
195
- tag, line = q.get_nowait()
196
- if line.strip():
197
- if tag == 'STDOUT':
198
- _console.print(line.rstrip())
199
- else:
200
- _console.print(f"[bold yellow]{line.rstrip()}[/bold yellow]")
201
- except queue.Empty:
202
- pass
203
-
204
- if process.poll() is not None:
205
- break # command finished
206
-
207
- # Check for ESC key press
208
- if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
209
- ch = sys.stdin.read(1)
210
- if ord(ch) == ESC_CODE:
211
- _console.print("[bold red]⏹ ESC detected – terminating command...[/bold red]")
212
- process.terminate()
213
- timed_out = True
214
- break
215
-
216
- if time.time() - start_time > timeout:
217
- _console.print(f"[bold red]⏱ Command timed out after {timeout} seconds – killing...[/bold red]")
218
- process.terminate()
219
- timed_out = True
220
- break
221
-
222
- finally:
223
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
224
-
225
- stdout_thread.join(timeout=1)
226
- stderr_thread.join(timeout=1)
227
- stdout_remaining = ''.join(line for tag, line in list(q.queue) if tag == 'STDOUT')
228
- stderr_remaining = ''.join(line for tag, line in list(q.queue) if tag == 'STDERR')
229
- exit_code = process.returncode
230
- execution_time = time.time() - start_time
231
- success = (exit_code == 0) and not timed_out
232
- return {
233
- "success": success,
234
- "command": command,
235
- "stdout": stdout_remaining,
236
- "stderr": stderr_remaining,
237
- "exit_code": exit_code,
238
- "execution_time": execution_time,
239
- "timeout": timed_out,
240
- "error": None if success else "Command interrupted" if timed_out else "Command failed",
241
- }
242
- except Exception as e:
243
- _console.print_exception(show_locals=True)
244
- _console.print("[dim]" + "-" * 60 + "[/dim]\n")
245
- return {"success": False, "command": command, "error": f"Error executing command: {str(e)}", "stdout": "", "stderr": "", "exit_code": -1, "timeout": False}
246
-
247
-
248
- def share_your_reasoning(context: Optional[RunContext], reasoning: str, next_steps: str | None = None) -> Dict[str, Any]:
249
- from rich.markdown import Markdown
250
- from code_puppy.tools.common import console as _console
251
- _console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
252
- _console.print("[bold cyan]Current reasoning:[/bold cyan]")
253
- _console.print(Markdown(reasoning))
254
- if next_steps and next_steps.strip():
255
- _console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
256
- _console.print(Markdown(next_steps))
257
- _console.print("[dim]" + "-" * 60 + "[/dim]\n")
258
- return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
259
-
260
- # ---------------------------------------------------------------------------
261
- # Original registration function now simply registers the helpers above
262
- # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes