minion-code 0.1.0__py3-none-any.whl → 0.1.2__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 (115) hide show
  1. examples/cli_entrypoint.py +60 -0
  2. examples/{agent_with_todos.py → components/agent_with_todos.py} +58 -47
  3. examples/{message_response_children_demo.py → components/message_response_children_demo.py} +61 -55
  4. examples/components/messages_component.py +199 -0
  5. examples/file_freshness_example.py +22 -22
  6. examples/file_watching_example.py +32 -26
  7. examples/interruptible_tui.py +921 -3
  8. examples/repl_tui.py +129 -0
  9. examples/skills/example_usage.py +57 -0
  10. examples/start.py +173 -0
  11. minion_code/__init__.py +1 -1
  12. minion_code/acp_server/__init__.py +34 -0
  13. minion_code/acp_server/agent.py +539 -0
  14. minion_code/acp_server/hooks.py +354 -0
  15. minion_code/acp_server/main.py +194 -0
  16. minion_code/acp_server/permissions.py +142 -0
  17. minion_code/acp_server/test_client.py +104 -0
  18. minion_code/adapters/__init__.py +22 -0
  19. minion_code/adapters/output_adapter.py +207 -0
  20. minion_code/adapters/rich_adapter.py +169 -0
  21. minion_code/adapters/textual_adapter.py +254 -0
  22. minion_code/agents/__init__.py +2 -2
  23. minion_code/agents/code_agent.py +517 -104
  24. minion_code/agents/hooks.py +378 -0
  25. minion_code/cli.py +538 -429
  26. minion_code/cli_simple.py +665 -0
  27. minion_code/commands/__init__.py +136 -29
  28. minion_code/commands/clear_command.py +19 -46
  29. minion_code/commands/help_command.py +33 -49
  30. minion_code/commands/history_command.py +37 -55
  31. minion_code/commands/model_command.py +194 -0
  32. minion_code/commands/quit_command.py +9 -12
  33. minion_code/commands/resume_command.py +181 -0
  34. minion_code/commands/skill_command.py +89 -0
  35. minion_code/commands/status_command.py +48 -73
  36. minion_code/commands/tools_command.py +54 -52
  37. minion_code/commands/version_command.py +34 -69
  38. minion_code/components/ConfirmDialog.py +430 -0
  39. minion_code/components/Message.py +318 -97
  40. minion_code/components/MessageResponse.py +30 -29
  41. minion_code/components/Messages.py +351 -0
  42. minion_code/components/PromptInput.py +499 -245
  43. minion_code/components/__init__.py +24 -17
  44. minion_code/const.py +7 -0
  45. minion_code/screens/REPL.py +1453 -469
  46. minion_code/screens/__init__.py +1 -1
  47. minion_code/services/__init__.py +20 -20
  48. minion_code/services/event_system.py +19 -14
  49. minion_code/services/file_freshness_service.py +223 -170
  50. minion_code/skills/__init__.py +25 -0
  51. minion_code/skills/skill.py +128 -0
  52. minion_code/skills/skill_loader.py +198 -0
  53. minion_code/skills/skill_registry.py +177 -0
  54. minion_code/subagents/__init__.py +31 -0
  55. minion_code/subagents/builtin/__init__.py +30 -0
  56. minion_code/subagents/builtin/claude_code_guide.py +32 -0
  57. minion_code/subagents/builtin/explore.py +36 -0
  58. minion_code/subagents/builtin/general_purpose.py +19 -0
  59. minion_code/subagents/builtin/plan.py +61 -0
  60. minion_code/subagents/subagent.py +116 -0
  61. minion_code/subagents/subagent_loader.py +147 -0
  62. minion_code/subagents/subagent_registry.py +151 -0
  63. minion_code/tools/__init__.py +8 -2
  64. minion_code/tools/bash_tool.py +16 -3
  65. minion_code/tools/file_edit_tool.py +201 -104
  66. minion_code/tools/file_read_tool.py +183 -26
  67. minion_code/tools/file_write_tool.py +17 -3
  68. minion_code/tools/glob_tool.py +23 -2
  69. minion_code/tools/grep_tool.py +229 -21
  70. minion_code/tools/ls_tool.py +28 -3
  71. minion_code/tools/multi_edit_tool.py +89 -84
  72. minion_code/tools/python_interpreter_tool.py +9 -1
  73. minion_code/tools/skill_tool.py +210 -0
  74. minion_code/tools/task_tool.py +287 -0
  75. minion_code/tools/todo_read_tool.py +28 -24
  76. minion_code/tools/todo_write_tool.py +82 -65
  77. minion_code/{types.py → type_defs.py} +15 -2
  78. minion_code/utils/__init__.py +45 -17
  79. minion_code/utils/config.py +610 -0
  80. minion_code/utils/history.py +114 -0
  81. minion_code/utils/logs.py +53 -0
  82. minion_code/utils/mcp_loader.py +153 -55
  83. minion_code/utils/output_truncator.py +233 -0
  84. minion_code/utils/session_storage.py +369 -0
  85. minion_code/utils/todo_file_utils.py +26 -22
  86. minion_code/utils/todo_storage.py +43 -33
  87. minion_code/web/__init__.py +9 -0
  88. minion_code/web/adapters/__init__.py +5 -0
  89. minion_code/web/adapters/web_adapter.py +524 -0
  90. minion_code/web/api/__init__.py +7 -0
  91. minion_code/web/api/chat.py +277 -0
  92. minion_code/web/api/interactions.py +136 -0
  93. minion_code/web/api/sessions.py +135 -0
  94. minion_code/web/server.py +149 -0
  95. minion_code/web/services/__init__.py +5 -0
  96. minion_code/web/services/session_manager.py +420 -0
  97. minion_code-0.1.2.dist-info/METADATA +476 -0
  98. minion_code-0.1.2.dist-info/RECORD +111 -0
  99. {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/WHEEL +1 -1
  100. minion_code-0.1.2.dist-info/entry_points.txt +6 -0
  101. tests/test_adapter.py +67 -0
  102. tests/test_adapter_simple.py +79 -0
  103. tests/test_file_read_tool.py +144 -0
  104. tests/test_readonly_tools.py +0 -2
  105. tests/test_skills.py +441 -0
  106. examples/advance_tui.py +0 -508
  107. examples/rich_example.py +0 -4
  108. examples/simple_file_watching.py +0 -57
  109. examples/simple_tui.py +0 -267
  110. examples/simple_usage.py +0 -69
  111. minion_code-0.1.0.dist-info/METADATA +0 -350
  112. minion_code-0.1.0.dist-info/RECORD +0 -59
  113. minion_code-0.1.0.dist-info/entry_points.txt +0 -4
  114. {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/licenses/LICENSE +0 -0
  115. {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/top_level.txt +0 -0
examples/advance_tui.py DELETED
@@ -1,508 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Textual UI for MinionCodeAgent
5
-
6
- A modern terminal user interface using the Textual library for the MinionCodeAgent.
7
- Features include:
8
- - Real-time chat interface
9
- - Tool management
10
- - Conversation history
11
- - Task interruption support
12
- - Rich text formatting
13
- """
14
-
15
- import asyncio
16
- import sys
17
- from pathlib import Path
18
- from typing import Optional, List
19
-
20
- # Add project root to path
21
- sys.path.insert(0, str(Path(__file__).parent.parent))
22
-
23
- from textual.app import App, ComposeResult
24
- from textual.containers import Container, Horizontal, Vertical, ScrollableContainer
25
- from textual.widgets import (
26
- Header, Footer, Input, Button, Static, DataTable,
27
- TabbedContent, TabPane, Log, ProgressBar, Label
28
- )
29
- from textual.reactive import reactive
30
- from textual.message import Message
31
- from textual.binding import Binding
32
- from textual import events
33
- from textual.screen import ModalScreen
34
- from rich.text import Text
35
- from rich.markdown import Markdown
36
- from rich.console import Console
37
- from rich.panel import Panel
38
-
39
- from minion_code import MinionCodeAgent
40
- from minion_code.utils.mcp_loader import MCPToolsLoader
41
-
42
-
43
- class ToolsModal(ModalScreen):
44
- """Modal screen to display available tools."""
45
-
46
- def __init__(self, tools: List):
47
- super().__init__()
48
- self.tools = tools
49
-
50
- def compose(self) -> ComposeResult:
51
- with Container(id="tools-modal"):
52
- yield Static("🛠️ Available Tools", classes="modal-title")
53
-
54
- table = DataTable()
55
- table.add_columns("Name", "Description", "Type")
56
-
57
- for tool in self.tools:
58
- tool_type = "Read-only" if getattr(tool, 'readonly', False) else "Read-write"
59
- description = tool.description[:60] + "..." if len(tool.description) > 60 else tool.description
60
- table.add_row(tool.name, description, tool_type)
61
-
62
- yield table
63
- yield Button("Close", id="close-tools", variant="primary")
64
-
65
- def on_button_pressed(self, event: Button.Pressed) -> None:
66
- if event.button.id == "close-tools":
67
- self.dismiss()
68
-
69
-
70
- class HistoryModal(ModalScreen):
71
- """Modal screen to display conversation history."""
72
-
73
- def __init__(self, history: List):
74
- super().__init__()
75
- self.history = history
76
-
77
- def compose(self) -> ComposeResult:
78
- with Container(id="history-modal"):
79
- yield Static("📝 Conversation History", classes="modal-title")
80
-
81
- with ScrollableContainer():
82
- if not self.history:
83
- yield Static("No conversation history yet.", classes="empty-history")
84
- else:
85
- for i, entry in enumerate(self.history[-10:], 1): # Show last 10
86
- yield Static(f"👤 You (Message {len(self.history)-10+i}):", classes="user-label")
87
- yield Static(entry['user_message'][:200] + "..." if len(entry['user_message']) > 200 else entry['user_message'], classes="user-message")
88
- yield Static("🤖 Agent:", classes="agent-label")
89
- yield Static(entry['agent_response'][:200] + "..." if len(entry['agent_response']) > 200 else entry['agent_response'], classes="agent-message")
90
- yield Static("", classes="message-separator")
91
-
92
- yield Button("Close", id="close-history", variant="primary")
93
-
94
- def on_button_pressed(self, event: Button.Pressed) -> None:
95
- if event.button.id == "close-history":
96
- self.dismiss()
97
-
98
-
99
- class MinionCodeTUI(App):
100
- """Textual UI for MinionCodeAgent."""
101
-
102
- CSS = """
103
- #main-container {
104
- layout: vertical;
105
- height: 100%;
106
- }
107
-
108
- #chat-container {
109
- layout: vertical;
110
- height: 1fr;
111
- border: solid $primary;
112
- margin: 1;
113
- }
114
-
115
- #chat-log {
116
- height: 1fr;
117
- scrollbar-gutter: stable;
118
- border: solid $accent;
119
- margin: 1;
120
- }
121
-
122
- #input-container {
123
- layout: horizontal;
124
- height: 3;
125
- margin: 1;
126
- }
127
-
128
- #user-input {
129
- width: 1fr;
130
- margin-right: 1;
131
- }
132
-
133
- #send-button {
134
- width: 10;
135
- }
136
-
137
- #status-bar {
138
- height: 3;
139
- background: $surface;
140
- border: solid $primary;
141
- margin: 1;
142
- }
143
-
144
- #tools-modal, #history-modal {
145
- align: center middle;
146
- width: 80%;
147
- height: 80%;
148
- background: $surface;
149
- border: solid $primary;
150
- }
151
-
152
- .modal-title {
153
- text-align: center;
154
- text-style: bold;
155
- background: $primary;
156
- color: $text;
157
- height: 3;
158
- content-align: center middle;
159
- }
160
-
161
- .user-message {
162
- background: $primary 20%;
163
- margin: 1;
164
- padding: 1;
165
- }
166
-
167
- .agent-message {
168
- background: $success 20%;
169
- margin: 1;
170
- padding: 1;
171
- }
172
-
173
- .user-label {
174
- text-style: bold;
175
- color: $primary;
176
- margin-top: 1;
177
- }
178
-
179
- .agent-label {
180
- text-style: bold;
181
- color: $success;
182
- margin-top: 1;
183
- }
184
-
185
- .message-separator {
186
- height: 1;
187
- }
188
-
189
- .empty-history {
190
- text-align: center;
191
- text-style: italic;
192
- margin: 2;
193
- }
194
-
195
- .status-text {
196
- text-align: center;
197
- content-align: center middle;
198
- }
199
-
200
- .error-message {
201
- color: $error;
202
- text-style: bold;
203
- }
204
-
205
- .success-message {
206
- color: $success;
207
- text-style: bold;
208
- }
209
-
210
- .processing-message {
211
- color: $warning;
212
- text-style: bold;
213
- }
214
- """
215
-
216
- BINDINGS = [
217
- Binding("ctrl+q", "quit", "Quit"),
218
- Binding("ctrl+t", "show_tools", "Tools"),
219
- Binding("ctrl+h", "show_history", "History"),
220
- Binding("ctrl+c", "interrupt", "Interrupt"),
221
- Binding("ctrl+l", "clear_chat", "Clear Chat"),
222
- ]
223
-
224
- agent_status = reactive("Initializing...")
225
- processing = reactive(False)
226
-
227
- def __init__(self, mcp_config: Optional[Path] = None, verbose: bool = False):
228
- super().__init__()
229
- self.agent = None
230
- self.mcp_config = mcp_config
231
- self.verbose = verbose
232
- self.mcp_tools = []
233
- self.mcp_loader = None
234
- self.current_task = None
235
- self.interrupt_requested = False
236
-
237
- def compose(self) -> ComposeResult:
238
- """Create the UI layout."""
239
- yield Header()
240
-
241
- with Container(id="main-container"):
242
- with Container(id="chat-container"):
243
- yield Log(id="chat-log", auto_scroll=True)
244
-
245
- with Horizontal(id="input-container"):
246
- yield Input(placeholder="Type your message here...", id="user-input")
247
- yield Button("Send", id="send-button", variant="primary")
248
-
249
- yield Static(self.agent_status, id="status-bar", classes="status-text")
250
-
251
- yield Footer()
252
-
253
- async def on_mount(self) -> None:
254
- """Initialize the application."""
255
- self.title = "🤖 MinionCodeAgent TUI"
256
- self.sub_title = "AI-powered code assistant"
257
-
258
- # Start agent setup
259
- await self.setup_agent()
260
-
261
- # Focus on input
262
- self.query_one("#user-input").focus()
263
-
264
- async def setup_agent(self) -> None:
265
- """Setup the MinionCodeAgent."""
266
- try:
267
- self.agent_status = "🔧 Setting up MinionCodeAgent..."
268
-
269
- # Load MCP tools if config provided
270
- if self.mcp_config:
271
- self.agent_status = "🔌 Loading MCP tools..."
272
- try:
273
- self.mcp_loader = MCPToolsLoader(self.mcp_config)
274
- self.mcp_loader.load_config()
275
- self.mcp_tools = await self.mcp_loader.load_all_tools()
276
-
277
- if self.mcp_tools:
278
- self.log_message(f"✅ Loaded {len(self.mcp_tools)} MCP tools", "success")
279
- else:
280
- self.log_message("⚠️ No MCP tools loaded", "warning")
281
-
282
- except Exception as e:
283
- self.log_message(f"❌ Failed to load MCP tools: {e}", "error")
284
-
285
- # Create agent
286
- self.agent_status = "🤖 Creating agent..."
287
- self.agent = await MinionCodeAgent.create(
288
- name="TUI Code Assistant",
289
- llm="sonnet",
290
- additional_tools=self.mcp_tools if self.mcp_tools else None
291
- )
292
-
293
- # Update status
294
- total_tools = len(self.agent.tools)
295
- mcp_count = len(self.mcp_tools)
296
- builtin_count = total_tools - mcp_count
297
-
298
- self.agent_status = f"✅ Ready! {total_tools} tools available"
299
-
300
- # Log welcome message
301
- welcome_msg = f"🚀 MinionCodeAgent TUI Ready!\n"
302
- welcome_msg += f"🛠️ Total tools: {total_tools}"
303
- if mcp_count > 0:
304
- welcome_msg += f" (Built-in: {builtin_count}, MCP: {mcp_count})"
305
- welcome_msg += f"\n💡 Type your message and press Enter or click Send"
306
- welcome_msg += f"\n⚠️ Use Ctrl+C to interrupt tasks, Ctrl+Q to quit"
307
-
308
- self.log_message(welcome_msg, "success")
309
-
310
- except Exception as e:
311
- self.agent_status = f"❌ Setup failed: {e}"
312
- self.log_message(f"❌ Failed to setup agent: {e}", "error")
313
-
314
- def log_message(self, message: str, msg_type: str = "info") -> None:
315
- """Add a message to the chat log."""
316
- chat_log = self.query_one("#chat-log", Log)
317
-
318
- # Format messages with simple text prefixes since Log only supports plain text
319
- if msg_type == "error":
320
- chat_log.write(f"❌ {message}")
321
- elif msg_type == "success":
322
- chat_log.write(f"✅ {message}")
323
- elif msg_type == "warning":
324
- chat_log.write(f"⚠️ {message}")
325
- elif msg_type == "user":
326
- chat_log.write(f"� You: {message}")
327
- elif msg_type == "agent":
328
- # Handle markdown in agent responses
329
- if "```" in message:
330
- chat_log.write("🤖 Agent:")
331
- chat_log.write(message)
332
- else:
333
- chat_log.write(f"🤖 Agent: {message}")
334
- else:
335
- chat_log.write(message)
336
-
337
- async def process_user_input(self, message: str) -> None:
338
- """Process user input with the agent."""
339
- if not self.agent:
340
- self.log_message("❌ Agent not ready yet", "error")
341
- return
342
-
343
- if not message.strip():
344
- return
345
-
346
- # Log user message
347
- self.log_message(message, "user")
348
-
349
- # Check for commands
350
- if message.startswith('/'):
351
- await self.process_command(message)
352
- return
353
-
354
- # Process with agent
355
- try:
356
- self.processing = True
357
- self.agent_status = "🤖 Processing... (Ctrl+C to interrupt)"
358
- self.interrupt_requested = False
359
-
360
- # Create processing task
361
- async def processing_task():
362
- response = await self.agent.run_async(message)
363
- return response
364
-
365
- self.current_task = asyncio.create_task(processing_task())
366
-
367
- # Monitor for interruption
368
- while not self.current_task.done():
369
- if self.interrupt_requested:
370
- self.current_task.cancel()
371
- try:
372
- await self.current_task
373
- except asyncio.CancelledError:
374
- pass
375
- self.log_message("⚠️ Task interrupted by user", "warning")
376
- return
377
-
378
- await asyncio.sleep(0.1)
379
-
380
- # Get result
381
- response = await self.current_task
382
- self.log_message(response.answer, "agent")
383
-
384
- except asyncio.CancelledError:
385
- self.log_message("⚠️ Task was cancelled", "warning")
386
- except Exception as e:
387
- self.log_message(f"❌ Error: {e}", "error")
388
- finally:
389
- self.processing = False
390
- self.current_task = None
391
- total_tools = len(self.agent.tools) if self.agent else 0
392
- self.agent_status = f"✅ Ready! {total_tools} tools available"
393
-
394
- async def process_command(self, command: str) -> None:
395
- """Process a command."""
396
- command = command[1:].lower().strip() # Remove leading /
397
-
398
- if command == "help":
399
- help_msg = """📚 Available Commands:
400
- /help - Show this help
401
- /tools - Show available tools
402
- /history - Show conversation history
403
- /clear - Clear chat log
404
- /quit - Exit application
405
-
406
- 🔧 Keyboard Shortcuts:
407
- Ctrl+T - Show tools
408
- Ctrl+H - Show history
409
- Ctrl+L - Clear chat
410
- Ctrl+C - Interrupt current task
411
- Ctrl+Q - Quit application"""
412
- self.log_message(help_msg, "info")
413
-
414
- elif command == "tools":
415
- await self.action_show_tools()
416
-
417
- elif command == "history":
418
- await self.action_show_history()
419
-
420
- elif command == "clear":
421
- await self.action_clear_chat()
422
-
423
- elif command in ["quit", "exit", "q"]:
424
- await self.action_quit()
425
-
426
- else:
427
- self.log_message(f"❌ Unknown command: /{command}. Use /help for available commands.", "error")
428
-
429
- async def on_input_submitted(self, event: Input.Submitted) -> None:
430
- """Handle input submission."""
431
- if event.input.id == "user-input":
432
- message = event.value
433
- event.input.clear()
434
- asyncio.create_task(self.process_user_input(message))
435
-
436
- async def on_button_pressed(self, event: Button.Pressed) -> None:
437
- """Handle button presses."""
438
- if event.button.id == "send-button":
439
- user_input = self.query_one("#user-input", Input)
440
- message = user_input.value
441
- user_input.clear()
442
- asyncio.create_task(self.process_user_input(message))
443
-
444
- async def action_show_tools(self) -> None:
445
- """Show available tools."""
446
- if not self.agent:
447
- self.log_message("❌ Agent not ready yet", "error")
448
- return
449
-
450
- await self.push_screen(ToolsModal(self.agent.tools))
451
-
452
- async def action_show_history(self) -> None:
453
- """Show conversation history."""
454
- if not self.agent:
455
- self.log_message("❌ Agent not ready yet", "error")
456
- return
457
-
458
- history = self.agent.get_conversation_history()
459
- await self.push_screen(HistoryModal(history))
460
-
461
- async def action_clear_chat(self) -> None:
462
- """Clear the chat log."""
463
- chat_log = self.query_one("#chat-log", Log)
464
- chat_log.clear()
465
- self.log_message("🧹 Chat cleared", "info")
466
-
467
- async def action_interrupt(self) -> None:
468
- """Interrupt current task."""
469
- if self.current_task and not self.current_task.done():
470
- self.interrupt_requested = True
471
- self.log_message("⚠️ Interruption requested...", "warning")
472
- else:
473
- self.log_message("ℹ️ No task to interrupt", "info")
474
-
475
- async def action_quit(self) -> None:
476
- """Quit the application."""
477
- if self.mcp_loader:
478
- try:
479
- await self.mcp_loader.close()
480
- except Exception:
481
- pass
482
-
483
- self.exit()
484
-
485
-
486
- def main():
487
- """Main entry point."""
488
- import argparse
489
-
490
- parser = argparse.ArgumentParser(description="MinionCodeAgent Textual UI")
491
- parser.add_argument("--config", "-c", type=str, help="Path to MCP configuration file")
492
- parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
493
-
494
- args = parser.parse_args()
495
-
496
- mcp_config = None
497
- if args.config:
498
- mcp_config = Path(args.config)
499
- if not mcp_config.exists():
500
- print(f"❌ MCP config file does not exist: {args.config}")
501
- sys.exit(1)
502
-
503
- app = MinionCodeTUI(mcp_config=mcp_config, verbose=args.verbose)
504
- app.run()
505
-
506
-
507
- if __name__ == "__main__":
508
- main()
examples/rich_example.py DELETED
@@ -1,4 +0,0 @@
1
- from rich.console import Console
2
-
3
- console = Console()
4
- console.prompt("Enter your name: ") # This doesn't exist in rich
@@ -1,57 +0,0 @@
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()