code-puppy 0.0.53__py3-none-any.whl → 0.0.54__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.
@@ -3,20 +3,24 @@ from pathlib import Path
3
3
  from datetime import datetime, timedelta
4
4
  from typing import Any, List, Dict, Optional
5
5
 
6
- DEFAULT_MEMORY_PATH = Path('.puppy_session_memory.json')
6
+ DEFAULT_MEMORY_PATH = Path(".puppy_session_memory.json")
7
+
7
8
 
8
9
  class SessionMemory:
9
10
  """
10
11
  Simple persistent memory for Code Puppy agent sessions.
11
12
  Stores short histories of tasks, notes, user preferences, and watched files.
12
13
  """
13
- def __init__(self, storage_path: Path = DEFAULT_MEMORY_PATH, memory_limit: int = 128):
14
+
15
+ def __init__(
16
+ self, storage_path: Path = DEFAULT_MEMORY_PATH, memory_limit: int = 128
17
+ ):
14
18
  self.storage_path = storage_path
15
19
  self.memory_limit = memory_limit
16
20
  self._data = {
17
- 'history': [], # List of task/event dicts
18
- 'user_preferences': {},
19
- 'watched_files': [],
21
+ "history": [], # List of task/event dicts
22
+ "user_preferences": {},
23
+ "watched_files": [],
20
24
  }
21
25
  self._load()
22
26
 
@@ -25,47 +29,55 @@ class SessionMemory:
25
29
  try:
26
30
  self._data = json.loads(self.storage_path.read_text())
27
31
  except Exception:
28
- self._data = {'history': [], 'user_preferences': {}, 'watched_files': []}
32
+ self._data = {
33
+ "history": [],
34
+ "user_preferences": {},
35
+ "watched_files": [],
36
+ }
29
37
 
30
38
  def _save(self):
31
39
  try:
32
40
  self.storage_path.write_text(json.dumps(self._data, indent=2))
33
- except Exception as e:
41
+ except Exception:
34
42
  pass # Don't crash the agent for memory fails
35
43
 
36
44
  def log_task(self, description: str, extras: Optional[Dict[str, Any]] = None):
37
45
  entry = {
38
- 'timestamp': datetime.utcnow().isoformat(),
39
- 'description': description,
46
+ "timestamp": datetime.utcnow().isoformat(),
47
+ "description": description,
40
48
  }
41
49
  if extras:
42
50
  entry.update(extras)
43
- self._data['history'].append(entry)
51
+ self._data["history"].append(entry)
44
52
  # Trim memory
45
- self._data['history'] = self._data['history'][-self.memory_limit:]
53
+ self._data["history"] = self._data["history"][-self.memory_limit :]
46
54
  self._save()
47
55
 
48
56
  def get_history(self, within_minutes: Optional[int] = None) -> List[Dict[str, Any]]:
49
57
  if not within_minutes:
50
- return list(self._data['history'])
58
+ return list(self._data["history"])
51
59
  cutoff = datetime.utcnow() - timedelta(minutes=within_minutes)
52
- return [h for h in self._data['history'] if datetime.fromisoformat(h['timestamp']) >= cutoff]
60
+ return [
61
+ h
62
+ for h in self._data["history"]
63
+ if datetime.fromisoformat(h["timestamp"]) >= cutoff
64
+ ]
53
65
 
54
66
  def set_preference(self, key: str, value: Any):
55
- self._data['user_preferences'][key] = value
67
+ self._data["user_preferences"][key] = value
56
68
  self._save()
57
69
 
58
70
  def get_preference(self, key: str, default: Any = None) -> Any:
59
- return self._data['user_preferences'].get(key, default)
71
+ return self._data["user_preferences"].get(key, default)
60
72
 
61
73
  def add_watched_file(self, path: str):
62
- if path not in self._data['watched_files']:
63
- self._data['watched_files'].append(path)
74
+ if path not in self._data["watched_files"]:
75
+ self._data["watched_files"].append(path)
64
76
  self._save()
65
77
 
66
78
  def list_watched_files(self) -> List[str]:
67
- return list(self._data['watched_files'])
79
+ return list(self._data["watched_files"])
68
80
 
69
81
  def clear(self):
70
- self._data = {'history': [], 'user_preferences': {}, 'watched_files': []}
82
+ self._data = {"history": [], "user_preferences": {}, "watched_files": []}
71
83
  self._save()
@@ -3,6 +3,7 @@ from code_puppy.tools.file_modifications import register_file_modifications_tool
3
3
  from code_puppy.tools.command_runner import register_command_runner_tools
4
4
  from code_puppy.tools.web_search import register_web_search_tools
5
5
 
6
+
6
7
  def register_all_tools(agent):
7
8
  """Register all available tools to the provided agent."""
8
9
  register_file_operations_tools(agent)
@@ -1,9 +1,7 @@
1
1
  import os
2
2
  import ast
3
- from typing import List, Tuple
4
3
  from rich.tree import Tree
5
4
  from rich.text import Text
6
- from pathlib import Path
7
5
  import pathspec
8
6
 
9
7
 
@@ -36,8 +34,8 @@ def map_python_file(file_path: str, show_doc: bool = True) -> Tree:
36
34
  if doc:
37
35
  t.add(Text(f'"{doc}"', style="dim"))
38
36
  # Add inner functions
39
- if hasattr(node, 'body'):
40
- for subnode in getattr(node, 'body'):
37
+ if hasattr(node, "body"):
38
+ for subnode in getattr(node, "body"):
41
39
  subsum = summarize_node(subnode)
42
40
  if subsum:
43
41
  sub_t = Tree(subsum)
@@ -50,13 +48,14 @@ def map_python_file(file_path: str, show_doc: bool = True) -> Tree:
50
48
 
51
49
 
52
50
  def load_gitignore(directory: str):
53
- gitignore_file = os.path.join(directory, '.gitignore')
51
+ gitignore_file = os.path.join(directory, ".gitignore")
54
52
  if os.path.exists(gitignore_file):
55
- with open(gitignore_file, 'r') as f:
56
- spec = pathspec.PathSpec.from_lines('gitwildmatch', f)
53
+ with open(gitignore_file, "r") as f:
54
+ spec = pathspec.PathSpec.from_lines("gitwildmatch", f)
57
55
  return spec
58
56
  else:
59
- return pathspec.PathSpec.from_lines('gitwildmatch', [])
57
+ return pathspec.PathSpec.from_lines("gitwildmatch", [])
58
+
60
59
 
61
60
  def make_code_map(directory: str, show_doc: bool = True) -> Tree:
62
61
  """
@@ -71,16 +70,22 @@ def make_code_map(directory: str, show_doc: bool = True) -> Tree:
71
70
  for root, dirs, files in os.walk(directory):
72
71
  rel_root = os.path.relpath(root, abs_directory)
73
72
  # Remove ignored directories in-place for os.walk to not descend
74
- dirs[:] = [d for d in dirs if not spec.match_file(os.path.normpath(os.path.join(rel_root, d)))]
73
+ dirs[:] = [
74
+ d
75
+ for d in dirs
76
+ if not spec.match_file(os.path.normpath(os.path.join(rel_root, d)))
77
+ ]
75
78
  for fname in files:
76
79
  rel_file = os.path.normpath(os.path.join(rel_root, fname))
77
- if fname.endswith('.py') and not fname.startswith("__"):
80
+ if fname.endswith(".py") and not fname.startswith("__"):
78
81
  if not spec.match_file(rel_file):
79
82
  fpath = os.path.join(root, fname)
80
83
  try:
81
84
  file_tree = map_python_file(fpath, show_doc=show_doc)
82
85
  base_tree.add(file_tree)
83
86
  except Exception as e:
84
- err = Tree(Text(f"[error reading {fname}: {e}]", style="bold red"))
87
+ err = Tree(
88
+ Text(f"[error reading {fname}: {e}]", style="bold red")
89
+ )
85
90
  base_tree.add(err)
86
91
  return base_tree
@@ -1,75 +1,172 @@
1
- # command_runner.py
2
1
  import subprocess
3
2
  import time
4
- import os
5
3
  from typing import Dict, Any
6
4
  from code_puppy.tools.common import console
7
5
  from pydantic_ai import RunContext
8
6
  from rich.markdown import Markdown
9
7
  from rich.syntax import Syntax
10
8
 
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
- from code_puppy.config import get_yolo_mode
23
- yolo_mode = get_yolo_mode()
24
- if not yolo_mode:
25
- user_input = input("Are you sure you want to run this command? (yes/no): ")
26
- if user_input.strip().lower() not in {"yes", "y"}:
27
- console.print("[bold yellow]Command execution canceled by user.[/bold yellow]")
28
- return {"success": False, "command": command, "error": "User canceled command execution"}
9
+
10
+ def run_shell_command(
11
+ context: RunContext, command: str, cwd: str = None, timeout: int = 60
12
+ ) -> Dict[str, Any]:
13
+ if not command or not command.strip():
14
+ console.print("[bold red]Error:[/bold red] Command cannot be empty")
15
+ return {"error": "Command cannot be empty"}
16
+ console.print(
17
+ f"\n[bold white on blue] SHELL COMMAND [/bold white on blue] \U0001f4c2 [bold green]$ {command}[/bold green]"
18
+ )
19
+ if cwd:
20
+ console.print(f"[dim]Working directory: {cwd}[/dim]")
21
+ console.print("[dim]" + "-" * 60 + "[/dim]")
22
+ from code_puppy.config import get_yolo_mode
23
+
24
+ yolo_mode = get_yolo_mode()
25
+ if not yolo_mode:
26
+ user_input = input("Are you sure you want to run this command? (yes/no): ")
27
+ if user_input.strip().lower() not in {"yes", "y"}:
28
+ console.print(
29
+ "[bold yellow]Command execution canceled by user.[/bold yellow]"
30
+ )
31
+ return {
32
+ "success": False,
33
+ "command": command,
34
+ "error": "User canceled command execution",
35
+ }
36
+ try:
37
+ start_time = time.time()
38
+ process = subprocess.Popen(
39
+ command,
40
+ shell=True,
41
+ stdout=subprocess.PIPE,
42
+ stderr=subprocess.PIPE,
43
+ text=True,
44
+ cwd=cwd,
45
+ )
29
46
  try:
30
- start_time = time.time()
31
- process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=cwd)
32
- try:
33
- stdout, stderr = process.communicate(timeout=timeout)
34
- exit_code = process.returncode
35
- execution_time = time.time() - start_time
36
- if stdout.strip():
37
- console.print("[bold white]STDOUT:[/bold white]")
38
- console.print(Syntax(stdout.strip(), "bash", theme="monokai", background_color="default"))
39
- if stderr.strip():
40
- console.print("[bold yellow]STDERR:[/bold yellow]")
41
- console.print(Syntax(stderr.strip(), "bash", theme="monokai", background_color="default"))
42
- if exit_code == 0:
43
- console.print(f"[bold green]✓ Command completed successfully[/bold green] [dim](took {execution_time:.2f}s)[/dim]")
44
- else:
45
- console.print(f"[bold red]✗ Command failed with exit code {exit_code}[/bold red] [dim](took {execution_time:.2f}s)[/dim]")
46
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
47
- return {"success": exit_code == 0, "command": command, "stdout": stdout, "stderr": stderr, "exit_code": exit_code, "execution_time": execution_time, "timeout": False}
48
- except subprocess.TimeoutExpired:
49
- process.kill()
50
- stdout, stderr = process.communicate()
51
- execution_time = time.time() - start_time
52
- if stdout.strip():
53
- console.print("[bold white]STDOUT (incomplete due to timeout):[/bold white]")
54
- console.print(Syntax(stdout.strip(), "bash", theme="monokai", background_color="default"))
55
- if stderr.strip():
56
- console.print("[bold yellow]STDERR:[/bold yellow]")
57
- console.print(Syntax(stderr.strip(), "bash", theme="monokai", background_color="default"))
58
- console.print(f"[bold red]⏱ Command timed out after {timeout} seconds[/bold red] [dim](ran for {execution_time:.2f}s)[/dim]")
59
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
60
- 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"}
61
- except Exception as e:
62
- console.print_exception(show_locals=True)
47
+ stdout, stderr = process.communicate(timeout=timeout)
48
+ exit_code = process.returncode
49
+ execution_time = time.time() - start_time
50
+ if stdout.strip():
51
+ console.print("[bold white]STDOUT:[/bold white]")
52
+ console.print(
53
+ Syntax(
54
+ stdout.strip(),
55
+ "bash",
56
+ theme="monokai",
57
+ background_color="default",
58
+ )
59
+ )
60
+ else:
61
+ console.print("[yellow]No STDOUT output[/yellow]")
62
+ if stderr.strip():
63
+ console.print("[bold yellow]STDERR:[/bold yellow]")
64
+ console.print(
65
+ Syntax(
66
+ stderr.strip(),
67
+ "bash",
68
+ theme="monokai",
69
+ background_color="default",
70
+ )
71
+ )
72
+ if exit_code == 0:
73
+ console.print(
74
+ f"[bold green]✓ Command completed successfully[/bold green] [dim](took {execution_time:.2f}s)[/dim]"
75
+ )
76
+ else:
77
+ console.print(
78
+ f"[bold red]✗ Command failed with exit code {exit_code}[/bold red] [dim](took {execution_time:.2f}s)[/dim]"
79
+ )
80
+ if not stdout.strip() and not stderr.strip():
81
+ console.print(
82
+ "[bold yellow]This command produced no output at all![/bold yellow]"
83
+ )
63
84
  console.print("[dim]" + "-" * 60 + "[/dim]\n")
64
- return {"success": False, "command": command, "error": f"Error executing command: {str(e)}", "stdout": "", "stderr": "", "exit_code": -1, "timeout": False}
85
+ return {
86
+ "success": exit_code == 0,
87
+ "command": command,
88
+ "stdout": stdout,
89
+ "stderr": stderr,
90
+ "exit_code": exit_code,
91
+ "execution_time": execution_time,
92
+ "timeout": False,
93
+ }
94
+ except subprocess.TimeoutExpired:
95
+ process.kill()
96
+ stdout, stderr = process.communicate()
97
+ execution_time = time.time() - start_time
98
+ if stdout.strip():
99
+ console.print(
100
+ "[bold white]STDOUT (incomplete due to timeout):[/bold white]"
101
+ )
102
+ console.print(
103
+ Syntax(
104
+ stdout.strip(),
105
+ "bash",
106
+ theme="monokai",
107
+ background_color="default",
108
+ )
109
+ )
110
+ if stderr.strip():
111
+ console.print("[bold yellow]STDERR:[/bold yellow]")
112
+ console.print(
113
+ Syntax(
114
+ stderr.strip(),
115
+ "bash",
116
+ theme="monokai",
117
+ background_color="default",
118
+ )
119
+ )
120
+ console.print(
121
+ f"[bold red]⏱ Command timed out after {timeout} seconds[/bold red] [dim](ran for {execution_time:.2f}s)[/dim]"
122
+ )
123
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
124
+ return {
125
+ "success": False,
126
+ "command": command,
127
+ "stdout": stdout[-1000:],
128
+ "stderr": stderr[-1000:],
129
+ "exit_code": None,
130
+ "execution_time": execution_time,
131
+ "timeout": True,
132
+ "error": f"Command timed out after {timeout} seconds",
133
+ }
134
+ except Exception as e:
135
+ console.print_exception(show_locals=True)
136
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
137
+ return {
138
+ "success": False,
139
+ "command": command,
140
+ "error": f"Error executing command: {str(e)}",
141
+ "stdout": "",
142
+ "stderr": "",
143
+ "exit_code": -1,
144
+ "timeout": False,
145
+ }
146
+
65
147
 
148
+ def share_your_reasoning(
149
+ context: RunContext, reasoning: str, next_steps: str = None
150
+ ) -> Dict[str, Any]:
151
+ console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
152
+ console.print("[bold cyan]Current reasoning:[/bold cyan]")
153
+ console.print(Markdown(reasoning))
154
+ if next_steps and next_steps.strip():
155
+ console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
156
+ console.print(Markdown(next_steps))
157
+ console.print("[dim]" + "-" * 60 + "[/dim]\n")
158
+ return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
159
+
160
+
161
+ def register_command_runner_tools(agent):
66
162
  @agent.tool
67
- def share_your_reasoning(context: RunContext, reasoning: str, next_steps: str = None) -> Dict[str, Any]:
68
- console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
69
- console.print("[bold cyan]Current reasoning:[/bold cyan]")
70
- console.print(Markdown(reasoning))
71
- if next_steps and next_steps.strip():
72
- console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
73
- console.print(Markdown(next_steps))
74
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
75
- return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
163
+ def agent_run_shell_command(
164
+ context: RunContext, command: str, cwd: str = None, timeout: int = 60
165
+ ) -> Dict[str, Any]:
166
+ return run_shell_command(context, command, cwd, timeout)
167
+
168
+ @agent.tool
169
+ def agent_share_your_reasoning(
170
+ context: RunContext, reasoning: str, next_steps: str = None
171
+ ) -> Dict[str, Any]:
172
+ return share_your_reasoning(context, reasoning, next_steps)
@@ -1,5 +1,5 @@
1
1
  import os
2
2
  from rich.console import Console
3
3
 
4
- NO_COLOR = bool(int(os.environ.get('CODE_PUPPY_NO_COLOR', '0')))
4
+ NO_COLOR = bool(int(os.environ.get("CODE_PUPPY_NO_COLOR", "0")))
5
5
  console = Console(no_color=NO_COLOR)