code-puppy 0.0.154__py3-none-any.whl → 0.0.156__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 (48) hide show
  1. code_puppy/agent.py +26 -5
  2. code_puppy/agents/agent_creator_agent.py +65 -13
  3. code_puppy/agents/json_agent.py +8 -0
  4. code_puppy/agents/runtime_manager.py +12 -4
  5. code_puppy/command_line/command_handler.py +83 -0
  6. code_puppy/command_line/mcp/install_command.py +50 -1
  7. code_puppy/command_line/mcp/wizard_utils.py +88 -17
  8. code_puppy/command_line/prompt_toolkit_completion.py +18 -2
  9. code_puppy/config.py +8 -2
  10. code_puppy/main.py +17 -4
  11. code_puppy/mcp/__init__.py +2 -2
  12. code_puppy/mcp/config_wizard.py +1 -1
  13. code_puppy/messaging/spinner/console_spinner.py +1 -1
  14. code_puppy/model_factory.py +13 -12
  15. code_puppy/models.json +26 -0
  16. code_puppy/round_robin_model.py +35 -18
  17. code_puppy/summarization_agent.py +1 -3
  18. code_puppy/tools/agent_tools.py +41 -138
  19. code_puppy/tools/file_operations.py +116 -96
  20. code_puppy/tui/app.py +1 -1
  21. {code_puppy-0.0.154.data → code_puppy-0.0.156.data}/data/code_puppy/models.json +26 -0
  22. {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/METADATA +4 -3
  23. {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/RECORD +26 -48
  24. code_puppy/token_utils.py +0 -67
  25. code_puppy/tools/token_check.py +0 -32
  26. code_puppy/tui/tests/__init__.py +0 -1
  27. code_puppy/tui/tests/test_agent_command.py +0 -79
  28. code_puppy/tui/tests/test_chat_message.py +0 -28
  29. code_puppy/tui/tests/test_chat_view.py +0 -88
  30. code_puppy/tui/tests/test_command_history.py +0 -89
  31. code_puppy/tui/tests/test_copy_button.py +0 -191
  32. code_puppy/tui/tests/test_custom_widgets.py +0 -27
  33. code_puppy/tui/tests/test_disclaimer.py +0 -27
  34. code_puppy/tui/tests/test_enums.py +0 -15
  35. code_puppy/tui/tests/test_file_browser.py +0 -60
  36. code_puppy/tui/tests/test_help.py +0 -38
  37. code_puppy/tui/tests/test_history_file_reader.py +0 -107
  38. code_puppy/tui/tests/test_input_area.py +0 -33
  39. code_puppy/tui/tests/test_settings.py +0 -44
  40. code_puppy/tui/tests/test_sidebar.py +0 -33
  41. code_puppy/tui/tests/test_sidebar_history.py +0 -153
  42. code_puppy/tui/tests/test_sidebar_history_navigation.py +0 -132
  43. code_puppy/tui/tests/test_status_bar.py +0 -54
  44. code_puppy/tui/tests/test_timestamped_history.py +0 -52
  45. code_puppy/tui/tests/test_tools.py +0 -82
  46. {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/WHEEL +0 -0
  47. {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/entry_points.txt +0 -0
  48. {code_puppy-0.0.154.dist-info → code_puppy-0.0.156.dist-info}/licenses/LICENSE +0 -0
@@ -1,153 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock, patch
3
-
4
- from textual.widgets import ListItem, ListView
5
-
6
- from code_puppy.tui.components.command_history_modal import CommandHistoryModal
7
- from code_puppy.tui.components.sidebar import Sidebar
8
- from code_puppy.tui.models.command_history import HistoryFileReader
9
-
10
-
11
- class TestSidebarHistory(unittest.TestCase):
12
- def setUp(self):
13
- # Create a sidebar
14
- self.sidebar = Sidebar()
15
-
16
- # Mock history_list
17
- self.mock_history_list = MagicMock(spec=ListView)
18
- self.mock_history_list.children = []
19
- self.sidebar.query_one = MagicMock(return_value=self.mock_history_list)
20
-
21
- # Mock the app's push_screen method without trying to set the app property
22
- self.mock_push_screen = MagicMock()
23
-
24
- @patch.object(HistoryFileReader, "read_history")
25
- def test_load_command_history(self, mock_read_history):
26
- # Mock the history entries
27
- mock_entries = [
28
- {"timestamp": "2023-01-01T12:34:56", "command": "First command"},
29
- {"timestamp": "2023-01-01T13:45:00", "command": "Second command"},
30
- ]
31
- mock_read_history.return_value = mock_entries
32
-
33
- # Call the method
34
- self.sidebar.load_command_history()
35
-
36
- # Check that ListView.append was called for each entry
37
- self.assertEqual(self.mock_history_list.append.call_count, 2)
38
-
39
- # Check that ListView.clear was called
40
- self.mock_history_list.clear.assert_called_once()
41
-
42
- @patch.object(HistoryFileReader, "read_history")
43
- def test_load_command_history_empty(self, mock_read_history):
44
- # Mock empty history
45
- mock_read_history.return_value = []
46
-
47
- # Call the method
48
- self.sidebar.load_command_history()
49
-
50
- # Check that an empty message was added
51
- self.mock_history_list.append.assert_called_once()
52
- # Just verify append was called, don't try to access complex children structure
53
- self.assertTrue(self.mock_history_list.append.called)
54
-
55
- @patch.object(HistoryFileReader, "read_history")
56
- def test_load_command_history_exception(self, mock_read_history):
57
- # Force an exception
58
- mock_read_history.side_effect = Exception("Test error")
59
-
60
- # Call the method
61
- self.sidebar.load_command_history()
62
-
63
- # Check that an error message was added
64
- self.mock_history_list.append.assert_called_once()
65
- # Just verify append was called, don't try to access complex children structure
66
- self.assertTrue(self.mock_history_list.append.called)
67
-
68
- @patch.object(HistoryFileReader, "read_history")
69
- def test_load_command_history_filters_cli_commands(self, mock_read_history):
70
- # Mock history with CLI commands mixed with regular commands
71
- mock_read_history.return_value = [
72
- {
73
- "timestamp": "2024-01-01T10:00:00Z",
74
- "command": "How do I create a function?",
75
- },
76
- {"timestamp": "2024-01-01T10:01:00Z", "command": "/help"},
77
- {"timestamp": "2024-01-01T10:02:00Z", "command": "Write a Python script"},
78
- {"timestamp": "2024-01-01T10:04:00Z", "command": "/exit"},
79
- {"timestamp": "2024-01-01T10:05:00Z", "command": "Debug this error"},
80
- {"timestamp": "2024-01-01T10:06:00Z", "command": "/m gpt-4"},
81
- {"timestamp": "2024-01-01T10:07:00Z", "command": "Explain this code"},
82
- ]
83
-
84
- # Call the method
85
- self.sidebar.load_command_history()
86
-
87
- # Verify that CLI commands were filtered out
88
- # Should have 4 non-CLI commands: "How do I create a function?", "Write a Python script", "Debug this error", "Explain this code"
89
- self.assertEqual(len(self.sidebar.history_entries), 4)
90
-
91
- # Verify the filtered commands are the correct ones
92
- commands = [entry["command"] for entry in self.sidebar.history_entries]
93
- expected_commands = [
94
- "How do I create a function?",
95
- "Write a Python script",
96
- "Debug this error",
97
- "Explain this code",
98
- ]
99
- self.assertEqual(commands, expected_commands)
100
-
101
- # Verify CLI commands are not in the filtered list
102
- for entry in self.sidebar.history_entries:
103
- command = entry["command"]
104
- self.assertFalse(
105
- any(
106
- command.startswith(cli_cmd)
107
- for cli_cmd in {
108
- "/help",
109
- "/exit",
110
- "/m",
111
- "/motd",
112
- "/show",
113
- "/set",
114
- "/tools",
115
- }
116
- )
117
- )
118
-
119
- @patch(
120
- "code_puppy.tui.components.sidebar.Sidebar.app",
121
- new_callable=lambda: MagicMock(),
122
- )
123
- def test_on_key_enter(self, mock_app_property):
124
- # Create a mock highlighted child with a command entry
125
- mock_item = MagicMock(spec=ListItem)
126
- mock_item.command_entry = {
127
- "timestamp": "2023-01-01T12:34:56",
128
- "command": "Test command",
129
- }
130
-
131
- self.mock_history_list.highlighted_child = mock_item
132
- self.mock_history_list.has_focus = True
133
- self.mock_history_list.index = 0
134
-
135
- # Create a mock Key event
136
- mock_event = MagicMock()
137
- mock_event.key = "enter"
138
-
139
- # Call the method
140
- self.sidebar.on_key(mock_event)
141
-
142
- # Check that push_screen was called with CommandHistoryModal
143
- mock_app_property.push_screen.assert_called_once()
144
- args, kwargs = mock_app_property.push_screen.call_args
145
- self.assertIsInstance(args[0], CommandHistoryModal)
146
-
147
- # Check that event propagation was stopped
148
- mock_event.stop.assert_called_once()
149
- mock_event.prevent_default.assert_called_once()
150
-
151
-
152
- if __name__ == "__main__":
153
- unittest.main()
@@ -1,132 +0,0 @@
1
- """
2
- Tests for the history navigation in the sidebar component.
3
- """
4
-
5
- import pytest
6
- from textual.app import App
7
-
8
- from code_puppy.tui.components.command_history_modal import CommandHistoryModal
9
- from code_puppy.tui.components.sidebar import Sidebar
10
-
11
-
12
- class TestSidebarHistoryNavigation:
13
- """Tests for the history navigation functionality in the sidebar."""
14
-
15
- @pytest.fixture
16
- def sidebar(self):
17
- """Create a sidebar instance for testing."""
18
- sidebar = Sidebar()
19
- return sidebar
20
-
21
- async def test_navigation_index_tracking(self, sidebar):
22
- """Test that the index tracking works correctly for navigation."""
23
- # Setup test data
24
- sidebar.history_entries = [
25
- {"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
26
- {"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
27
- {"command": "command3", "timestamp": "2023-01-01T12:00:00Z"},
28
- ]
29
- sidebar.current_history_index = 0
30
-
31
- # Test navigation to next command
32
- assert sidebar.navigate_to_next_command() is True
33
- assert sidebar.current_history_index == 1
34
-
35
- # Test navigation to next command again
36
- assert sidebar.navigate_to_next_command() is True
37
- assert sidebar.current_history_index == 2
38
-
39
- # Test navigation at the end of the list
40
- assert sidebar.navigate_to_next_command() is False
41
- assert sidebar.current_history_index == 2 # Index shouldn't change
42
-
43
- # Test navigation to previous command
44
- assert sidebar.navigate_to_previous_command() is True
45
- assert sidebar.current_history_index == 1
46
-
47
- # Test navigation to previous command again
48
- assert sidebar.navigate_to_previous_command() is True
49
- assert sidebar.current_history_index == 0
50
-
51
- # Test navigation at the beginning of the list
52
- assert sidebar.navigate_to_previous_command() is False
53
- assert sidebar.current_history_index == 0 # Index shouldn't change
54
-
55
- async def test_get_current_command_entry(self, sidebar):
56
- """Test that the current command entry is retrieved correctly."""
57
- # Setup test data
58
- sidebar.history_entries = [
59
- {"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
60
- {"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
61
- ]
62
-
63
- # Test getting entry at index 0
64
- sidebar.current_history_index = 0
65
- entry = sidebar.get_current_command_entry()
66
- assert entry["command"] == "command1"
67
- assert entry["timestamp"] == "2023-01-01T10:00:00Z"
68
-
69
- # Test getting entry at index 1
70
- sidebar.current_history_index = 1
71
- entry = sidebar.get_current_command_entry()
72
- assert entry["command"] == "command2"
73
- assert entry["timestamp"] == "2023-01-01T11:00:00Z"
74
-
75
- # Test getting entry with invalid index
76
- sidebar.current_history_index = 99
77
- entry = sidebar.get_current_command_entry()
78
- assert entry == {"command": "", "timestamp": ""}
79
-
80
- # Test getting entry with empty history entries
81
- sidebar.history_entries = []
82
- sidebar.current_history_index = 0
83
- entry = sidebar.get_current_command_entry()
84
- assert entry == {"command": "", "timestamp": ""}
85
-
86
- class TestApp(App):
87
- """Test app for simulating modal and sidebar interaction."""
88
-
89
- def compose(self):
90
- """Create the app layout."""
91
- self.sidebar = Sidebar()
92
- yield self.sidebar
93
-
94
- async def test_modal_navigation_integration(self, monkeypatch):
95
- """Test that the modal uses the sidebar's navigation methods."""
96
- app = self.TestApp()
97
- async with app.run_test() as pilot:
98
- # Setup test data in sidebar
99
- app.sidebar.history_entries = [
100
- {"command": "command1", "timestamp": "2023-01-01T10:00:00Z"},
101
- {"command": "command2", "timestamp": "2023-01-01T11:00:00Z"},
102
- {"command": "command3", "timestamp": "2023-01-01T12:00:00Z"},
103
- ]
104
- app.sidebar.current_history_index = 0
105
-
106
- # Create and mount the modal
107
- modal = CommandHistoryModal()
108
- modal.sidebar = app.sidebar
109
- app.push_screen(modal)
110
- await pilot.pause()
111
-
112
- # Test initial state
113
- assert modal.command == "command1"
114
- assert modal.timestamp == "2023-01-01T10:00:00Z"
115
-
116
- # Test navigation down
117
- await pilot.press("down")
118
- assert app.sidebar.current_history_index == 1
119
- assert modal.command == "command2"
120
- assert modal.timestamp == "2023-01-01T11:00:00Z"
121
-
122
- # Test navigation down again
123
- await pilot.press("down")
124
- assert app.sidebar.current_history_index == 2
125
- assert modal.command == "command3"
126
- assert modal.timestamp == "2023-01-01T12:00:00Z"
127
-
128
- # Test navigation up
129
- await pilot.press("up")
130
- assert app.sidebar.current_history_index == 1
131
- assert modal.command == "command2"
132
- assert modal.timestamp == "2023-01-01T11:00:00Z"
@@ -1,54 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock, patch
3
-
4
- from code_puppy.tui.components.status_bar import StatusBar
5
-
6
-
7
- class TestStatusBar(unittest.TestCase):
8
- def setUp(self):
9
- self.status_bar = StatusBar()
10
-
11
- def test_compose(self):
12
- widgets = list(self.status_bar.compose())
13
- self.assertGreaterEqual(len(widgets), 1)
14
-
15
- @patch(
16
- "code_puppy.tui.components.status_bar.StatusBar.app",
17
- new_callable=lambda: MagicMock(),
18
- )
19
- def test_update_status(self, mock_app_property):
20
- # Mock the query_one method to avoid DOM dependency
21
- mock_status_widget = MagicMock()
22
- self.status_bar.query_one = MagicMock(return_value=mock_status_widget)
23
-
24
- # Mock the app.size to avoid app dependency
25
- mock_app_property.size.width = 80
26
-
27
- # Should not raise
28
- self.status_bar.update_status()
29
-
30
- # Verify that update was called on the status widget (may be called multiple times)
31
- self.assertTrue(mock_status_widget.update.called)
32
-
33
- @patch(
34
- "code_puppy.tui.components.status_bar.StatusBar.app",
35
- new_callable=lambda: MagicMock(),
36
- )
37
- def test_watchers(self, mock_app_property):
38
- # Mock the query_one method to avoid DOM dependency
39
- mock_status_widget = MagicMock()
40
- self.status_bar.query_one = MagicMock(return_value=mock_status_widget)
41
-
42
- # Mock the app.size to avoid app dependency
43
- mock_app_property.size.width = 80
44
-
45
- # Should call update_status without error
46
- self.status_bar.watch_current_model()
47
- self.status_bar.watch_puppy_name()
48
- self.status_bar.watch_connection_status()
49
- self.status_bar.watch_agent_status()
50
- self.status_bar.watch_progress_visible()
51
-
52
-
53
- if __name__ == "__main__":
54
- unittest.main()
@@ -1,52 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock, patch
3
-
4
- from code_puppy.config import save_command_to_history
5
- from code_puppy.tui.app import CodePuppyTUI
6
- from code_puppy.tui.components.custom_widgets import CustomTextArea
7
-
8
-
9
- class TestTimestampedHistory(unittest.TestCase):
10
- def setUp(self):
11
- self.app = CodePuppyTUI()
12
-
13
- @patch("code_puppy.tui.app.save_command_to_history")
14
- def test_action_send_message_uses_timestamp_function(self, mock_save_command):
15
- # Setup test mocks
16
- self.app.query_one = MagicMock()
17
- input_field_mock = MagicMock(spec=CustomTextArea)
18
- input_field_mock.text = "test command"
19
- self.app.query_one.return_value = input_field_mock
20
-
21
- # Mock other methods to prevent full execution
22
- self.app.add_user_message = MagicMock()
23
- self.app._update_submit_cancel_button = MagicMock()
24
- self.app.run_worker = MagicMock()
25
-
26
- # Execute
27
- self.app.action_send_message()
28
-
29
- # Assertions
30
- mock_save_command.assert_called_once_with("test command")
31
- self.app.add_user_message.assert_called_once_with("test command")
32
-
33
- @patch("datetime.datetime")
34
- @patch("builtins.open", new_callable=unittest.mock.mock_open)
35
- def test_save_command_uses_iso_timestamp(self, mock_file, mock_datetime):
36
- # Setup
37
- mock_now = MagicMock()
38
- mock_now.isoformat.return_value = "2023-01-01T12:34:56"
39
- mock_datetime.now.return_value = mock_now
40
-
41
- # Call function
42
- save_command_to_history("test command")
43
-
44
- # Assertions
45
- mock_file().write.assert_called_once_with(
46
- "\n# 2023-01-01T12:34:56\ntest command\n"
47
- )
48
- mock_now.isoformat.assert_called_once_with(timespec="seconds")
49
-
50
-
51
- if __name__ == "__main__":
52
- unittest.main()
@@ -1,82 +0,0 @@
1
- """
2
- Tests for ToolsScreen TUI component.
3
- """
4
-
5
- from unittest.mock import patch
6
-
7
- from code_puppy.tools.tools_content import tools_content
8
- from code_puppy.tui.screens.tools import ToolsScreen
9
-
10
-
11
- class TestToolsScreen:
12
- """Test cases for ToolsScreen functionality."""
13
-
14
- def test_tools_screen_initialization(self):
15
- """Test that ToolsScreen can be initialized."""
16
- screen = ToolsScreen()
17
- assert screen is not None
18
- assert isinstance(screen, ToolsScreen)
19
-
20
- def test_tools_content_import(self):
21
- """Test that tools_content is imported correctly."""
22
- # Verify that tools_content is a non-empty string
23
- assert isinstance(tools_content, str)
24
- assert len(tools_content) > 0
25
- assert "File Operations" in tools_content
26
- assert "Search & Analysis" in tools_content
27
-
28
- def test_screen_composition(self):
29
- """Test that screen has compose method and can be called."""
30
- screen = ToolsScreen()
31
-
32
- # Verify the compose method exists and is callable
33
- assert hasattr(screen, "compose")
34
- assert callable(screen.compose)
35
-
36
- def test_markdown_widget_receives_tools_content(self):
37
- """Test that Markdown widget receives tools_content."""
38
- # Instead of actually executing compose, verify the tools.py implementation
39
- # directly by examining the source code
40
- import inspect
41
-
42
- source = inspect.getsource(ToolsScreen.compose)
43
-
44
- # Check that the compose method references tools_content
45
- assert "tools_content" in source
46
- # Check that Markdown is created with tools_content
47
- assert "yield Markdown(tools_content" in source
48
-
49
- def test_dismiss_functionality(self):
50
- """Test that dismiss button works correctly."""
51
- screen = ToolsScreen()
52
-
53
- # Mock the dismiss method
54
- with patch.object(screen, "dismiss") as mock_dismiss:
55
- screen.dismiss_tools()
56
-
57
- mock_dismiss.assert_called_once()
58
-
59
- def test_escape_key_dismisses(self):
60
- """Test that escape key dismisses the screen."""
61
- screen = ToolsScreen()
62
-
63
- # Create a mock key event
64
- class MockKeyEvent:
65
- key = "escape"
66
-
67
- with patch.object(screen, "dismiss") as mock_dismiss:
68
- screen.on_key(MockKeyEvent())
69
-
70
- mock_dismiss.assert_called_once()
71
-
72
- def test_non_escape_key_ignored(self):
73
- """Test that non-escape keys don't dismiss the screen."""
74
- screen = ToolsScreen()
75
-
76
- class MockKeyEvent:
77
- key = "enter"
78
-
79
- with patch.object(screen, "dismiss") as mock_dismiss:
80
- screen.on_key(MockKeyEvent())
81
-
82
- mock_dismiss.assert_not_called()