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,191 +0,0 @@
1
- """
2
- Tests for the copy button component.
3
- """
4
-
5
- from unittest.mock import MagicMock, patch
6
-
7
- from code_puppy.tui.components.copy_button import CopyButton
8
-
9
-
10
- class TestCopyButton:
11
- """Test cases for the CopyButton widget."""
12
-
13
- def test_copy_button_creation(self):
14
- """Test that a copy button can be created with text."""
15
- test_text = "Hello, World!"
16
- button = CopyButton(test_text)
17
-
18
- assert button.text_to_copy == test_text
19
- assert button.label == "📋 Copy"
20
-
21
- def test_update_text_to_copy(self):
22
- """Test updating the text to copy."""
23
- button = CopyButton("Initial text")
24
- new_text = "Updated text"
25
-
26
- button.update_text_to_copy(new_text)
27
-
28
- assert button.text_to_copy == new_text
29
-
30
- @patch("subprocess.run")
31
- def test_copy_to_clipboard_macos_success(self, mock_run):
32
- """Test successful clipboard copy on macOS."""
33
- mock_run.return_value = MagicMock(returncode=0)
34
-
35
- with patch("sys.platform", "darwin"):
36
- button = CopyButton("test content")
37
- success, error = button.copy_to_clipboard("test content")
38
-
39
- assert success is True
40
- assert error is None
41
- mock_run.assert_called_once_with(
42
- ["pbcopy"],
43
- input="test content",
44
- text=True,
45
- check=True,
46
- capture_output=True,
47
- )
48
-
49
- @patch("subprocess.run")
50
- def test_copy_to_clipboard_windows_success(self, mock_run):
51
- """Test successful clipboard copy on Windows."""
52
- mock_run.return_value = MagicMock(returncode=0)
53
-
54
- with patch("sys.platform", "win32"):
55
- button = CopyButton("test content")
56
- success, error = button.copy_to_clipboard("test content")
57
-
58
- assert success is True
59
- assert error is None
60
- mock_run.assert_called_once_with(
61
- ["clip"],
62
- input="test content",
63
- text=True,
64
- check=True,
65
- capture_output=True,
66
- )
67
-
68
- @patch("subprocess.run")
69
- def test_copy_to_clipboard_linux_success(self, mock_run):
70
- """Test successful clipboard copy on Linux with xclip."""
71
- mock_run.return_value = MagicMock(returncode=0)
72
-
73
- with patch("sys.platform", "linux"):
74
- button = CopyButton("test content")
75
- success, error = button.copy_to_clipboard("test content")
76
-
77
- assert success is True
78
- assert error is None
79
- mock_run.assert_called_once_with(
80
- ["xclip", "-selection", "clipboard"],
81
- input="test content",
82
- text=True,
83
- check=True,
84
- capture_output=True,
85
- )
86
-
87
- @patch("subprocess.run")
88
- def test_copy_to_clipboard_linux_xsel_fallback(self, mock_run):
89
- """Test Linux clipboard copy falls back to xsel when xclip fails."""
90
- # First call (xclip) fails, second call (xsel) succeeds
91
- mock_run.side_effect = [
92
- FileNotFoundError("xclip not found"),
93
- MagicMock(returncode=0),
94
- ]
95
-
96
- with patch("sys.platform", "linux"):
97
- button = CopyButton("test content")
98
- success, error = button.copy_to_clipboard("test content")
99
-
100
- assert success is True
101
- assert error is None
102
- assert mock_run.call_count == 2
103
- # Check that xsel was called as fallback
104
- mock_run.assert_any_call(
105
- ["xsel", "--clipboard", "--input"],
106
- input="test content",
107
- text=True,
108
- check=True,
109
- capture_output=True,
110
- )
111
-
112
- @patch("subprocess.run")
113
- def test_copy_to_clipboard_failure(self, mock_run):
114
- """Test clipboard copy failure handling."""
115
- from subprocess import CalledProcessError
116
-
117
- mock_run.side_effect = CalledProcessError(1, "pbcopy", "Command failed")
118
-
119
- with patch("sys.platform", "darwin"):
120
- button = CopyButton("test content")
121
- success, error = button.copy_to_clipboard("test content")
122
-
123
- assert success is False
124
- assert "Clipboard command failed" in error
125
-
126
- @patch("subprocess.run")
127
- def test_copy_to_clipboard_no_utility(self, mock_run):
128
- """Test clipboard copy when utility is not found."""
129
- mock_run.side_effect = FileNotFoundError("Command not found")
130
-
131
- with patch("sys.platform", "linux"):
132
- button = CopyButton("test content")
133
- success, error = button.copy_to_clipboard("test content")
134
-
135
- assert success is False
136
- assert "Clipboard utilities not found" in error
137
-
138
- def test_copy_button_labels(self):
139
- """Test that copy button has correct labels."""
140
- button = CopyButton("test")
141
-
142
- assert button._original_label == "📋 Copy"
143
- assert button._copied_label == "✅ Copied!"
144
-
145
- def test_copy_completed_message(self):
146
- """Test CopyCompleted message creation."""
147
- # Test success message
148
- success_msg = CopyButton.CopyCompleted(True)
149
- assert success_msg.success is True
150
- assert success_msg.error is None
151
-
152
- # Test error message
153
- error_msg = CopyButton.CopyCompleted(False, "Test error")
154
- assert error_msg.success is False
155
- assert error_msg.error == "Test error"
156
-
157
- @patch.object(CopyButton, "copy_to_clipboard")
158
- @patch.object(CopyButton, "post_message")
159
- def test_action_press_success(self, mock_post_message, mock_copy):
160
- """Test action_press method with successful copy."""
161
- mock_copy.return_value = (True, None)
162
-
163
- button = CopyButton("test content")
164
- button.action_press()
165
-
166
- mock_copy.assert_called_once_with("test content")
167
- mock_post_message.assert_called_once()
168
- # Note: timer is currently commented out in implementation
169
-
170
- # Check that the message posted is a CopyCompleted with success=True
171
- call_args = mock_post_message.call_args[0][0]
172
- assert isinstance(call_args, CopyButton.CopyCompleted)
173
- assert call_args.success is True
174
-
175
- @patch.object(CopyButton, "copy_to_clipboard")
176
- @patch.object(CopyButton, "post_message")
177
- def test_action_press_failure(self, mock_post_message, mock_copy):
178
- """Test action_press method with failed copy."""
179
- mock_copy.return_value = (False, "Test error")
180
-
181
- button = CopyButton("test content")
182
- button.action_press()
183
-
184
- mock_copy.assert_called_once_with("test content")
185
- mock_post_message.assert_called_once()
186
-
187
- # Check that the message posted is a CopyCompleted with success=False
188
- call_args = mock_post_message.call_args[0][0]
189
- assert isinstance(call_args, CopyButton.CopyCompleted)
190
- assert call_args.success is False
191
- assert call_args.error == "Test error"
@@ -1,27 +0,0 @@
1
- import unittest
2
-
3
- from code_puppy.tui.components.custom_widgets import CustomTextArea
4
-
5
-
6
- class DummyEvent:
7
- def __init__(self, key):
8
- self.key = key
9
-
10
-
11
- class TestCustomTextArea(unittest.TestCase):
12
- def setUp(self):
13
- self.text_area = CustomTextArea()
14
-
15
- def test_message_sent_on_enter(self):
16
- # Simulate pressing Enter
17
- event = DummyEvent("enter")
18
- # Should not raise
19
- self.text_area._on_key(event)
20
-
21
- def test_message_sent_class(self):
22
- msg = CustomTextArea.MessageSent()
23
- self.assertIsInstance(msg, CustomTextArea.MessageSent)
24
-
25
-
26
- if __name__ == "__main__":
27
- unittest.main()
@@ -1,27 +0,0 @@
1
- import unittest
2
- from unittest.mock import MagicMock
3
-
4
- # Skip importing the non-existent module
5
- # Commenting out: from code_puppy.tui.screens.disclaimer import DisclaimerScreen
6
-
7
-
8
- # We'll use unittest.skip to skip the entire test class
9
- @unittest.skip("DisclaimerScreen has been removed from the codebase")
10
- class TestDisclaimerScreen(unittest.TestCase):
11
- def setUp(self):
12
- # Create a mock screen instead of the real one
13
- self.screen = MagicMock()
14
- self.screen.get_disclaimer_content.return_value = "Prompt responsibly"
15
- self.screen.compose.return_value = [MagicMock()]
16
-
17
- def test_get_disclaimer_content(self):
18
- content = self.screen.get_disclaimer_content()
19
- self.assertIn("Prompt responsibly", content)
20
-
21
- def test_compose(self):
22
- widgets = list(self.screen.compose())
23
- self.assertGreaterEqual(len(widgets), 1)
24
-
25
-
26
- if __name__ == "__main__":
27
- unittest.main()
@@ -1,15 +0,0 @@
1
- import unittest
2
-
3
- from code_puppy.tui.models.enums import MessageType
4
-
5
-
6
- class TestMessageType(unittest.TestCase):
7
- def test_enum_values(self):
8
- self.assertEqual(MessageType.USER.value, "user")
9
- self.assertEqual(MessageType.AGENT.value, "agent")
10
- self.assertEqual(MessageType.SYSTEM.value, "system")
11
- self.assertEqual(MessageType.ERROR.value, "error")
12
-
13
-
14
- if __name__ == "__main__":
15
- unittest.main()
@@ -1,60 +0,0 @@
1
- """Tests for the FileBrowser component."""
2
-
3
- from unittest.mock import MagicMock
4
-
5
- import pytest
6
-
7
- # Import only Sidebar which exists, and skip FileBrowser
8
- from code_puppy.tui.components import Sidebar
9
-
10
-
11
- # Use pytest.skip for skipping the FileBrowser tests
12
- @pytest.mark.skip(reason="FileBrowser component has been removed from the codebase")
13
- class TestFileBrowser:
14
- """Test the FileBrowser component."""
15
-
16
- def test_file_browser_creation(self):
17
- """Test that FileBrowser can be created."""
18
- # Create a mock instead of the real component
19
- browser = MagicMock()
20
- assert browser is not None
21
-
22
- def test_file_browser_has_directory_tree(self):
23
- """Test that FileBrowser contains a DirectoryTree widget."""
24
- browser = MagicMock()
25
- browser.compose = MagicMock()
26
- # This is a basic structure test - in a real app test we'd mount it
27
- assert hasattr(browser, "compose")
28
-
29
- def test_file_browser_message_type(self):
30
- """Test that FileBrowser.FileSelected message works."""
31
-
32
- # Create a mock message class
33
- class MockFileSelected:
34
- def __init__(self, file_path):
35
- self.file_path = file_path
36
-
37
- message = MockFileSelected("/test/path/file.py")
38
- assert message.file_path == "/test/path/file.py"
39
-
40
-
41
- class TestSidebarTabs:
42
- """Test the enhanced Sidebar with tabs."""
43
-
44
- def test_sidebar_creation(self):
45
- """Test that enhanced Sidebar can be created."""
46
- sidebar = Sidebar()
47
- assert sidebar is not None
48
-
49
- def test_sidebar_has_compose_method(self):
50
- """Test that Sidebar has the compose method for tab layout."""
51
- sidebar = Sidebar()
52
- assert hasattr(sidebar, "compose")
53
- # Skip checking methods that may have been removed
54
- # Comment out removed methods:
55
- # assert hasattr(sidebar, "load_models_list")
56
- # assert hasattr(sidebar, "on_file_browser_file_selected")
57
-
58
-
59
- if __name__ == "__main__":
60
- pytest.main([__file__])
@@ -1,38 +0,0 @@
1
- import unittest
2
-
3
- from textual.app import App
4
-
5
- from code_puppy.tui.screens.help import HelpScreen
6
-
7
-
8
- class TestHelpScreen(unittest.TestCase):
9
- def setUp(self):
10
- self.screen = HelpScreen()
11
-
12
- def test_get_help_content(self):
13
- content = self.screen.get_help_content()
14
- self.assertIn("Code Puppy TUI", content)
15
-
16
- def test_compose(self):
17
- # Create a minimal app context for testing
18
- class TestApp(App):
19
- def compose(self):
20
- yield self.screen
21
-
22
- app = TestApp()
23
- self.screen = HelpScreen()
24
-
25
- # Test that compose returns widgets without error
26
- try:
27
- # Use app.run_test() context to provide proper app context
28
- with app:
29
- widgets = list(self.screen.compose())
30
- self.assertGreaterEqual(len(widgets), 1)
31
- except Exception:
32
- # If compose still fails, just verify the method exists
33
- self.assertTrue(hasattr(self.screen, "compose"))
34
- self.assertTrue(callable(getattr(self.screen, "compose")))
35
-
36
-
37
- if __name__ == "__main__":
38
- unittest.main()
@@ -1,107 +0,0 @@
1
- import os
2
- import tempfile
3
- import unittest
4
-
5
- from code_puppy.tui.models.command_history import HistoryFileReader
6
-
7
-
8
- class TestHistoryFileReader(unittest.TestCase):
9
- def setUp(self):
10
- # Create a temporary file for testing
11
- self.temp_file = tempfile.NamedTemporaryFile(delete=False)
12
- self.temp_file_path = self.temp_file.name
13
-
14
- # Sample content with multiple commands
15
- sample_content = """
16
- # 2023-01-01T12:00:00
17
- First command
18
-
19
- # 2023-01-01T13:00:00
20
- Second command
21
- with multiple lines
22
-
23
- # 2023-01-01T14:00:00
24
- Third command
25
- """
26
- # Write sample content to the temporary file
27
- with open(self.temp_file_path, "w") as f:
28
- f.write(sample_content)
29
-
30
- # Initialize reader with the temp file
31
- self.reader = HistoryFileReader(self.temp_file_path)
32
-
33
- def tearDown(self):
34
- # Clean up the temporary file
35
- if os.path.exists(self.temp_file_path):
36
- os.unlink(self.temp_file_path)
37
-
38
- def test_read_history(self):
39
- # Test reading history entries
40
- entries = self.reader.read_history()
41
-
42
- # Check that we have the correct number of entries
43
- self.assertEqual(len(entries), 3)
44
-
45
- # Check that entries are in reverse chronological order (newest first)
46
- self.assertEqual(entries[0]["timestamp"], "2023-01-01T14:00:00")
47
- self.assertEqual(entries[0]["command"], "Third command")
48
-
49
- self.assertEqual(entries[1]["timestamp"], "2023-01-01T13:00:00")
50
- self.assertEqual(entries[1]["command"], "Second command\nwith multiple lines")
51
-
52
- self.assertEqual(entries[2]["timestamp"], "2023-01-01T12:00:00")
53
- self.assertEqual(entries[2]["command"], "First command")
54
-
55
- def test_read_history_with_limit(self):
56
- # Test reading history with a limit
57
- entries = self.reader.read_history(max_entries=2)
58
-
59
- # Check that we only get the specified number of entries
60
- self.assertEqual(len(entries), 2)
61
-
62
- # Check that we get the most recent entries
63
- self.assertEqual(entries[0]["timestamp"], "2023-01-01T14:00:00")
64
- self.assertEqual(entries[1]["timestamp"], "2023-01-01T13:00:00")
65
-
66
- def test_read_history_empty_file(self):
67
- # Create an empty file
68
- empty_file = tempfile.NamedTemporaryFile(delete=False)
69
- empty_file_path = empty_file.name
70
- empty_file.close()
71
-
72
- try:
73
- # Create reader with empty file
74
- empty_reader = HistoryFileReader(empty_file_path)
75
-
76
- # Should return empty list
77
- entries = empty_reader.read_history()
78
- self.assertEqual(len(entries), 0)
79
- finally:
80
- # Clean up
81
- if os.path.exists(empty_file_path):
82
- os.unlink(empty_file_path)
83
-
84
- def test_read_history_nonexistent_file(self):
85
- # Create reader with non-existent file
86
- nonexistent_reader = HistoryFileReader("/nonexistent/file/path")
87
-
88
- # Should return empty list, not raise an exception
89
- entries = nonexistent_reader.read_history()
90
- self.assertEqual(len(entries), 0)
91
-
92
- def test_format_timestamp(self):
93
- # Test default formatting
94
- formatted = self.reader.format_timestamp("2023-01-01T12:34:56")
95
- self.assertEqual(formatted, "12:34:56")
96
-
97
- # Test custom format
98
- formatted = self.reader.format_timestamp("2023-01-01T12:34:56", "%H:%M")
99
- self.assertEqual(formatted, "12:34")
100
-
101
- # Test invalid timestamp
102
- formatted = self.reader.format_timestamp("invalid")
103
- self.assertEqual(formatted, "invalid")
104
-
105
-
106
- if __name__ == "__main__":
107
- unittest.main()
@@ -1,33 +0,0 @@
1
- import unittest
2
-
3
- from textual.app import App
4
-
5
- from code_puppy.tui.components.input_area import InputArea
6
-
7
-
8
- class TestInputArea(unittest.TestCase):
9
- def setUp(self):
10
- self.input_area = InputArea()
11
-
12
- def test_compose(self):
13
- # Create a minimal app context for testing
14
- class TestApp(App):
15
- def compose(self):
16
- yield self.input_area
17
-
18
- app = TestApp()
19
- self.input_area = InputArea()
20
-
21
- # Test that compose returns widgets without error
22
- try:
23
- with app:
24
- widgets = list(self.input_area.compose())
25
- self.assertGreaterEqual(len(widgets), 3)
26
- except Exception:
27
- # If compose still fails, just verify the method exists
28
- self.assertTrue(hasattr(self.input_area, "compose"))
29
- self.assertTrue(callable(getattr(self.input_area, "compose")))
30
-
31
-
32
- if __name__ == "__main__":
33
- unittest.main()
@@ -1,44 +0,0 @@
1
- import unittest
2
-
3
- from textual.app import App
4
-
5
- from code_puppy.tui.screens.settings import SettingsScreen
6
-
7
-
8
- class TestSettingsScreen(unittest.TestCase):
9
- def setUp(self):
10
- self.screen = SettingsScreen()
11
-
12
- def test_compose(self):
13
- # Create a minimal app context for testing
14
- class TestApp(App):
15
- def compose(self):
16
- yield self.screen
17
-
18
- app = TestApp()
19
- self.screen = SettingsScreen()
20
-
21
- # Test that compose returns widgets without error
22
- try:
23
- with app:
24
- widgets = list(self.screen.compose())
25
- self.assertGreaterEqual(len(widgets), 1)
26
- except Exception:
27
- # If compose still fails, just verify the method exists
28
- self.assertTrue(hasattr(self.screen, "compose"))
29
- self.assertTrue(callable(getattr(self.screen, "compose")))
30
-
31
- def test_load_model_options_fallback(self):
32
- class DummySelect:
33
- def set_options(self, options):
34
- self.options = options
35
-
36
- select = DummySelect()
37
- # Should fallback to default if file not found
38
- self.screen.load_model_options(select)
39
- self.assertTrue(hasattr(select, "options"))
40
- self.assertGreaterEqual(len(select.options), 1)
41
-
42
-
43
- if __name__ == "__main__":
44
- unittest.main()
@@ -1,33 +0,0 @@
1
- import unittest
2
-
3
- from textual.app import App
4
-
5
- from code_puppy.tui.components.sidebar import Sidebar
6
-
7
-
8
- class TestSidebar(unittest.TestCase):
9
- def setUp(self):
10
- self.sidebar = Sidebar()
11
-
12
- def test_compose(self):
13
- # Create a minimal app context for testing
14
- class TestApp(App):
15
- def compose(self):
16
- yield self.sidebar
17
-
18
- app = TestApp()
19
- self.sidebar = Sidebar()
20
-
21
- # Test that compose returns widgets without error
22
- try:
23
- with app:
24
- widgets = list(self.sidebar.compose())
25
- self.assertGreaterEqual(len(widgets), 1)
26
- except Exception:
27
- # If compose still fails, just verify the method exists
28
- self.assertTrue(hasattr(self.sidebar, "compose"))
29
- self.assertTrue(callable(getattr(self.sidebar, "compose")))
30
-
31
-
32
- if __name__ == "__main__":
33
- unittest.main()