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.
- code_puppy/agents/__init__.py +2 -2
- code_puppy/agents/agent_code_puppy.py +2 -1
- code_puppy/agents/agent_creator_agent.py +3 -2
- code_puppy/agents/agent_manager.py +5 -5
- code_puppy/agents/base_agent.py +60 -40
- code_puppy/command_line/command_handler.py +19 -10
- code_puppy/command_line/mcp/start_all_command.py +1 -1
- code_puppy/command_line/mcp/start_command.py +0 -1
- code_puppy/command_line/mcp/stop_all_command.py +1 -1
- code_puppy/command_line/mcp/stop_command.py +1 -0
- code_puppy/config.py +5 -3
- code_puppy/main.py +5 -2
- code_puppy/mcp_/examples/retry_example.py +4 -1
- code_puppy/messaging/spinner/console_spinner.py +1 -1
- code_puppy/model_factory.py +1 -1
- code_puppy/round_robin_model.py +2 -4
- code_puppy/tools/agent_tools.py +10 -8
- code_puppy/tools/browser/browser_screenshot.py +4 -3
- code_puppy/tools/browser/browser_scripts.py +0 -6
- code_puppy/tools/browser/browser_workflows.py +28 -20
- code_puppy/tools/browser/camoufox_manager.py +33 -48
- code_puppy/tools/browser/vqa_agent.py +0 -2
- code_puppy/tools/browser_scripts.py +0 -6
- code_puppy/tools/browser_workflows.py +28 -20
- code_puppy/tools/command_runner.py +1 -1
- code_puppy/tui/app.py +3 -13
- code_puppy/tui/components/chat_view.py +1 -0
- code_puppy/tui/screens/settings.py +3 -3
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/METADATA +10 -10
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/RECORD +34 -35
- code_puppy/tools/camoufox_manager.py +0 -150
- {code_puppy-0.0.175.data → code_puppy-0.0.177.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.175.dist-info → code_puppy-0.0.177.dist-info}/entry_points.txt +0 -0
- {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
|
|
16
|
+
from .vqa_agent import run_vqa_analysis
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
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(
|
|
38
|
-
safe_name +=
|
|
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,
|
|
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(
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
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[
|
|
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(
|
|
129
|
-
name +=
|
|
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 {
|
|
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,
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 (
|
|
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(
|
|
38
|
-
safe_name +=
|
|
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,
|
|
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(
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
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[
|
|
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(
|
|
129
|
-
name +=
|
|
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 {
|
|
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,
|
|
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)}")
|
|
@@ -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.
|
|
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
|
-
|
|
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",
|