code-puppy 0.0.30__py3-none-any.whl → 0.0.32__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.
code_puppy/config.py ADDED
@@ -0,0 +1,53 @@
1
+ import os
2
+ import configparser
3
+
4
+ CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".code_puppy")
5
+ CONFIG_FILE = os.path.join(CONFIG_DIR, "puppy.cfg")
6
+
7
+ DEFAULT_SECTION = "puppy"
8
+ REQUIRED_KEYS = ["puppy_name", "owner_name"]
9
+
10
+
11
+ def ensure_config_exists():
12
+ """
13
+ Ensure that the .code_puppy dir and puppy.cfg exist, prompting if needed.
14
+ Returns configparser.ConfigParser for reading.
15
+ """
16
+ if not os.path.exists(CONFIG_DIR):
17
+ os.makedirs(CONFIG_DIR, exist_ok=True)
18
+ exists = os.path.isfile(CONFIG_FILE)
19
+ config = configparser.ConfigParser()
20
+ if exists:
21
+ config.read(CONFIG_FILE)
22
+ missing = []
23
+ if DEFAULT_SECTION not in config:
24
+ config[DEFAULT_SECTION] = {}
25
+ for key in REQUIRED_KEYS:
26
+ if not config[DEFAULT_SECTION].get(key):
27
+ missing.append(key)
28
+ if missing:
29
+ print("🐾 Let's get your Puppy ready!")
30
+ for key in missing:
31
+ if key == "puppy_name":
32
+ val = input("What should we name the puppy? ").strip()
33
+ elif key == "owner_name":
34
+ val = input("What's your name (so Code Puppy knows its master)? ").strip()
35
+ else:
36
+ val = input(f"Enter {key}: ").strip()
37
+ config[DEFAULT_SECTION][key] = val
38
+ with open(CONFIG_FILE, "w") as f:
39
+ config.write(f)
40
+ return config
41
+
42
+ def get_value(key: str):
43
+ config = configparser.ConfigParser()
44
+ config.read(CONFIG_FILE)
45
+ val = config.get(DEFAULT_SECTION, key, fallback=None)
46
+ return val
47
+
48
+
49
+ def get_puppy_name():
50
+ return get_value("puppy_name") or "Puppy"
51
+
52
+ def get_owner_name():
53
+ return get_value("owner_name") or "Master"
code_puppy/main.py CHANGED
@@ -5,6 +5,7 @@ from code_puppy.version_checker import fetch_latest_version
5
5
  from code_puppy import __version__
6
6
  import sys
7
7
  from dotenv import load_dotenv
8
+ from code_puppy.config import ensure_config_exists
8
9
  from rich.console import Console
9
10
  from rich.markdown import Markdown
10
11
  from rich.console import ConsoleOptions, RenderResult
@@ -12,12 +13,13 @@ from rich.markdown import CodeBlock
12
13
  from rich.text import Text
13
14
  from rich.syntax import Syntax
14
15
  from code_puppy.command_line.prompt_toolkit_completion import (
15
- get_input_with_path_completion,
16
+ get_input_with_combined_completion,
17
+ get_prompt_with_active_model
16
18
  )
17
19
 
18
20
  # Initialize rich console for pretty output
19
21
  from code_puppy.tools.common import console
20
- from code_puppy.agent import code_generation_agent
22
+ from code_puppy.agent import get_code_generation_agent, session_memory
21
23
 
22
24
  from code_puppy.tools import *
23
25
 
@@ -31,6 +33,8 @@ def get_secret_file_path():
31
33
 
32
34
 
33
35
  async def main():
36
+ # Ensure the config directory and puppy.cfg with name info exist (prompt user if needed)
37
+ ensure_config_exists()
34
38
  current_version = __version__
35
39
  latest_version = fetch_latest_version('code-puppy')
36
40
  console.print(f'Current version: {current_version}')
@@ -59,9 +63,18 @@ async def main():
59
63
  command = " ".join(args.command)
60
64
  try:
61
65
  while not shutdown_flag:
62
- response = await code_generation_agent.run(command)
66
+ agent = get_code_generation_agent()
67
+ response = await agent.run(command)
63
68
  agent_response = response.output
64
69
  console.print(agent_response.output_message)
70
+ # Log to session memory
71
+ session_memory().log_task(
72
+ f'Command executed: {command}',
73
+ extras={
74
+ 'output': agent_response.output_message,
75
+ 'awaiting_user_input': agent_response.awaiting_user_input
76
+ }
77
+ )
65
78
  if agent_response.awaiting_user_input:
66
79
  console.print(
67
80
  "[bold red]The agent requires further input. Interactive mode is recommended for such tasks."
@@ -82,13 +95,12 @@ async def main():
82
95
 
83
96
  # Add the file handling functionality for interactive mode
84
97
  async def interactive_mode(history_file_path: str) -> None:
98
+ from code_puppy.command_line.meta_command_handler import handle_meta_command
85
99
  """Run the agent in interactive mode."""
86
100
  console.print("[bold green]Code Puppy[/bold green] - Interactive Mode")
87
101
  console.print("Type 'exit' or 'quit' to exit the interactive mode.")
88
102
  console.print("Type 'clear' to reset the conversation history.")
89
- console.print(
90
- "Type [bold blue]@[/bold blue] followed by a path to use file path completion."
91
- )
103
+ console.print("Type [bold blue]@[/bold blue] for path completion, or [bold blue]~m[/bold blue] to pick a model.")
92
104
 
93
105
  # Check if prompt_toolkit is installed
94
106
  try:
@@ -133,9 +145,10 @@ async def interactive_mode(history_file_path: str) -> None:
133
145
  try:
134
146
  # Use prompt_toolkit for enhanced input with path completion
135
147
  try:
136
- # Use the async version of get_input_with_path_completion
137
- task = await get_input_with_path_completion(
138
- ">>> 🐶 ", symbol="@", history_file=history_file_path_prompt
148
+ # Use the async version of get_input_with_combined_completion
149
+ task = await get_input_with_combined_completion(
150
+ get_prompt_with_active_model(),
151
+ history_file=history_file_path_prompt
139
152
  )
140
153
  except ImportError:
141
154
  # Fall back to basic input if prompt_toolkit is not available
@@ -151,8 +164,8 @@ async def interactive_mode(history_file_path: str) -> None:
151
164
  console.print("[bold green]Goodbye![/bold green]")
152
165
  break
153
166
 
154
- # Check for clear command
155
- if task.strip().lower() == "clear":
167
+ # Check for clear command (supports both `clear` and `~clear`)
168
+ if task.strip().lower() in ("clear", "~clear"):
156
169
  message_history = []
157
170
  console.print("[bold yellow]Conversation history cleared![/bold yellow]")
158
171
  console.print(
@@ -160,6 +173,10 @@ async def interactive_mode(history_file_path: str) -> None:
160
173
  )
161
174
  continue
162
175
 
176
+ # Handle ~ meta/config commands before anything else
177
+ if task.strip().startswith('~'):
178
+ if handle_meta_command(task.strip(), console):
179
+ continue
163
180
  if task.strip():
164
181
  console.print(f"\n[bold blue]Processing task:[/bold blue] {task}\n")
165
182
 
@@ -175,15 +192,28 @@ async def interactive_mode(history_file_path: str) -> None:
175
192
  # Store agent's full response
176
193
  agent_response = None
177
194
 
178
- result = await code_generation_agent.run(
179
- task, message_history=message_history
180
- )
195
+ agent = get_code_generation_agent()
196
+ result = await agent.run(task, message_history=message_history)
181
197
  # Get the structured response
182
198
  agent_response = result.output
183
199
  console.print(agent_response.output_message)
200
+ # Log to session memory
201
+ session_memory().log_task(
202
+ f'Interactive task: {task}',
203
+ extras={
204
+ 'output': agent_response.output_message,
205
+ 'awaiting_user_input': agent_response.awaiting_user_input
206
+ }
207
+ )
184
208
 
185
- # Update message history with all messages from this interaction
186
- message_history = result.new_messages()
209
+ # Update message history but apply filters & limits
210
+ new_msgs = result.new_messages()
211
+ # 1. Drop any system/config messages (e.g., "agent loaded with model")
212
+ filtered = [m for m in new_msgs if not (isinstance(m, dict) and m.get("role") == "system")]
213
+ # 2. Append to existing history and keep only the most recent 40
214
+ message_history.extend(filtered)
215
+ if len(message_history) > 40:
216
+ message_history = message_history[-40:]
187
217
 
188
218
  if agent_response and agent_response.awaiting_user_input:
189
219
  console.print(
@@ -0,0 +1,71 @@
1
+ import json
2
+ from pathlib import Path
3
+ from datetime import datetime, timedelta
4
+ from typing import Any, List, Dict, Optional
5
+
6
+ DEFAULT_MEMORY_PATH = Path('.puppy_session_memory.json')
7
+
8
+ class SessionMemory:
9
+ """
10
+ Simple persistent memory for Code Puppy agent sessions.
11
+ Stores short histories of tasks, notes, user preferences, and watched files.
12
+ """
13
+ def __init__(self, storage_path: Path = DEFAULT_MEMORY_PATH, memory_limit: int = 128):
14
+ self.storage_path = storage_path
15
+ self.memory_limit = memory_limit
16
+ self._data = {
17
+ 'history': [], # List of task/event dicts
18
+ 'user_preferences': {},
19
+ 'watched_files': [],
20
+ }
21
+ self._load()
22
+
23
+ def _load(self):
24
+ if self.storage_path.exists():
25
+ try:
26
+ self._data = json.loads(self.storage_path.read_text())
27
+ except Exception:
28
+ self._data = {'history': [], 'user_preferences': {}, 'watched_files': []}
29
+
30
+ def _save(self):
31
+ try:
32
+ self.storage_path.write_text(json.dumps(self._data, indent=2))
33
+ except Exception as e:
34
+ pass # Don't crash the agent for memory fails
35
+
36
+ def log_task(self, description: str, extras: Optional[Dict[str, Any]] = None):
37
+ entry = {
38
+ 'timestamp': datetime.utcnow().isoformat(),
39
+ 'description': description,
40
+ }
41
+ if extras:
42
+ entry.update(extras)
43
+ self._data['history'].append(entry)
44
+ # Trim memory
45
+ self._data['history'] = self._data['history'][-self.memory_limit:]
46
+ self._save()
47
+
48
+ def get_history(self, within_minutes: Optional[int] = None) -> List[Dict[str, Any]]:
49
+ if not within_minutes:
50
+ return list(self._data['history'])
51
+ cutoff = datetime.utcnow() - timedelta(minutes=within_minutes)
52
+ return [h for h in self._data['history'] if datetime.fromisoformat(h['timestamp']) >= cutoff]
53
+
54
+ def set_preference(self, key: str, value: Any):
55
+ self._data['user_preferences'][key] = value
56
+ self._save()
57
+
58
+ def get_preference(self, key: str, default: Any = None) -> Any:
59
+ return self._data['user_preferences'].get(key, default)
60
+
61
+ def add_watched_file(self, path: str):
62
+ if path not in self._data['watched_files']:
63
+ self._data['watched_files'].append(path)
64
+ self._save()
65
+
66
+ def list_watched_files(self) -> List[str]:
67
+ return list(self._data['watched_files'])
68
+
69
+ def clear(self):
70
+ self._data = {'history': [], 'user_preferences': {}, 'watched_files': []}
71
+ self._save()
@@ -1,4 +1,11 @@
1
- import code_puppy.tools.file_modifications
2
- import code_puppy.tools.file_operations
3
- import code_puppy.tools.command_runner
4
- import code_puppy.tools.web_search
1
+ from code_puppy.tools.file_operations import register_file_operations_tools
2
+ from code_puppy.tools.file_modifications import register_file_modifications_tools
3
+ from code_puppy.tools.command_runner import register_command_runner_tools
4
+ from code_puppy.tools.web_search import register_web_search_tools
5
+
6
+ def register_all_tools(agent):
7
+ """Register all available tools to the provided agent."""
8
+ register_file_operations_tools(agent)
9
+ register_file_modifications_tools(agent)
10
+ register_command_runner_tools(agent)
11
+ register_web_search_tools(agent)
@@ -0,0 +1,86 @@
1
+ import os
2
+ import ast
3
+ from typing import List, Tuple
4
+ from rich.tree import Tree
5
+ from rich.text import Text
6
+ from pathlib import Path
7
+ import pathspec
8
+
9
+
10
+ def summarize_node(node: ast.AST) -> str:
11
+ if isinstance(node, ast.ClassDef):
12
+ return f"class {node.name}"
13
+ if isinstance(node, ast.FunctionDef):
14
+ return f"def {node.name}()"
15
+ return ""
16
+
17
+
18
+ def get_docstring(node: ast.AST) -> str:
19
+ doc = ast.get_docstring(node)
20
+ if doc:
21
+ lines = doc.strip().split("\n")
22
+ return lines[0] if lines else doc.strip()
23
+ return ""
24
+
25
+
26
+ def map_python_file(file_path: str, show_doc: bool = True) -> Tree:
27
+ tree = Tree(Text(file_path, style="bold cyan"))
28
+ with open(file_path, "r", encoding="utf-8") as f:
29
+ root = ast.parse(f.read(), filename=file_path)
30
+ for node in root.body:
31
+ summary = summarize_node(node)
32
+ if summary:
33
+ t = Tree(summary)
34
+ if show_doc:
35
+ doc = get_docstring(node)
36
+ if doc:
37
+ t.add(Text(f'"{doc}"', style="dim"))
38
+ # Add inner functions
39
+ if hasattr(node, 'body'):
40
+ for subnode in getattr(node, 'body'):
41
+ subsum = summarize_node(subnode)
42
+ if subsum:
43
+ sub_t = Tree(subsum)
44
+ doc2 = get_docstring(subnode)
45
+ if doc2:
46
+ sub_t.add(Text(f'"{doc2}"', style="dim"))
47
+ t.add(sub_t)
48
+ tree.add(t)
49
+ return tree
50
+
51
+
52
+ def load_gitignore(directory: str):
53
+ gitignore_file = os.path.join(directory, '.gitignore')
54
+ if os.path.exists(gitignore_file):
55
+ with open(gitignore_file, 'r') as f:
56
+ spec = pathspec.PathSpec.from_lines('gitwildmatch', f)
57
+ return spec
58
+ else:
59
+ return pathspec.PathSpec.from_lines('gitwildmatch', [])
60
+
61
+ def make_code_map(directory: str, show_doc: bool = True) -> Tree:
62
+ """
63
+ Recursively build a Tree displaying the code structure of all .py files in a directory,
64
+ ignoring files listed in .gitignore if present.
65
+ """
66
+ base_tree = Tree(Text(directory, style="bold magenta"))
67
+
68
+ spec = load_gitignore(directory)
69
+ abs_directory = os.path.abspath(directory)
70
+
71
+ for root, dirs, files in os.walk(directory):
72
+ rel_root = os.path.relpath(root, abs_directory)
73
+ # 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)))]
75
+ for fname in files:
76
+ rel_file = os.path.normpath(os.path.join(rel_root, fname))
77
+ if fname.endswith('.py') and not fname.startswith("__"):
78
+ if not spec.match_file(rel_file):
79
+ fpath = os.path.join(root, fname)
80
+ try:
81
+ file_tree = map_python_file(fpath, show_doc=show_doc)
82
+ base_tree.add(file_tree)
83
+ except Exception as e:
84
+ err = Tree(Text(f"[error reading {fname}: {e}]", style="bold red"))
85
+ base_tree.add(err)
86
+ return base_tree
@@ -4,207 +4,71 @@ import time
4
4
  import os
5
5
  from typing import Dict, Any
6
6
  from code_puppy.tools.common import console
7
- from code_puppy.agent import code_generation_agent
8
7
  from pydantic_ai import RunContext
9
8
  from rich.markdown import Markdown
10
9
  from rich.syntax import Syntax
11
10
 
12
- # Environment variables used in this module:
13
- # - YOLO_MODE: When set to "true" (case-insensitive), bypasses the safety confirmation
14
- # prompt when running shell commands. This allows commands to execute
15
- # without user intervention, which can be useful for automation but
16
- # introduces security risks. Default is "false".
17
-
18
-
19
- @code_generation_agent.tool
20
- def run_shell_command(
21
- context: RunContext, command: str, cwd: str = None, timeout: int = 60
22
- ) -> Dict[str, Any]:
23
- """Run a shell command and return its output.
24
-
25
- Args:
26
- command: The shell command to execute.
27
- cwd: The current working directory to run the command in. Defaults to None (current directory).
28
- timeout: Maximum time in seconds to wait for the command to complete. Defaults to 60.
29
-
30
- Returns:
31
- A dictionary with the command result, including stdout, stderr, and exit code.
32
- """
33
- if not command or not command.strip():
34
- console.print("[bold red]Error:[/bold red] Command cannot be empty")
35
- return {"error": "Command cannot be empty"}
36
-
37
- # Display command execution in a visually distinct way
38
- console.print("\n[bold white on blue] SHELL COMMAND [/bold white on blue]")
39
- console.print(f"[bold green]$ {command}[/bold green]")
40
- if cwd:
41
- console.print(f"[dim]Working directory: {cwd}[/dim]")
42
- console.print("[dim]" + "-" * 60 + "[/dim]")
43
-
44
- # Check for YOLO_MODE environment variable to bypass safety check
45
- yolo_mode = os.getenv("YOLO_MODE", "false").lower() == "true"
46
-
47
- if not yolo_mode:
48
- # Prompt user for confirmation before running the command
49
- user_input = input("Are you sure you want to run this command? (yes/no): ")
50
- if user_input.strip().lower() not in {"yes", "y"}:
51
- console.print(
52
- "[bold yellow]Command execution canceled by user.[/bold yellow]"
53
- )
54
- return {
55
- "success": False,
56
- "command": command,
57
- "error": "User canceled command execution",
58
- }
59
-
60
- try:
61
- start_time = time.time()
62
-
63
- # Execute the command with timeout
64
- process = subprocess.Popen(
65
- command,
66
- shell=True,
67
- stdout=subprocess.PIPE,
68
- stderr=subprocess.PIPE,
69
- text=True,
70
- cwd=cwd,
71
- )
72
-
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"}
73
28
  try:
74
- stdout, stderr = process.communicate(timeout=timeout)
75
- exit_code = process.returncode
76
- execution_time = time.time() - start_time
77
-
78
- # Display command output
79
- if stdout.strip():
80
- console.print("[bold white]STDOUT:[/bold white]")
81
- console.print(
82
- Syntax(
83
- stdout.strip(),
84
- "bash",
85
- theme="monokai",
86
- background_color="default",
87
- )
88
- )
89
-
90
- if stderr.strip():
91
- console.print("[bold yellow]STDERR:[/bold yellow]")
92
- console.print(
93
- Syntax(
94
- stderr.strip(),
95
- "bash",
96
- theme="monokai",
97
- background_color="default",
98
- )
99
- )
100
-
101
- # Show execution summary
102
- if exit_code == 0:
103
- console.print(
104
- f"[bold green]✓ Command completed successfully[/bold green] [dim](took {execution_time:.2f}s)[/dim]"
105
- )
106
- else:
107
- console.print(
108
- f"[bold red]✗ Command failed with exit code {exit_code}[/bold red] [dim](took {execution_time:.2f}s)[/dim]"
109
- )
110
-
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)
111
62
  console.print("[dim]" + "-" * 60 + "[/dim]\n")
112
-
113
- return {
114
- "success": exit_code == 0,
115
- "command": command,
116
- "stdout": stdout,
117
- "stderr": stderr,
118
- "exit_code": exit_code,
119
- "execution_time": execution_time,
120
- "timeout": False,
121
- }
122
- except subprocess.TimeoutExpired:
123
- # Kill the process if it times out
124
- process.kill()
125
- stdout, stderr = process.communicate()
126
- execution_time = time.time() - start_time
127
-
128
- # Display timeout information
129
- if stdout.strip():
130
- console.print(
131
- "[bold white]STDOUT (incomplete due to timeout):[/bold white]"
132
- )
133
- console.print(
134
- Syntax(
135
- stdout.strip(),
136
- "bash",
137
- theme="monokai",
138
- background_color="default",
139
- )
140
- )
141
-
142
- if stderr.strip():
143
- console.print("[bold yellow]STDERR:[/bold yellow]")
144
- console.print(
145
- Syntax(
146
- stderr.strip(),
147
- "bash",
148
- theme="monokai",
149
- background_color="default",
150
- )
151
- )
152
-
153
- console.print(
154
- f"[bold red]⏱ Command timed out after {timeout} seconds[/bold red] [dim](ran for {execution_time:.2f}s)[/dim]"
155
- )
156
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
157
-
158
- return {
159
- "success": False,
160
- "command": command,
161
- "stdout": stdout[-1000:],
162
- "stderr": stderr[-1000:],
163
- "exit_code": None, # No exit code since the process was killed
164
- "execution_time": execution_time,
165
- "timeout": True,
166
- "error": f"Command timed out after {timeout} seconds",
167
- }
168
- except Exception as e:
169
- # Display error information
170
- console.print_exception(show_locals=True)
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))
171
73
  console.print("[dim]" + "-" * 60 + "[/dim]\n")
172
-
173
- return {
174
- "success": False,
175
- "command": command,
176
- "error": f"Error executing command: {str(e)}",
177
- "stdout": "",
178
- "stderr": "",
179
- "exit_code": -1,
180
- "timeout": False,
181
- }
182
-
183
-
184
- @code_generation_agent.tool
185
- def share_your_reasoning(
186
- context: RunContext, reasoning: str, next_steps: str = None
187
- ) -> Dict[str, Any]:
188
- """Share the agent's current reasoning and planned next steps with the user.
189
-
190
- Args:
191
- reasoning: The agent's current reasoning or thought process.
192
- next_steps: Optional description of what the agent plans to do next.
193
-
194
- Returns:
195
- A dictionary with the reasoning information.
196
- """
197
- console.print("\n[bold white on purple] AGENT REASONING [/bold white on purple]")
198
-
199
- # Display the reasoning with markdown formatting
200
- console.print("[bold cyan]Current reasoning:[/bold cyan]")
201
- console.print(Markdown(reasoning))
202
-
203
- # Display next steps if provided
204
- if next_steps and next_steps.strip():
205
- console.print("\n[bold cyan]Planned next steps:[/bold cyan]")
206
- console.print(Markdown(next_steps))
207
-
208
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
209
-
210
- return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
74
+ return {"success": True, "reasoning": reasoning, "next_steps": next_steps}
@@ -1,3 +1,5 @@
1
+ import os
1
2
  from rich.console import Console
2
3
 
3
- console = Console()
4
+ NO_COLOR = bool(int(os.environ.get('CODE_PUPPY_NO_COLOR', '0')))
5
+ console = Console(no_color=NO_COLOR)