minion-code 0.1.0__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 (59) hide show
  1. examples/advance_tui.py +508 -0
  2. examples/agent_with_todos.py +165 -0
  3. examples/file_freshness_example.py +97 -0
  4. examples/file_watching_example.py +110 -0
  5. examples/interruptible_tui.py +5 -0
  6. examples/message_response_children_demo.py +226 -0
  7. examples/rich_example.py +4 -0
  8. examples/simple_file_watching.py +57 -0
  9. examples/simple_tui.py +267 -0
  10. examples/simple_usage.py +69 -0
  11. minion_code/__init__.py +16 -0
  12. minion_code/agents/__init__.py +11 -0
  13. minion_code/agents/code_agent.py +320 -0
  14. minion_code/cli.py +502 -0
  15. minion_code/commands/__init__.py +90 -0
  16. minion_code/commands/clear_command.py +70 -0
  17. minion_code/commands/help_command.py +90 -0
  18. minion_code/commands/history_command.py +104 -0
  19. minion_code/commands/quit_command.py +32 -0
  20. minion_code/commands/status_command.py +115 -0
  21. minion_code/commands/tools_command.py +86 -0
  22. minion_code/commands/version_command.py +104 -0
  23. minion_code/components/Message.py +304 -0
  24. minion_code/components/MessageResponse.py +188 -0
  25. minion_code/components/PromptInput.py +534 -0
  26. minion_code/components/__init__.py +29 -0
  27. minion_code/screens/REPL.py +925 -0
  28. minion_code/screens/__init__.py +4 -0
  29. minion_code/services/__init__.py +50 -0
  30. minion_code/services/event_system.py +108 -0
  31. minion_code/services/file_freshness_service.py +582 -0
  32. minion_code/tools/__init__.py +69 -0
  33. minion_code/tools/bash_tool.py +58 -0
  34. minion_code/tools/file_edit_tool.py +238 -0
  35. minion_code/tools/file_read_tool.py +73 -0
  36. minion_code/tools/file_write_tool.py +36 -0
  37. minion_code/tools/glob_tool.py +58 -0
  38. minion_code/tools/grep_tool.py +105 -0
  39. minion_code/tools/ls_tool.py +65 -0
  40. minion_code/tools/multi_edit_tool.py +271 -0
  41. minion_code/tools/python_interpreter_tool.py +105 -0
  42. minion_code/tools/todo_read_tool.py +100 -0
  43. minion_code/tools/todo_write_tool.py +234 -0
  44. minion_code/tools/user_input_tool.py +53 -0
  45. minion_code/types.py +88 -0
  46. minion_code/utils/__init__.py +44 -0
  47. minion_code/utils/mcp_loader.py +211 -0
  48. minion_code/utils/todo_file_utils.py +110 -0
  49. minion_code/utils/todo_storage.py +149 -0
  50. minion_code-0.1.0.dist-info/METADATA +350 -0
  51. minion_code-0.1.0.dist-info/RECORD +59 -0
  52. minion_code-0.1.0.dist-info/WHEEL +5 -0
  53. minion_code-0.1.0.dist-info/entry_points.txt +4 -0
  54. minion_code-0.1.0.dist-info/licenses/LICENSE +661 -0
  55. minion_code-0.1.0.dist-info/top_level.txt +3 -0
  56. tests/__init__.py +1 -0
  57. tests/test_basic.py +20 -0
  58. tests/test_readonly_tools.py +102 -0
  59. tests/test_tools.py +83 -0
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env python3
2
+ """Example usage of the FileFreshnessService with event system."""
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+
8
+ # Add parent directory to path for imports
9
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10
+
11
+ from minion_code.services import (
12
+ file_freshness_service,
13
+ add_event_listener,
14
+ emit_event,
15
+ record_file_read,
16
+ record_file_edit,
17
+ check_file_freshness,
18
+ )
19
+
20
+
21
+ def main():
22
+ """Demonstrate FileFreshnessService usage."""
23
+
24
+ # Create a test file
25
+ test_file = "test_file.txt"
26
+ with open(test_file, "w") as f:
27
+ f.write("Initial content")
28
+
29
+ print("=== File Freshness Service Demo ===\n")
30
+
31
+ # Set up event listeners to monitor what's happening
32
+ def on_file_read(context):
33
+ print(f"๐Ÿ“– File read event: {context.data['file_path']}")
34
+
35
+ def on_file_edited(context):
36
+ print(f"โœ๏ธ File edited event: {context.data['file_path']}")
37
+
38
+ def on_file_conflict(context):
39
+ print(f"โš ๏ธ File conflict detected: {context.data['file_path']}")
40
+
41
+ add_event_listener('file:read', on_file_read)
42
+ add_event_listener('file:edited', on_file_edited)
43
+ add_event_listener('file:conflict', on_file_conflict)
44
+
45
+ # 1. Record initial file read
46
+ print("1. Recording initial file read...")
47
+ record_file_read(test_file)
48
+
49
+ # 2. Check freshness (should be fresh)
50
+ print("\n2. Checking file freshness...")
51
+ result = check_file_freshness(test_file)
52
+ print(f" Is fresh: {result.is_fresh}, Conflict: {result.conflict}")
53
+
54
+ # 3. Simulate external modification
55
+ print("\n3. Simulating external file modification...")
56
+ time.sleep(0.1) # Small delay to ensure different timestamp
57
+ with open(test_file, "w") as f:
58
+ f.write("Modified content externally")
59
+
60
+ # 4. Check freshness again (should detect conflict)
61
+ print("\n4. Checking freshness after external modification...")
62
+ result = check_file_freshness(test_file)
63
+ print(f" Is fresh: {result.is_fresh}, Conflict: {result.conflict}")
64
+
65
+ # 5. Record agent edit (should clear conflict)
66
+ print("\n5. Recording agent edit...")
67
+ record_file_edit(test_file, "Agent modified content")
68
+
69
+ # 6. Check freshness after agent edit
70
+ print("\n6. Checking freshness after agent edit...")
71
+ result = check_file_freshness(test_file)
72
+ print(f" Is fresh: {result.is_fresh}, Conflict: {result.conflict}")
73
+
74
+ # 7. Show session files and conflicts
75
+ print("\n7. Session summary:")
76
+ session_files = file_freshness_service.get_session_files()
77
+ conflicted_files = file_freshness_service.get_conflicted_files()
78
+ important_files = file_freshness_service.get_important_files()
79
+
80
+ print(f" Session files: {session_files}")
81
+ print(f" Conflicted files: {conflicted_files}")
82
+ print(f" Important files: {important_files}")
83
+
84
+ # 8. Test session reset
85
+ print("\n8. Resetting session...")
86
+ emit_event('session:startup', {'context': {}})
87
+
88
+ session_files_after = file_freshness_service.get_session_files()
89
+ print(f" Session files after reset: {session_files_after}")
90
+
91
+ # Cleanup
92
+ os.remove(test_file)
93
+ print("\nโœ… Demo completed successfully!")
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """Example usage of file watching functionality."""
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+ import threading
8
+
9
+ # Add parent directory to path for imports
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
11
+
12
+ from minion_code.services import (
13
+ start_watching_todo_file,
14
+ stop_watching_todo_file,
15
+ add_event_listener,
16
+ record_file_read,
17
+ file_freshness_service,
18
+ )
19
+
20
+
21
+ def main():
22
+ """Demonstrate file watching functionality."""
23
+
24
+ print("=== File Watching Demo ===\n")
25
+
26
+ # Set up event listeners
27
+ def on_todo_file_changed(context):
28
+ data = context.data
29
+ print(f"๐Ÿ”” Todo file changed for agent {data['agent_id']}")
30
+ print(f" File: {data['file_path']}")
31
+ print(f" Reminder: {data['reminder']}")
32
+ print()
33
+
34
+ add_event_listener('todo:file_changed', on_todo_file_changed)
35
+
36
+ # Create test directory and file
37
+ test_dir = "test_todos"
38
+ os.makedirs(test_dir, exist_ok=True)
39
+
40
+ test_file = os.path.join(test_dir, "agent1.json")
41
+ agent_id = "agent1"
42
+
43
+ # Create initial todo file
44
+ with open(test_file, "w") as f:
45
+ f.write('{"todos": [{"id": 1, "content": "Initial task", "status": "pending"}]}')
46
+
47
+ print(f"1. Created test todo file: {test_file}")
48
+
49
+ # Start watching the file
50
+ print(f"2. Starting to watch todo file for agent: {agent_id}")
51
+ start_watching_todo_file(agent_id, test_file)
52
+
53
+ # Record initial read
54
+ record_file_read(test_file)
55
+ print("3. Recorded initial file read")
56
+
57
+ # Check if we're watching
58
+ watched_files = file_freshness_service.get_watched_files()
59
+ print(f"4. Currently watching files: {watched_files}")
60
+
61
+ # Wait a bit for watcher to initialize
62
+ time.sleep(1)
63
+
64
+ # Simulate external modification
65
+ print("\n5. Simulating external file modification...")
66
+
67
+ def modify_file():
68
+ time.sleep(0.5) # Small delay
69
+ with open(test_file, "w") as f:
70
+ f.write('{"todos": [{"id": 1, "content": "Modified task", "status": "completed"}]}')
71
+ print(" โœ๏ธ File modified externally")
72
+
73
+ # Run modification in separate thread to avoid blocking
74
+ modifier_thread = threading.Thread(target=modify_file)
75
+ modifier_thread.start()
76
+
77
+ # Wait for modification and watcher to detect it
78
+ modifier_thread.join()
79
+ time.sleep(2) # Give watcher time to detect change
80
+
81
+ # Modify file again
82
+ print("6. Another external modification...")
83
+ time.sleep(0.5)
84
+ with open(test_file, "w") as f:
85
+ f.write('{"todos": [{"id": 2, "content": "Another task", "status": "pending"}]}')
86
+
87
+ # Wait for detection
88
+ time.sleep(2)
89
+
90
+ # Stop watching
91
+ print("7. Stopping file watcher...")
92
+ stop_watching_todo_file(agent_id)
93
+
94
+ # Verify we're no longer watching
95
+ watched_files_after = file_freshness_service.get_watched_files()
96
+ print(f"8. Files being watched after stop: {watched_files_after}")
97
+
98
+ # Cleanup
99
+ try:
100
+ os.remove(test_file)
101
+ os.rmdir(test_dir)
102
+ print("\n๐Ÿงน Cleaned up test files")
103
+ except Exception as e:
104
+ print(f"โš ๏ธ Cleanup warning: {e}")
105
+
106
+ print("\nโœ… File watching demo completed!")
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,5 @@
1
+ from minion_code.cli import app
2
+ def run():
3
+ app()
4
+ if __name__ == '__main__':
5
+ app()
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Demo showing MessageResponse with children usage
4
+ Equivalent to React's <MessageResponse><Message /></MessageResponse> pattern
5
+ """
6
+
7
+ import asyncio
8
+ import time
9
+ from textual.app import App, ComposeResult
10
+ from textual.containers import Container, Vertical, ScrollableContainer
11
+ from textual.widgets import Header, Footer, Button, Static
12
+
13
+ # Import the components
14
+ import sys
15
+ import os
16
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
17
+
18
+ from minion_code.components import Message, MessageResponse
19
+ from minion_code.types import Message as MessageType, MessageContent, MessageType as MsgType
20
+
21
+
22
+ class MessageResponseChildrenDemo(App):
23
+ """Demo showing MessageResponse children usage like React"""
24
+
25
+ CSS = """
26
+ Screen {
27
+ background: $surface;
28
+ }
29
+
30
+ .demo-container {
31
+ padding: 1;
32
+ height: 100%;
33
+ }
34
+
35
+ .messages-container {
36
+ height: 1fr;
37
+ border: solid $primary;
38
+ padding: 1;
39
+ margin-bottom: 1;
40
+ }
41
+
42
+ .controls {
43
+ height: auto;
44
+ dock: bottom;
45
+ padding: 1;
46
+ background: $surface-lighten-1;
47
+ }
48
+ """
49
+
50
+ def compose(self) -> ComposeResult:
51
+ """Compose the demo interface"""
52
+ yield Header()
53
+
54
+ with Container(classes="demo-container"):
55
+ with ScrollableContainer(classes="messages-container", id="messages"):
56
+ # Show examples of different usage patterns
57
+ yield from self._create_usage_examples()
58
+
59
+ # with Container(classes="controls"):
60
+ # yield Button("Add Tool Progress", id="add_tool_progress", variant="primary")
61
+ # yield Button("Add Nested Response", id="add_nested", variant="success")
62
+ # yield Button("Add Multiple Children", id="add_multiple", variant="warning")
63
+ # yield Button("Add Dynamic Mount", id="add_dynamic", variant="default")
64
+ # yield Button("Clear", id="clear", variant="error")
65
+
66
+ yield Footer()
67
+ self.log(self.tree)
68
+
69
+ def _create_usage_examples(self):
70
+ """Create examples showing different MessageResponse usage patterns"""
71
+ child_msg = MessageType(
72
+ type=MsgType.ASSISTANT,
73
+ message=MessageContent("This message is wrapped in MessageResponse"),
74
+ timestamp=time.time()
75
+ )
76
+ yield Message(child_msg, classes="example-header")
77
+ yield Static("\n2. Single child message (React-like):", classes="example-header")
78
+
79
+ # This is equivalent to: <MessageResponse><Message /></MessageResponse>
80
+
81
+ yield MessageResponse(children=[Message(child_msg, classes="example-header")])
82
+
83
+ # Example 3: Tool execution progress
84
+ yield Static("\n3. Tool execution progress:", classes="example-header")
85
+ tool_msg = MessageType(
86
+ type=MsgType.TOOL_USE,
87
+ message=MessageContent([{
88
+ "type": "tool_use",
89
+ "id": "tool_demo",
90
+ "name": "file_editor",
91
+ "input": {"path": "demo.py", "content": "print('Hello')"}
92
+ }]),
93
+ timestamp=time.time()
94
+ )
95
+ yield MessageResponse(children=[Message(tool_msg, verbose=True)])
96
+
97
+ # Example 4: Multiple children (using initialization)
98
+ yield Static("\n4. Multiple children:", classes="example-header")
99
+
100
+ # Add multiple children to demonstrate flexibility
101
+ status_msg = MessageType(
102
+ type=MsgType.ASSISTANT,
103
+ message=MessageContent("Step 1: Analyzing code..."),
104
+ timestamp=time.time()
105
+ )
106
+ progress_msg = MessageType(
107
+ type=MsgType.ASSISTANT,
108
+ message=MessageContent("Step 2: Applying changes..."),
109
+ timestamp=time.time()
110
+ )
111
+
112
+ # Create with children during initialization
113
+ yield MessageResponse(children=[
114
+ Message(status_msg),
115
+ Message(progress_msg)
116
+ ])
117
+
118
+ def on_button_pressed(self, event: Button.Pressed) -> None:
119
+ """Handle button presses"""
120
+ messages_container = self.query_one("#messages", ScrollableContainer)
121
+
122
+ if event.button.id == "add_tool_progress":
123
+ self._add_tool_progress(messages_container)
124
+ elif event.button.id == "add_nested":
125
+ self._add_nested_response(messages_container)
126
+ elif event.button.id == "add_multiple":
127
+ self._add_multiple_children(messages_container)
128
+ elif event.button.id == "add_dynamic":
129
+ self._add_dynamic_mount(messages_container)
130
+ elif event.button.id == "clear":
131
+ self._clear_messages(messages_container)
132
+
133
+ def _add_tool_progress(self, container):
134
+ """Add tool execution progress using MessageResponse + Message"""
135
+ # Simulate tool execution progress
136
+ tool_msg = MessageType(
137
+ type=MsgType.ASSISTANT,
138
+ message=MessageContent("๐Ÿ”ง Executing file operation..."),
139
+ timestamp=time.time()
140
+ )
141
+
142
+ # Create MessageResponse with child during initialization
143
+ response = MessageResponse(children=[Message(tool_msg)])
144
+
145
+ container.mount(response)
146
+ container.scroll_end()
147
+
148
+ def _add_nested_response(self, container):
149
+ """Add nested MessageResponse (response within response)"""
150
+ # Create outer message
151
+ outer_msg = MessageType(
152
+ type=MsgType.ASSISTANT,
153
+ message=MessageContent("Starting complex operation..."),
154
+ timestamp=time.time()
155
+ )
156
+
157
+ # Create inner message for sub-operation
158
+ inner_msg = MessageType(
159
+ type=MsgType.ASSISTANT,
160
+ message=MessageContent(" โ””โ”€ Sub-operation in progress..."),
161
+ timestamp=time.time()
162
+ )
163
+
164
+ # Create nested structure with initialization
165
+ inner_response = MessageResponse(children=[Message(inner_msg)])
166
+ outer_response = MessageResponse(children=[
167
+ Message(outer_msg),
168
+ inner_response
169
+ ])
170
+
171
+ container.mount(outer_response)
172
+ container.scroll_end()
173
+
174
+ def _add_multiple_children(self, container):
175
+ """Add MessageResponse with multiple child messages"""
176
+ # Create multiple related messages
177
+ messages = [
178
+ MessageType(
179
+ type=MsgType.ASSISTANT,
180
+ message=MessageContent(f"Processing step {i+1}/3..."),
181
+ timestamp=time.time()
182
+ ) for i in range(3)
183
+ ]
184
+
185
+ # Create MessageResponse with multiple children during initialization
186
+ message_widgets = [Message(msg) for msg in messages]
187
+ response = MessageResponse(children=message_widgets)
188
+
189
+ container.mount(response)
190
+ container.scroll_end()
191
+
192
+ def _add_dynamic_mount(self, container):
193
+ """Add MessageResponse and then dynamically mount children after it's mounted"""
194
+ # Create empty MessageResponse first
195
+ response = MessageResponse(content="Dynamic mounting example:")
196
+
197
+ # Mount it to the container
198
+ container.mount(response)
199
+
200
+ # Use call_after_refresh to mount children after the component is fully mounted
201
+ def mount_children_after():
202
+ msg = MessageType(
203
+ type=MsgType.ASSISTANT,
204
+ message=MessageContent("This was added dynamically after mounting!"),
205
+ timestamp=time.time()
206
+ )
207
+ response.mount_child(Message(msg))
208
+
209
+ # Schedule the dynamic mounting
210
+ self.call_after_refresh(mount_children_after)
211
+ container.scroll_end()
212
+
213
+ def _clear_messages(self, container):
214
+ """Clear all messages except headers"""
215
+ for child in list(container.children)[2:]: # Keep title and subtitle
216
+ child.remove()
217
+
218
+
219
+ def main():
220
+ """Run the demo application"""
221
+ app = MessageResponseChildrenDemo()
222
+ app.run()
223
+
224
+
225
+ if __name__ == "__main__":
226
+ main()
@@ -0,0 +1,4 @@
1
+ from rich.console import Console
2
+
3
+ console = Console()
4
+ console.prompt("Enter your name: ") # This doesn't exist in rich
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ """Simple file watching example."""
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+
8
+ # Add parent directory to path for imports
9
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
10
+
11
+ from minion_code.services import (
12
+ start_watching_todo_file,
13
+ stop_watching_todo_file,
14
+ add_event_listener,
15
+ record_file_read,
16
+ )
17
+
18
+
19
+ def main():
20
+ """Simple file watching demonstration."""
21
+
22
+ # Set up event listener
23
+ def on_file_changed(context):
24
+ print(f"๐Ÿ“ File changed: {context.data['file_path']}")
25
+ print(f"๐Ÿ’ก {context.data['reminder']}")
26
+
27
+ add_event_listener('todo:file_changed', on_file_changed)
28
+
29
+ # Create test file
30
+ test_file = "simple_todo.json"
31
+ with open(test_file, "w") as f:
32
+ f.write('{"task": "initial"}')
33
+
34
+ print(f"โœ… Created {test_file}")
35
+
36
+ # Start watching
37
+ start_watching_todo_file("test_agent", test_file)
38
+ record_file_read(test_file)
39
+ print("๐Ÿ‘€ Started watching file")
40
+
41
+ # Wait and modify
42
+ time.sleep(1)
43
+ print("โœ๏ธ Modifying file...")
44
+ with open(test_file, "w") as f:
45
+ f.write('{"task": "modified"}')
46
+
47
+ # Wait for detection
48
+ time.sleep(2)
49
+
50
+ # Stop watching and cleanup
51
+ stop_watching_todo_file("test_agent")
52
+ os.remove(test_file)
53
+ print("๐Ÿงน Cleaned up")
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()