code-puppy 0.0.175__py3-none-any.whl → 0.0.177__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 (35) hide show
  1. code_puppy/agents/__init__.py +2 -2
  2. code_puppy/agents/agent_code_puppy.py +2 -1
  3. code_puppy/agents/agent_creator_agent.py +3 -2
  4. code_puppy/agents/agent_manager.py +5 -5
  5. code_puppy/agents/base_agent.py +60 -40
  6. code_puppy/command_line/command_handler.py +19 -10
  7. code_puppy/command_line/mcp/start_all_command.py +1 -1
  8. code_puppy/command_line/mcp/start_command.py +0 -1
  9. code_puppy/command_line/mcp/stop_all_command.py +1 -1
  10. code_puppy/command_line/mcp/stop_command.py +1 -0
  11. code_puppy/config.py +5 -3
  12. code_puppy/main.py +5 -2
  13. code_puppy/mcp_/examples/retry_example.py +4 -1
  14. code_puppy/messaging/spinner/console_spinner.py +1 -1
  15. code_puppy/model_factory.py +1 -1
  16. code_puppy/round_robin_model.py +2 -4
  17. code_puppy/tools/agent_tools.py +10 -8
  18. code_puppy/tools/browser/browser_screenshot.py +4 -3
  19. code_puppy/tools/browser/browser_scripts.py +0 -6
  20. code_puppy/tools/browser/browser_workflows.py +28 -20
  21. code_puppy/tools/browser/camoufox_manager.py +33 -48
  22. code_puppy/tools/browser/vqa_agent.py +0 -2
  23. code_puppy/tools/browser_scripts.py +0 -6
  24. code_puppy/tools/browser_workflows.py +28 -20
  25. code_puppy/tools/command_runner.py +1 -1
  26. code_puppy/tui/app.py +3 -13
  27. code_puppy/tui/components/chat_view.py +1 -0
  28. code_puppy/tui/screens/settings.py +3 -3
  29. {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/METADATA +10 -10
  30. {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/RECORD +34 -35
  31. code_puppy/tools/camoufox_manager.py +0 -150
  32. {code_puppy-0.0.175.data → code_puppy-0.0.177.data}/data/code_puppy/models.json +0 -0
  33. {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/WHEEL +0 -0
  34. {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/entry_points.txt +0 -0
  35. {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/licenses/LICENSE +0 -0
@@ -13,10 +13,11 @@ from code_puppy.messaging import emit_error, emit_info
13
13
  from code_puppy.tools.common import generate_group_id
14
14
 
15
15
  from .camoufox_manager import get_camoufox_manager
16
- from .vqa_agent import VisualAnalysisResult, run_vqa_analysis
16
+ from .vqa_agent import run_vqa_analysis
17
17
 
18
-
19
- _TEMP_SCREENSHOT_ROOT = Path(mkdtemp(prefix="code_puppy_screenshots_", dir=gettempdir()))
18
+ _TEMP_SCREENSHOT_ROOT = Path(
19
+ mkdtemp(prefix="code_puppy_screenshots_", dir=gettempdir())
20
+ )
20
21
 
21
22
 
22
23
  def _build_screenshot_path(timestamp: str) -> Path:
@@ -236,9 +236,6 @@ async def wait_for_element(
236
236
  return {"success": False, "error": str(e), "selector": selector, "state": state}
237
237
 
238
238
 
239
-
240
-
241
-
242
239
  async def highlight_element(
243
240
  selector: str,
244
241
  color: str = "red",
@@ -437,9 +434,6 @@ def register_wait_for_element(agent):
437
434
  return await wait_for_element(selector, state, timeout)
438
435
 
439
436
 
440
-
441
-
442
-
443
437
  def register_browser_highlight_element(agent):
444
438
  """Register the element highlighting tool."""
445
439
 
@@ -29,18 +29,18 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
29
29
  workflows_dir = get_workflows_directory()
30
30
 
31
31
  # Clean up the filename - remove spaces, special chars, etc.
32
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
32
+ safe_name = "".join(c for c in name if c.isalnum() or c in ("-", "_")).lower()
33
33
  if not safe_name:
34
34
  safe_name = "workflow"
35
35
 
36
36
  # Ensure .md extension
37
- if not safe_name.endswith('.md'):
38
- safe_name += '.md'
37
+ if not safe_name.endswith(".md"):
38
+ safe_name += ".md"
39
39
 
40
40
  workflow_path = workflows_dir / safe_name
41
41
 
42
42
  # Write the workflow content
43
- with open(workflow_path, 'w', encoding='utf-8') as f:
43
+ with open(workflow_path, "w", encoding="utf-8") as f:
44
44
  f.write(content)
45
45
 
46
46
  emit_info(
@@ -52,7 +52,7 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
52
52
  "success": True,
53
53
  "path": str(workflow_path),
54
54
  "name": safe_name,
55
- "size": len(content)
55
+ "size": len(content),
56
56
  }
57
57
 
58
58
  except Exception as e:
@@ -75,23 +75,27 @@ async def list_workflows() -> Dict[str, Any]:
75
75
  workflows_dir = get_workflows_directory()
76
76
 
77
77
  # Find all .md files in the workflows directory
78
- workflow_files = list(workflows_dir.glob('*.md'))
78
+ workflow_files = list(workflows_dir.glob("*.md"))
79
79
 
80
80
  workflows = []
81
81
  for workflow_file in workflow_files:
82
82
  try:
83
83
  stat = workflow_file.stat()
84
- workflows.append({
85
- "name": workflow_file.name,
86
- "path": str(workflow_file),
87
- "size": stat.st_size,
88
- "modified": stat.st_mtime
89
- })
84
+ workflows.append(
85
+ {
86
+ "name": workflow_file.name,
87
+ "path": str(workflow_file),
88
+ "size": stat.st_size,
89
+ "modified": stat.st_mtime,
90
+ }
91
+ )
90
92
  except Exception as e:
91
- emit_info(f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]")
93
+ emit_info(
94
+ f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]"
95
+ )
92
96
 
93
97
  # Sort by modification time (newest first)
94
- workflows.sort(key=lambda x: x['modified'], reverse=True)
98
+ workflows.sort(key=lambda x: x["modified"], reverse=True)
95
99
 
96
100
  emit_info(
97
101
  f"[green]✅ Found {len(workflows)} workflow(s)[/green]",
@@ -102,7 +106,7 @@ async def list_workflows() -> Dict[str, Any]:
102
106
  "success": True,
103
107
  "workflows": workflows,
104
108
  "count": len(workflows),
105
- "directory": str(workflows_dir)
109
+ "directory": str(workflows_dir),
106
110
  }
107
111
 
108
112
  except Exception as e:
@@ -125,8 +129,8 @@ async def read_workflow(name: str) -> Dict[str, Any]:
125
129
  workflows_dir = get_workflows_directory()
126
130
 
127
131
  # Handle both with and without .md extension
128
- if not name.endswith('.md'):
129
- name += '.md'
132
+ if not name.endswith(".md"):
133
+ name += ".md"
130
134
 
131
135
  workflow_path = workflows_dir / name
132
136
 
@@ -135,10 +139,14 @@ async def read_workflow(name: str) -> Dict[str, Any]:
135
139
  f"[red]❌ Workflow not found: {name}[/red]",
136
140
  message_group=group_id,
137
141
  )
138
- return {"success": False, "error": f"Workflow '{name}' not found", "name": name}
142
+ return {
143
+ "success": False,
144
+ "error": f"Workflow '{name}' not found",
145
+ "name": name,
146
+ }
139
147
 
140
148
  # Read the workflow content
141
- with open(workflow_path, 'r', encoding='utf-8') as f:
149
+ with open(workflow_path, "r", encoding="utf-8") as f:
142
150
  content = f.read()
143
151
 
144
152
  emit_info(
@@ -151,7 +159,7 @@ async def read_workflow(name: str) -> Dict[str, Any]:
151
159
  "name": name,
152
160
  "content": content,
153
161
  "path": str(workflow_path),
154
- "size": len(content)
162
+ "size": len(content),
155
163
  }
156
164
 
157
165
  except Exception as e:
@@ -3,12 +3,13 @@
3
3
  from typing import Optional
4
4
 
5
5
  import camoufox
6
- from playwright.async_api import Browser, BrowserContext, Page, Playwright, async_playwright
6
+ from camoufox.addons import DefaultAddons
7
+ from camoufox.exceptions import CamoufoxNotInstalled, UnsupportedVersion
8
+ from camoufox.locale import ALLOW_GEOIP, download_mmdb
9
+ from camoufox.pkgman import CamoufoxFetcher, camoufox_path
10
+ from playwright.async_api import Browser, BrowserContext, Page
7
11
 
8
12
  from code_puppy.messaging import emit_info
9
- from camoufox.pkgman import CamoufoxFetcher
10
- from camoufox.locale import ALLOW_GEOIP, download_mmdb
11
- from camoufox.addons import maybe_download_addons, DefaultAddons
12
13
 
13
14
 
14
15
  class CamoufoxManager:
@@ -17,7 +18,6 @@ class CamoufoxManager:
17
18
  _instance: Optional["CamoufoxManager"] = None
18
19
  _browser: Optional[Browser] = None
19
20
  _context: Optional[BrowserContext] = None
20
- _playwright: Optional[Playwright] = None
21
21
  _initialized: bool = False
22
22
 
23
23
  def __new__(cls):
@@ -52,27 +52,17 @@ class CamoufoxManager:
52
52
 
53
53
  try:
54
54
  emit_info("[yellow]Initializing Camoufox (privacy Firefox)...[/yellow]")
55
-
55
+
56
56
  # Ensure Camoufox binary and dependencies are fetched before launching
57
57
  await self._prefetch_camoufox()
58
58
 
59
- try:
60
- await self._initialize_camoufox()
61
- emit_info(
62
- "[green]✅ Camoufox initialized successfully (privacy-focused Firefox)[/green]"
63
- )
64
- except Exception as camoufox_error:
65
- error_reason = str(camoufox_error).splitlines()[0]
66
- emit_info(
67
- "[yellow]⚠️ Camoufox failed to initialize, falling back to Playwright Firefox[/yellow]"
68
- )
69
- await self._cleanup()
70
- await self._initialize_playwright_firefox(error_reason)
71
-
59
+ await self._initialize_camoufox()
60
+ emit_info(
61
+ "[green]✅ Camoufox initialized successfully (privacy-focused Firefox)[/green]"
62
+ )
72
63
  self._initialized = True
73
64
 
74
- except Exception as e:
75
- emit_info(f"[red]❌ Failed to initialize browser: {e}[/red]")
65
+ except Exception:
76
66
  await self._cleanup()
77
67
  raise
78
68
 
@@ -82,6 +72,8 @@ class CamoufoxManager:
82
72
  headless=self.headless,
83
73
  block_webrtc=self.block_webrtc,
84
74
  humanize=self.humanize,
75
+ exclude_addons=list(DefaultAddons),
76
+ addons=[],
85
77
  )
86
78
  self._browser = await camoufox_instance.start()
87
79
  self._context = await self._browser.new_context(
@@ -91,20 +83,6 @@ class CamoufoxManager:
91
83
  page = await self._context.new_page()
92
84
  await page.goto(self.homepage)
93
85
 
94
- async def _initialize_playwright_firefox(self, error_reason: str) -> None:
95
- """Fallback to vanilla Playwright Firefox when Camoufox fails."""
96
- self._playwright = await async_playwright().start()
97
- self._browser = await self._playwright.firefox.launch(headless=self.headless)
98
- self._context = await self._browser.new_context(
99
- viewport={"width": 1920, "height": 1080},
100
- ignore_https_errors=True,
101
- )
102
- page = await self._context.new_page()
103
- await page.goto(self.homepage)
104
- emit_info(
105
- f"[green]✅ Playwright Firefox fallback ready (Camoufox error: {error_reason})[/green]"
106
- )
107
-
108
86
  async def get_current_page(self) -> Optional[Page]:
109
87
  """Get the currently active page."""
110
88
  if not self._initialized or not self._context:
@@ -127,18 +105,28 @@ class CamoufoxManager:
127
105
 
128
106
  async def _prefetch_camoufox(self) -> None:
129
107
  """Prefetch Camoufox binary and dependencies."""
130
- emit_info("[cyan]🔍 Ensuring Camoufox binary and dependencies are up-to-date...[/cyan]")
131
-
132
- # Fetch Camoufox binary if needed
133
- CamoufoxFetcher().install()
134
-
108
+ emit_info(
109
+ "[cyan]🔍 Ensuring Camoufox binary and dependencies are up-to-date...[/cyan]"
110
+ )
111
+
112
+ needs_install = False
113
+ try:
114
+ camoufox_path(download_if_missing=False)
115
+ emit_info("[cyan]🗃️ Using cached Camoufox installation[/cyan]")
116
+ except (CamoufoxNotInstalled, FileNotFoundError):
117
+ emit_info("[cyan]📥 Camoufox not found, installing fresh copy[/cyan]")
118
+ needs_install = True
119
+ except UnsupportedVersion:
120
+ emit_info("[cyan]♻️ Camoufox update required, reinstalling[/cyan]")
121
+ needs_install = True
122
+
123
+ if needs_install:
124
+ CamoufoxFetcher().install()
125
+
135
126
  # Fetch GeoIP database if enabled
136
127
  if ALLOW_GEOIP:
137
128
  download_mmdb()
138
-
139
- # Download default addons
140
- maybe_download_addons(list(DefaultAddons))
141
-
129
+
142
130
  emit_info("[cyan]📦 Camoufox dependencies ready[/cyan]")
143
131
 
144
132
  async def close_page(self, page: Page) -> None:
@@ -160,9 +148,6 @@ class CamoufoxManager:
160
148
  if self._browser:
161
149
  await self._browser.close()
162
150
  self._browser = None
163
- if self._playwright:
164
- await self._playwright.stop()
165
- self._playwright = None
166
151
  self._initialized = False
167
152
  except Exception as e:
168
153
  emit_info(f"[yellow]Warning during cleanup: {e}[/yellow]")
@@ -184,7 +169,7 @@ class CamoufoxManager:
184
169
  loop.create_task(self._cleanup())
185
170
  else:
186
171
  loop.run_until_complete(self._cleanup())
187
- except:
172
+ except Exception:
188
173
  pass # Best effort cleanup
189
174
 
190
175
 
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from functools import lru_cache
6
- from typing import Optional
7
6
 
8
7
  from pydantic import BaseModel, Field
9
8
  from pydantic_ai import Agent, BinaryContent
@@ -37,7 +36,6 @@ def _load_vqa_agent(model_name: str) -> Agent[None, VisualAnalysisResult]:
37
36
  instructions=instructions,
38
37
  output_type=VisualAnalysisResult,
39
38
  retries=2,
40
- instrument=instrumentation,
41
39
  )
42
40
 
43
41
 
@@ -236,9 +236,6 @@ async def wait_for_element(
236
236
  return {"success": False, "error": str(e), "selector": selector, "state": state}
237
237
 
238
238
 
239
-
240
-
241
-
242
239
  async def highlight_element(
243
240
  selector: str,
244
241
  color: str = "red",
@@ -437,9 +434,6 @@ def register_wait_for_element(agent):
437
434
  return await wait_for_element(selector, state, timeout)
438
435
 
439
436
 
440
-
441
-
442
-
443
437
  def register_browser_highlight_element(agent):
444
438
  """Register the element highlighting tool."""
445
439
 
@@ -29,18 +29,18 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
29
29
  workflows_dir = get_workflows_directory()
30
30
 
31
31
  # Clean up the filename - remove spaces, special chars, etc.
32
- safe_name = "".join(c for c in name if c.isalnum() or c in ('-', '_')).lower()
32
+ safe_name = "".join(c for c in name if c.isalnum() or c in ("-", "_")).lower()
33
33
  if not safe_name:
34
34
  safe_name = "workflow"
35
35
 
36
36
  # Ensure .md extension
37
- if not safe_name.endswith('.md'):
38
- safe_name += '.md'
37
+ if not safe_name.endswith(".md"):
38
+ safe_name += ".md"
39
39
 
40
40
  workflow_path = workflows_dir / safe_name
41
41
 
42
42
  # Write the workflow content
43
- with open(workflow_path, 'w', encoding='utf-8') as f:
43
+ with open(workflow_path, "w", encoding="utf-8") as f:
44
44
  f.write(content)
45
45
 
46
46
  emit_info(
@@ -52,7 +52,7 @@ async def save_workflow(name: str, content: str) -> Dict[str, Any]:
52
52
  "success": True,
53
53
  "path": str(workflow_path),
54
54
  "name": safe_name,
55
- "size": len(content)
55
+ "size": len(content),
56
56
  }
57
57
 
58
58
  except Exception as e:
@@ -75,23 +75,27 @@ async def list_workflows() -> Dict[str, Any]:
75
75
  workflows_dir = get_workflows_directory()
76
76
 
77
77
  # Find all .md files in the workflows directory
78
- workflow_files = list(workflows_dir.glob('*.md'))
78
+ workflow_files = list(workflows_dir.glob("*.md"))
79
79
 
80
80
  workflows = []
81
81
  for workflow_file in workflow_files:
82
82
  try:
83
83
  stat = workflow_file.stat()
84
- workflows.append({
85
- "name": workflow_file.name,
86
- "path": str(workflow_file),
87
- "size": stat.st_size,
88
- "modified": stat.st_mtime
89
- })
84
+ workflows.append(
85
+ {
86
+ "name": workflow_file.name,
87
+ "path": str(workflow_file),
88
+ "size": stat.st_size,
89
+ "modified": stat.st_mtime,
90
+ }
91
+ )
90
92
  except Exception as e:
91
- emit_info(f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]")
93
+ emit_info(
94
+ f"[yellow]Warning: Could not read {workflow_file}: {e}[/yellow]"
95
+ )
92
96
 
93
97
  # Sort by modification time (newest first)
94
- workflows.sort(key=lambda x: x['modified'], reverse=True)
98
+ workflows.sort(key=lambda x: x["modified"], reverse=True)
95
99
 
96
100
  emit_info(
97
101
  f"[green]✅ Found {len(workflows)} workflow(s)[/green]",
@@ -102,7 +106,7 @@ async def list_workflows() -> Dict[str, Any]:
102
106
  "success": True,
103
107
  "workflows": workflows,
104
108
  "count": len(workflows),
105
- "directory": str(workflows_dir)
109
+ "directory": str(workflows_dir),
106
110
  }
107
111
 
108
112
  except Exception as e:
@@ -125,8 +129,8 @@ async def read_workflow(name: str) -> Dict[str, Any]:
125
129
  workflows_dir = get_workflows_directory()
126
130
 
127
131
  # Handle both with and without .md extension
128
- if not name.endswith('.md'):
129
- name += '.md'
132
+ if not name.endswith(".md"):
133
+ name += ".md"
130
134
 
131
135
  workflow_path = workflows_dir / name
132
136
 
@@ -135,10 +139,14 @@ async def read_workflow(name: str) -> Dict[str, Any]:
135
139
  f"[red]❌ Workflow not found: {name}[/red]",
136
140
  message_group=group_id,
137
141
  )
138
- return {"success": False, "error": f"Workflow '{name}' not found", "name": name}
142
+ return {
143
+ "success": False,
144
+ "error": f"Workflow '{name}' not found",
145
+ "name": name,
146
+ }
139
147
 
140
148
  # Read the workflow content
141
- with open(workflow_path, 'r', encoding='utf-8') as f:
149
+ with open(workflow_path, "r", encoding="utf-8") as f:
142
150
  content = f.read()
143
151
 
144
152
  emit_info(
@@ -151,7 +159,7 @@ async def read_workflow(name: str) -> Dict[str, Any]:
151
159
  "name": name,
152
160
  "content": content,
153
161
  "path": str(workflow_path),
154
- "size": len(content)
162
+ "size": len(content),
155
163
  }
156
164
 
157
165
  except Exception as e:
@@ -19,8 +19,8 @@ from code_puppy.messaging import (
19
19
  emit_system_message,
20
20
  emit_warning,
21
21
  )
22
- from code_puppy.tui_state import is_tui_mode
23
22
  from code_puppy.tools.common import generate_group_id
23
+ from code_puppy.tui_state import is_tui_mode
24
24
 
25
25
  # Maximum line length for shell command output to prevent massive token usage
26
26
  # This helps avoid exceeding model context limits when commands produce very long lines
code_puppy/tui/app.py CHANGED
@@ -12,6 +12,8 @@ from textual.events import Resize
12
12
  from textual.reactive import reactive
13
13
  from textual.widgets import Footer, ListView
14
14
 
15
+ # message_history_accumulator and prune_interrupted_tool_calls have been moved to BaseAgent class
16
+ from code_puppy.agents.agent_manager import get_current_agent
15
17
  from code_puppy.command_line.command_handler import handle_command
16
18
  from code_puppy.config import (
17
19
  get_global_model_name,
@@ -19,12 +21,9 @@ from code_puppy.config import (
19
21
  initialize_command_history_file,
20
22
  save_command_to_history,
21
23
  )
22
- # message_history_accumulator and prune_interrupted_tool_calls have been moved to BaseAgent class
23
- from code_puppy.agents.agent_manager import get_current_agent
24
24
 
25
25
  # Import our message queue system
26
26
  from code_puppy.messaging import TUIRenderer, get_global_queue
27
-
28
27
  from code_puppy.tui.components import (
29
28
  ChatView,
30
29
  CustomTextArea,
@@ -33,7 +32,6 @@ from code_puppy.tui.components import (
33
32
  StatusBar,
34
33
  )
35
34
 
36
-
37
35
  # Import shared message classes
38
36
  from .messages import CommandSelected, HistoryEntrySelected
39
37
  from .models import ChatMessage, MessageType
@@ -175,12 +173,6 @@ class CodePuppyTUI(App):
175
173
  "Welcome to Code Puppy 🐶!\n💨 YOLO mode is enabled in TUI: commands will execute without confirmation."
176
174
  )
177
175
 
178
- # Get current agent and display info
179
- agent = get_current_agent()
180
- self.add_system_message(
181
- f"🐕 Loaded agent '{self.puppy_name}' with model '{self.current_model}'"
182
- )
183
-
184
176
  # Start the message renderer EARLY to catch startup messages
185
177
  # Using call_after_refresh to start it as soon as possible after mount
186
178
  self.call_after_refresh(self.start_message_renderer_sync)
@@ -509,9 +501,7 @@ class CodePuppyTUI(App):
509
501
  pass
510
502
  except Exception as agent_error:
511
503
  # Handle any other errors in agent processing
512
- self.add_error_message(
513
- f"Agent processing failed: {str(agent_error)}"
514
- )
504
+ self.add_error_message(f"Agent processing failed: {str(agent_error)}")
515
505
 
516
506
  except Exception as e:
517
507
  self.add_error_message(f"Error processing message: {str(e)}")
@@ -267,6 +267,7 @@ class ChatView(VerticalScroll):
267
267
  ):
268
268
  # If either content is a Rich object, convert both to text and concatenate
269
269
  from io import StringIO
270
+
270
271
  from rich.console import Console
271
272
 
272
273
  # Convert existing content to string
@@ -125,12 +125,12 @@ class SettingsScreen(ModalScreen):
125
125
  def on_mount(self) -> None:
126
126
  """Load current settings when the screen mounts."""
127
127
  from code_puppy.config import (
128
+ get_compaction_strategy,
129
+ get_compaction_threshold,
128
130
  get_global_model_name,
129
131
  get_owner_name,
130
132
  get_protected_token_count,
131
133
  get_puppy_name,
132
- get_compaction_strategy,
133
- get_compaction_threshold,
134
134
  )
135
135
 
136
136
  # Load current values
@@ -188,9 +188,9 @@ class SettingsScreen(ModalScreen):
188
188
  def save_settings(self) -> None:
189
189
  """Save the modified settings."""
190
190
  from code_puppy.config import (
191
+ get_model_context_length,
191
192
  set_config_value,
192
193
  set_model_name,
193
- get_model_context_length,
194
194
  )
195
195
 
196
196
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.175
3
+ Version: 0.0.177
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -54,15 +54,15 @@ Description-Content-Type: text/markdown
54
54
 
55
55
  ## Overview
56
56
 
57
- *This project was coded angrily in reaction to Windsurf and Cursor removing access to models and raising prices.*
57
+ *This project was coded angrily in reaction to Windsurf and Cursor removing access to models and raising prices.*
58
58
 
59
59
  *You could also run 50 code puppies at once if you were insane enough.*
60
60
 
61
- *Would you rather plow a field with one ox or 1024 puppies?*
61
+ *Would you rather plow a field with one ox or 1024 puppies?*
62
62
  - If you pick the ox, better slam that back button in your browser.
63
-
64
63
 
65
- Code Puppy is an AI-powered code generation agent, designed to understand programming tasks, generate high-quality code, and explain its reasoning similar to tools like Windsurf and Cursor.
64
+
65
+ Code Puppy is an AI-powered code generation agent, designed to understand programming tasks, generate high-quality code, and explain its reasoning similar to tools like Windsurf and Cursor.
66
66
 
67
67
  ## Quick start
68
68
 
@@ -497,22 +497,22 @@ class MyCustomAgent(BaseAgent):
497
497
  @property
498
498
  def name(self) -> str:
499
499
  return "my-agent"
500
-
500
+
501
501
  @property
502
502
  def display_name(self) -> str:
503
503
  return "My Custom Agent ✨"
504
-
504
+
505
505
  @property
506
506
  def description(self) -> str:
507
507
  return "A custom agent for specialized tasks"
508
-
508
+
509
509
  def get_system_prompt(self) -> str:
510
510
  return "Your custom system prompt here..."
511
-
511
+
512
512
  def get_available_tools(self) -> list[str]:
513
513
  return [
514
514
  "list_files",
515
- "read_file",
515
+ "read_file",
516
516
  "grep",
517
517
  "edit_file",
518
518
  "delete_file",