patchpal 0.23.1__tar.gz → 0.24.0__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 (70) hide show
  1. {patchpal-0.23.1/patchpal.egg-info → patchpal-0.24.0}/PKG-INFO +6 -1
  2. {patchpal-0.23.1 → patchpal-0.24.0}/README.md +2 -0
  3. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/__init__.py +1 -1
  4. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/agent/function_calling.py +86 -0
  5. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/interactive.py +45 -1
  6. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/config.py +10 -0
  7. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/permissions.py +17 -0
  8. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/__init__.py +52 -0
  9. patchpal-0.24.0/patchpal/tools/browser_tools.py +1171 -0
  10. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/definitions.py +275 -0
  11. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/web_tools.py +59 -16
  12. {patchpal-0.23.1 → patchpal-0.24.0/patchpal.egg-info}/PKG-INFO +6 -1
  13. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal.egg-info/SOURCES.txt +1 -0
  14. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal.egg-info/requires.txt +4 -0
  15. {patchpal-0.23.1 → patchpal-0.24.0}/pyproject.toml +4 -0
  16. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_agent.py +40 -6
  17. {patchpal-0.23.1 → patchpal-0.24.0}/LICENSE +0 -0
  18. {patchpal-0.23.1 → patchpal-0.24.0}/MANIFEST.in +0 -0
  19. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/agent/__init__.py +0 -0
  20. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/agent/bedrock_profile_utils.py +0 -0
  21. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/agent/react.py +0 -0
  22. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/__init__.py +0 -0
  23. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/autopilot.py +0 -0
  24. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/mcp.py +0 -0
  25. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/sandbox.py +0 -0
  26. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/cli/streaming.py +0 -0
  27. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/context.py +0 -0
  28. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/prompts/react_prompt.md +0 -0
  29. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/prompts/system_prompt.md +0 -0
  30. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/skills.py +0 -0
  31. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/audit.py +0 -0
  32. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/code_analysis.py +0 -0
  33. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/common.py +0 -0
  34. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/file_reading.py +0 -0
  35. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/file_writing.py +0 -0
  36. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/find_tool.py +0 -0
  37. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/grep_tool.py +0 -0
  38. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/image_handler.py +0 -0
  39. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/mcp.py +0 -0
  40. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/repo_map.py +0 -0
  41. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/shell_tools.py +0 -0
  42. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/todo_tools.py +0 -0
  43. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/tool_schema.py +0 -0
  44. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal/tools/user_interaction.py +0 -0
  45. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal.egg-info/dependency_links.txt +0 -0
  46. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal.egg-info/entry_points.txt +0 -0
  47. {patchpal-0.23.1 → patchpal-0.24.0}/patchpal.egg-info/top_level.txt +0 -0
  48. {patchpal-0.23.1 → patchpal-0.24.0}/setup.cfg +0 -0
  49. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_cli.py +0 -0
  50. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_config_dynamic.py +0 -0
  51. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_context.py +0 -0
  52. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_custom_tools.py +0 -0
  53. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_enabled_tools.py +0 -0
  54. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_find_tool.py +0 -0
  55. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_guardrails.py +0 -0
  56. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_image_blocking.py +0 -0
  57. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_maximum_security.py +0 -0
  58. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_mcp_config.py +0 -0
  59. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_memory.py +0 -0
  60. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_memory_locations.py +0 -0
  61. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_operational_safety.py +0 -0
  62. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_optional_tools.py +0 -0
  63. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_permissions.py +0 -0
  64. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_react.py +0 -0
  65. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_reasoning_content.py +0 -0
  66. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_repo_map.py +0 -0
  67. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_simplified_prompt.py +0 -0
  68. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_skills.py +0 -0
  69. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_streaming.py +0 -0
  70. {patchpal-0.23.1 → patchpal-0.24.0}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: patchpal
3
- Version: 0.23.1
3
+ Version: 0.24.0
4
4
  Summary: An agentic coding and automation assistant, supporting both local and cloud LLMs
5
5
  Author: PatchPal Contributors
6
6
  License-Expression: Apache-2.0
@@ -38,6 +38,9 @@ Requires-Dist: ruff==0.14.13; extra == "dev"
38
38
  Requires-Dist: pre-commit>=3.0.0; extra == "dev"
39
39
  Provides-Extra: mcp
40
40
  Requires-Dist: mcp>=0.9.0; extra == "mcp"
41
+ Provides-Extra: browser
42
+ Requires-Dist: playwright>=1.40.0; extra == "browser"
43
+ Requires-Dist: nest-asyncio>=1.5.0; extra == "browser"
41
44
  Provides-Extra: docs
42
45
  Requires-Dist: mkdocs<2.0,>=1.5.0; extra == "docs"
43
46
  Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
@@ -62,6 +65,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
62
65
  - [Sandbox Mode](https://amaiya.github.io/patchpal/usage/sandbox/) for secure container isolation
63
66
  - [Python SDK](https://amaiya.github.io/patchpal/usage/python-api/) for flexibility and extensibility
64
67
  - [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
68
+ - [Browser Automation](https://amaiya.github.io/patchpal/features/tools/#browser-automation-14-tools-optional) for interactive web tasks, form filling, and JavaScript-heavy sites
65
69
  - [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
66
70
  - [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
67
71
  - [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
@@ -108,6 +112,7 @@ docker run -it --rm \
108
112
 
109
113
  ## Setup
110
114
  0. **Install**: `pip install patchpal`
115
+ - **Optional**: For browser automation (13 additional tools): `pip install patchpal[browser] && python -m playwright install chromium`
111
116
  1. **Get an API key or a Local LLM Engine**:
112
117
  - **[Cloud]** For Anthropic models (default): Sign up at https://console.anthropic.com/
113
118
  - **[Cloud]** For OpenAI models: Get a key from https://platform.openai.com/
@@ -14,6 +14,7 @@ Most agent frameworks are [built in TypeScript](https://news.ycombinator.com/ite
14
14
  - [Sandbox Mode](https://amaiya.github.io/patchpal/usage/sandbox/) for secure container isolation
15
15
  - [Python SDK](https://amaiya.github.io/patchpal/usage/python-api/) for flexibility and extensibility
16
16
  - [Built-In](https://amaiya.github.io/patchpal/features/tools/) and [Custom Tools](https://amaiya.github.io/patchpal/features/custom-tools/)
17
+ - [Browser Automation](https://amaiya.github.io/patchpal/features/tools/#browser-automation-14-tools-optional) for interactive web tasks, form filling, and JavaScript-heavy sites
17
18
  - [Skills System](https://amaiya.github.io/patchpal/features/skills/) and [MCP Integration](https://amaiya.github.io/patchpal/features/mcp/)
18
19
  - [Autopilot Mode](https://amaiya.github.io/patchpal/usage/autopilot/) using [Ralph Wiggum loops](https://github.com/amaiya/patchpal/tree/main/examples/ralph/)
19
20
  - [Project Memory](https://amaiya.github.io/patchpal/features/memory/) automatically loads project context from `MEMORY.md` (repository root or `~/.patchpal/repos/<repo-name>/MEMORY.md`) at startup.
@@ -60,6 +61,7 @@ docker run -it --rm \
60
61
 
61
62
  ## Setup
62
63
  0. **Install**: `pip install patchpal`
64
+ - **Optional**: For browser automation (13 additional tools): `pip install patchpal[browser] && python -m playwright install chromium`
63
65
  1. **Get an API key or a Local LLM Engine**:
64
66
  - **[Cloud]** For Anthropic models (default): Sign up at https://console.anthropic.com/
65
67
  - **[Cloud]** For OpenAI models: Get a key from https://platform.openai.com/
@@ -1,6 +1,6 @@
1
1
  """PatchPal - An open-source Claude Code clone implemented purely in Python."""
2
2
 
3
- __version__ = "0.23.1"
3
+ __version__ = "0.24.0"
4
4
 
5
5
  from patchpal.agent import create_agent, create_react_agent
6
6
  from patchpal.cli.autopilot import autopilot_loop
@@ -1496,6 +1496,92 @@ Repository root takes priority if both exist."""
1496
1496
  "\033[2m❓ Asking user a question...\033[0m",
1497
1497
  flush=True,
1498
1498
  )
1499
+ elif tool_name == "browser_navigate":
1500
+ print(
1501
+ f"\033[2m🌐 Browser navigating: {tool_args.get('url', '')}\033[0m",
1502
+ flush=True,
1503
+ )
1504
+ elif tool_name == "browser_click":
1505
+ print(
1506
+ f"\033[2m🖱️ Browser clicking: {tool_args.get('selector', '')}\033[0m",
1507
+ flush=True,
1508
+ )
1509
+ elif tool_name == "browser_fill":
1510
+ print(
1511
+ f"\033[2m⌨️ Browser filling: {tool_args.get('selector', '')}\033[0m",
1512
+ flush=True,
1513
+ )
1514
+ elif tool_name == "browser_screenshot":
1515
+ print(
1516
+ f"\033[2m📸 Browser screenshot: {tool_args.get('path', '') or 'default path'}\033[0m",
1517
+ flush=True,
1518
+ )
1519
+ elif tool_name == "browser_get_text":
1520
+ print(
1521
+ "\033[2m📄 Browser extracting page text...\033[0m",
1522
+ flush=True,
1523
+ )
1524
+ elif tool_name == "browser_get_html":
1525
+ print(
1526
+ "\033[2m📋 Browser extracting HTML source...\033[0m",
1527
+ flush=True,
1528
+ )
1529
+ elif tool_name == "browser_scroll":
1530
+ direction = tool_args.get("direction", "down")
1531
+ selector = tool_args.get("selector", "")
1532
+ if selector:
1533
+ scroll_desc = f"to '{selector}'"
1534
+ else:
1535
+ scroll_desc = direction
1536
+ print(
1537
+ f"\033[2m📜 Browser scrolling {scroll_desc}...\033[0m",
1538
+ flush=True,
1539
+ )
1540
+ elif tool_name == "browser_execute_script":
1541
+ script_preview = tool_args.get("script", "")[:50]
1542
+ if len(tool_args.get("script", "")) > 50:
1543
+ script_preview += "..."
1544
+ print(
1545
+ f"\033[2m⚡ Browser executing script: {script_preview}\033[0m",
1546
+ flush=True,
1547
+ )
1548
+ elif tool_name == "browser_wait":
1549
+ selector = tool_args.get("selector", "")
1550
+ if selector:
1551
+ wait_desc = f"for '{selector}'"
1552
+ else:
1553
+ wait_desc = f"{tool_args.get('milliseconds', 1000)}ms"
1554
+ print(
1555
+ f"\033[2m⏳ Browser waiting {wait_desc}...\033[0m",
1556
+ flush=True,
1557
+ )
1558
+ elif tool_name == "browser_close":
1559
+ print(
1560
+ "\033[2m🚪 Closing browser...\033[0m",
1561
+ flush=True,
1562
+ )
1563
+ elif tool_name == "browser_list_frames":
1564
+ print(
1565
+ "\033[2m🖼️ Browser listing frames...\033[0m",
1566
+ flush=True,
1567
+ )
1568
+ elif tool_name == "browser_switch_frame":
1569
+ frame_info = ""
1570
+ if "index" in tool_args and tool_args["index"] is not None:
1571
+ frame_info = f"index {tool_args['index']}"
1572
+ elif "name" in tool_args and tool_args["name"] is not None:
1573
+ frame_info = f"'{tool_args['name']}'"
1574
+ else:
1575
+ frame_info = "main page"
1576
+ print(
1577
+ f"\033[2m🖼️ Browser switching to frame: {frame_info}\033[0m",
1578
+ flush=True,
1579
+ )
1580
+ elif tool_name == "browser_dismiss_modals":
1581
+ print(
1582
+ "\033[2m🚫 Browser dismissing modals...\033[0m",
1583
+ flush=True,
1584
+ )
1499
1585
  else:
1500
1586
  # Check if this is an MCP tool by looking for __mcp_server__ attribute
1501
1587
  if hasattr(tool_func, "__mcp_server__"):
@@ -575,6 +575,32 @@ Supported models: Any LiteLLM-supported model
575
575
  pass # Don't fail if audit logging fails
576
576
 
577
577
  print("\nGoodbye!")
578
+
579
+ # Clean up the browser if it was opened during the session.
580
+ # An open Playwright browser keeps its own asyncio machinery
581
+ # alive; closing it here releases the browser process/loop so
582
+ # they don't linger into interpreter shutdown.
583
+ try:
584
+ from patchpal.tools.browser_tools import _BrowserState
585
+
586
+ if _BrowserState.is_open():
587
+ _BrowserState.close(silent=True)
588
+ except Exception:
589
+ pass # Browser tools optional / already closed
590
+
591
+ # Suppress the benign "Task exception was never retrieved"
592
+ # warning that asyncio emits at shutdown: prompt_toolkit's
593
+ # pending Application.run_async() task gets cancelled with a
594
+ # KeyboardInterrupt during teardown, and nobody retrieves it.
595
+ # This is cosmetic and happens AFTER all work is done.
596
+ try:
597
+ import asyncio
598
+
599
+ _loop = asyncio.get_event_loop()
600
+ _loop.set_exception_handler(lambda loop, context: None)
601
+ except Exception:
602
+ pass
603
+
578
604
  break
579
605
 
580
606
  # Handle /help command - show available commands
@@ -1633,7 +1659,25 @@ Supported models: Any LiteLLM-supported model
1633
1659
  )
1634
1660
  continue
1635
1661
  except Exception as e:
1636
- print(f"\n\033[1;31mError:\033[0m {e}")
1662
+ # Debug: Print full exception info to help diagnose asyncio errors
1663
+ import traceback
1664
+
1665
+ error_str = str(e)
1666
+ print(f"\n\033[1;31mError:\033[0m {error_str}")
1667
+
1668
+ # If it's the asyncio error, print more debug info
1669
+ if "asyncio.run() cannot be called from a running event loop" in error_str:
1670
+ print("\n\033[1;33m[DEBUG] AsyncIO Error Details:\033[0m")
1671
+ print(
1672
+ "This error occurs when asyncio.run() is called while an event loop is already running."
1673
+ )
1674
+ print("Stack trace:")
1675
+ traceback.print_exc()
1676
+ print(
1677
+ "\n\033[1;33m[DEBUG] If browser tools were just used, this may be a Playwright cleanup issue.\033[0m"
1678
+ )
1679
+ print("Try: Close any open browser windows and type 'exit' to restart PatchPal.\n")
1680
+
1637
1681
  print("Please try again or type 'exit' to quit.")
1638
1682
 
1639
1683
 
@@ -79,6 +79,16 @@ class Config:
79
79
  """Enable web search and fetch tools (default: true)."""
80
80
  return _get_env_bool("PATCHPAL_ENABLE_WEB", "true")
81
81
 
82
+ @property
83
+ def ENABLE_BROWSER(self) -> bool:
84
+ """Enable browser automation tools (default: true).
85
+
86
+ Browser tools require web access, so they are only available when
87
+ ENABLE_WEB is also true (and Playwright is installed). Set this to
88
+ false to disable the browser tools while keeping web_search/web_fetch.
89
+ """
90
+ return _get_env_bool("PATCHPAL_ENABLE_BROWSER", "true")
91
+
82
92
  @property
83
93
  def ENABLE_MCP(self) -> bool:
84
94
  """Enable Model Context Protocol tools (default: true)."""
@@ -358,6 +358,23 @@ class PermissionManager:
358
358
  if self._check_grant_list(self.persistent_grants, tool_name, pattern, full_command):
359
359
  return True
360
360
 
361
+ # Special case: "browser" pattern applies across all browser tools
362
+ # If browser pattern was granted to ANY browser tool, apply to all
363
+ if pattern == "browser":
364
+ browser_tools = [
365
+ "browser_navigate",
366
+ "browser_click",
367
+ "browser_fill",
368
+ "browser_execute_script",
369
+ ]
370
+ for browser_tool in browser_tools:
371
+ if self._check_grant_list(self.session_grants, browser_tool, pattern, full_command):
372
+ return True
373
+ if self._check_grant_list(
374
+ self.persistent_grants, browser_tool, pattern, full_command
375
+ ):
376
+ return True
377
+
361
378
  return False
362
379
 
363
380
  def _grant_permission(
@@ -67,6 +67,42 @@ from patchpal.tools.web_tools import (
67
67
  web_search,
68
68
  )
69
69
 
70
+ # Browser tools (optional - only available if Playwright installed)
71
+ try:
72
+ from patchpal.tools.browser_tools import (
73
+ PLAYWRIGHT_AVAILABLE,
74
+ browser_click,
75
+ browser_close,
76
+ browser_dismiss_modals,
77
+ browser_execute_script,
78
+ browser_fill,
79
+ browser_get_html,
80
+ browser_get_text,
81
+ browser_list_frames,
82
+ browser_navigate,
83
+ browser_press_key,
84
+ browser_screenshot,
85
+ browser_scroll,
86
+ browser_switch_frame,
87
+ browser_wait,
88
+ )
89
+ except ImportError:
90
+ PLAYWRIGHT_AVAILABLE = False
91
+ browser_navigate = None
92
+ browser_click = None
93
+ browser_fill = None
94
+ browser_screenshot = None
95
+ browser_get_text = None
96
+ browser_get_html = None
97
+ browser_scroll = None
98
+ browser_execute_script = None
99
+ browser_press_key = None
100
+ browser_wait = None
101
+ browser_close = None
102
+ browser_dismiss_modals = None
103
+ browser_list_frames = None
104
+ browser_switch_frame = None
105
+
70
106
  __all__ = [
71
107
  # File operations
72
108
  "read_file",
@@ -96,6 +132,22 @@ __all__ = [
96
132
  "web_fetch",
97
133
  "web_search",
98
134
  "get_url_tracker",
135
+ # Browser tools (optional)
136
+ "PLAYWRIGHT_AVAILABLE",
137
+ "browser_navigate",
138
+ "browser_click",
139
+ "browser_fill",
140
+ "browser_screenshot",
141
+ "browser_get_text",
142
+ "browser_get_html",
143
+ "browser_scroll",
144
+ "browser_execute_script",
145
+ "browser_press_key",
146
+ "browser_wait",
147
+ "browser_close",
148
+ "browser_dismiss_modals",
149
+ "browser_list_frames",
150
+ "browser_switch_frame",
99
151
  # Shell tools
100
152
  "run_shell",
101
153
  # User interaction