minion-code 0.1.0__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +475 -0
  98. minion_code-0.1.1.dist-info/RECORD +111 -0
  99. {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/WHEEL +1 -1
  100. minion_code-0.1.1.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.1.dist-info}/licenses/LICENSE +0 -0
  115. {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Simple ACP client for testing minion-code ACP agent.
5
+
6
+ Usage:
7
+ python -m minion_code.acp_server.test_client
8
+ python -m minion_code.acp_server.test_client "your prompt here"
9
+ """
10
+
11
+ import asyncio
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from acp import spawn_agent_process, text_block
17
+ from acp.interfaces import Client
18
+
19
+
20
+ class TestClient(Client):
21
+ """Simple client that prints all updates."""
22
+
23
+ async def request_permission(self, options, session_id, tool_call, **kwargs: Any):
24
+ """Auto-accept all permissions for testing."""
25
+ print(f"[PERMISSION] {tool_call}")
26
+ return {"outcome": {"outcome": "accepted"}}
27
+
28
+ async def session_update(self, session_id, update, **kwargs):
29
+ """Print all session updates."""
30
+ update_type = type(update).__name__
31
+
32
+ # Format based on update type
33
+ if hasattr(update, "delta"):
34
+ # AgentMessageChunk or AgentThoughtChunk
35
+ content = update.delta
36
+ if update_type == "AgentThoughtChunk":
37
+ print(f"[THOUGHT] {content}", end="", flush=True)
38
+ else:
39
+ print(f"{content}", end="", flush=True)
40
+ elif hasattr(update, "tool_call_id"):
41
+ # ToolCallStart or ToolCallProgress
42
+ # Check session_update to distinguish them
43
+ session_update_type = getattr(update, "session_update", None)
44
+ if session_update_type == "tool_call":
45
+ # ToolCallStart
46
+ print(f"\n[TOOL START] {update.title} (id={update.tool_call_id})")
47
+ # Print content if available
48
+ if hasattr(update, "content") and update.content:
49
+ for c in update.content:
50
+ if hasattr(c, "content") and hasattr(c.content, "text"):
51
+ print(f" Code:\n{c.content.text}")
52
+ elif session_update_type == "tool_call_update":
53
+ # ToolCallProgress
54
+ status = getattr(update, "status", "unknown")
55
+ print(f"\n[TOOL {status.upper()}] id={update.tool_call_id}")
56
+ # Print content if available
57
+ if hasattr(update, "content") and update.content:
58
+ for c in update.content:
59
+ if hasattr(c, "content") and hasattr(c.content, "text"):
60
+ print(f" Result: {c.content.text}")
61
+ else:
62
+ print(f"\n[UPDATE] {update_type}: {update}")
63
+
64
+
65
+ async def main(prompt: str = "Hello! What can you do?") -> None:
66
+ """Run the test client."""
67
+ print(f"Starting minion-code ACP agent...")
68
+ print(f"Prompt: {prompt}")
69
+ print("=" * 60)
70
+
71
+ # Spawn the minion-code ACP agent
72
+ async with spawn_agent_process(
73
+ TestClient(), sys.executable, "-m", "minion_code.acp_server.main"
74
+ ) as (conn, proc):
75
+ print("Agent spawned, initializing...")
76
+
77
+ # Initialize
78
+ init_response = await conn.initialize(protocol_version=1)
79
+ print(
80
+ f"Initialized: {init_response.agent_info.name} v{init_response.agent_info.version}"
81
+ )
82
+
83
+ # Create session
84
+ cwd = str(Path.cwd())
85
+ session = await conn.new_session(cwd=cwd, mcp_servers=[])
86
+ print(f"Session created: {session.session_id}")
87
+ print("=" * 60)
88
+ print()
89
+
90
+ # Send prompt
91
+ response = await conn.prompt(
92
+ session_id=session.session_id,
93
+ prompt=[text_block(prompt)],
94
+ )
95
+
96
+ print()
97
+ print("=" * 60)
98
+ print(f"Response stop_reason: {response.stop_reason}")
99
+
100
+
101
+ if __name__ == "__main__":
102
+ # Get prompt from command line or use default
103
+ prompt = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "Hello! What can you do?"
104
+ asyncio.run(main(prompt))
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Output Adapters - Abstraction layer for UI output
5
+
6
+ This module provides adapters to decouple command business logic from UI rendering.
7
+ Commands use a unified OutputAdapter interface, which can be implemented for different
8
+ UI environments (Rich CLI, Textual TUI, etc.).
9
+ """
10
+
11
+ from .output_adapter import OutputAdapter, MessageStyle, ConfirmOptions, ChoiceOptions
12
+ from .rich_adapter import RichOutputAdapter
13
+ from .textual_adapter import TextualOutputAdapter
14
+
15
+ __all__ = [
16
+ "OutputAdapter",
17
+ "MessageStyle",
18
+ "ConfirmOptions",
19
+ "ChoiceOptions",
20
+ "RichOutputAdapter",
21
+ "TextualOutputAdapter",
22
+ ]
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Output Adapter - Abstract interface for UI output
5
+
6
+ This module defines the abstract interface that all output adapters must implement.
7
+ It provides a unified API for commands to output content and request user interaction,
8
+ regardless of the underlying UI system (CLI, TUI, etc.).
9
+ """
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import List, Optional
13
+ from enum import Enum
14
+ from dataclasses import dataclass
15
+
16
+
17
+ class MessageStyle(Enum):
18
+ """Message style types"""
19
+
20
+ INFO = "info"
21
+ SUCCESS = "success"
22
+ WARNING = "warning"
23
+ ERROR = "error"
24
+
25
+
26
+ @dataclass
27
+ class ConfirmOptions:
28
+ """Options for confirmation dialog"""
29
+
30
+ message: str
31
+ title: str = "Confirm"
32
+ default: bool = False
33
+ ok_text: str = "Yes"
34
+ cancel_text: str = "No"
35
+ style: MessageStyle = MessageStyle.WARNING
36
+
37
+
38
+ @dataclass
39
+ class ChoiceOptions:
40
+ """Options for choice dialog"""
41
+
42
+ message: str
43
+ choices: List[str]
44
+ title: str = "Select"
45
+ default_index: int = 0
46
+
47
+
48
+ class OutputAdapter(ABC):
49
+ """
50
+ Abstract output adapter interface.
51
+
52
+ This interface defines methods for:
53
+ 1. Basic output (panel, table, text)
54
+ 2. User interaction (confirm, choice, input)
55
+
56
+ All methods that request user interaction are async to support
57
+ both blocking (CLI) and non-blocking (TUI) implementations.
58
+ """
59
+
60
+ @abstractmethod
61
+ def panel(self, content: str, title: str = "", border_style: str = "blue") -> None:
62
+ """
63
+ Display a panel with content.
64
+
65
+ Args:
66
+ content: Panel content text
67
+ title: Panel title
68
+ border_style: Border color/style
69
+ """
70
+ pass
71
+
72
+ @abstractmethod
73
+ def table(self, headers: List[str], rows: List[List[str]], title: str = "") -> None:
74
+ """
75
+ Display a table.
76
+
77
+ Args:
78
+ headers: Column headers
79
+ rows: Table rows
80
+ title: Table title
81
+ """
82
+ pass
83
+
84
+ @abstractmethod
85
+ def text(self, content: str, style: str = "") -> None:
86
+ """
87
+ Display plain text.
88
+
89
+ Args:
90
+ content: Text content
91
+ style: Text style/color
92
+ """
93
+ pass
94
+
95
+ def info(self, content: str) -> None:
96
+ """
97
+ Display info message.
98
+
99
+ Args:
100
+ content: Message content
101
+ """
102
+ self.text(f"[blue]ℹ {content}[/blue]")
103
+
104
+ def success(self, content: str) -> None:
105
+ """
106
+ Display success message.
107
+
108
+ Args:
109
+ content: Message content
110
+ """
111
+ self.text(f"[green]✓ {content}[/green]")
112
+
113
+ def warning(self, content: str) -> None:
114
+ """
115
+ Display warning message.
116
+
117
+ Args:
118
+ content: Message content
119
+ """
120
+ self.text(f"[yellow]⚠ {content}[/yellow]")
121
+
122
+ def error(self, content: str) -> None:
123
+ """
124
+ Display error message.
125
+
126
+ Args:
127
+ content: Message content
128
+ """
129
+ self.text(f"[red]✗ {content}[/red]")
130
+
131
+ @abstractmethod
132
+ async def confirm(
133
+ self,
134
+ message: str,
135
+ title: str = "Confirm",
136
+ default: bool = False,
137
+ ok_text: str = "Yes",
138
+ cancel_text: str = "No",
139
+ ) -> bool:
140
+ """
141
+ Display a confirmation dialog and wait for user response.
142
+
143
+ Args:
144
+ message: Confirmation message
145
+ title: Dialog title
146
+ default: Default choice
147
+ ok_text: Text for confirmation button
148
+ cancel_text: Text for cancel button
149
+
150
+ Returns:
151
+ True if user confirmed, False if cancelled
152
+ """
153
+ pass
154
+
155
+ @abstractmethod
156
+ async def choice(
157
+ self,
158
+ message: str,
159
+ choices: List[str],
160
+ title: str = "Select",
161
+ default_index: int = 0,
162
+ ) -> int:
163
+ """
164
+ Display a choice dialog and wait for user selection.
165
+
166
+ Args:
167
+ message: Choice prompt message
168
+ choices: List of choices
169
+ title: Dialog title
170
+ default_index: Default selection index
171
+
172
+ Returns:
173
+ Selected index (0-based), or -1 if cancelled
174
+ """
175
+ pass
176
+
177
+ @abstractmethod
178
+ async def input(
179
+ self,
180
+ message: str,
181
+ title: str = "Input",
182
+ default: str = "",
183
+ placeholder: str = "",
184
+ ) -> Optional[str]:
185
+ """
186
+ Display an input dialog and wait for user input.
187
+
188
+ Args:
189
+ message: Input prompt message
190
+ title: Dialog title
191
+ default: Default value
192
+ placeholder: Placeholder text
193
+
194
+ Returns:
195
+ User input string, or None if cancelled
196
+ """
197
+ pass
198
+
199
+ @abstractmethod
200
+ def print(self, *args, **kwargs) -> None:
201
+ """
202
+ Generic print method for compatibility.
203
+
204
+ This method provides a fallback for any console.print() calls
205
+ that haven't been converted to the specific adapter methods.
206
+ """
207
+ pass
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Rich Output Adapter - CLI implementation
5
+
6
+ This adapter uses Rich Console for terminal output. It provides synchronous
7
+ blocking behavior for user interactions, which is suitable for traditional
8
+ CLI applications.
9
+ """
10
+
11
+ from typing import List, Optional
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.table import Table
15
+ from rich.prompt import Confirm, Prompt
16
+
17
+ from .output_adapter import OutputAdapter
18
+
19
+
20
+ class RichOutputAdapter(OutputAdapter):
21
+ """
22
+ Rich Console adapter for CLI mode.
23
+
24
+ This adapter provides direct terminal output using Rich Console.
25
+ All user interaction methods (confirm, choice, input) block execution
26
+ until the user responds in the terminal.
27
+ """
28
+
29
+ def __init__(self, console: Optional[Console] = None):
30
+ """
31
+ Initialize Rich adapter.
32
+
33
+ Args:
34
+ console: Rich Console instance. Creates new one if None.
35
+ """
36
+ self.console = console or Console()
37
+
38
+ def panel(self, content: str, title: str = "", border_style: str = "blue") -> None:
39
+ """Display a panel using Rich Panel."""
40
+ panel = Panel(content, title=title, border_style=border_style)
41
+ self.console.print(panel)
42
+
43
+ def table(self, headers: List[str], rows: List[List[str]], title: str = "") -> None:
44
+ """Display a table using Rich Table."""
45
+ table = Table(title=title, show_header=True, header_style="bold blue")
46
+
47
+ # Add columns
48
+ for header in headers:
49
+ table.add_column(header)
50
+
51
+ # Add rows
52
+ for row in rows:
53
+ table.add_row(*row)
54
+
55
+ self.console.print(table)
56
+
57
+ def text(self, content: str, style: str = "") -> None:
58
+ """Display plain text."""
59
+ if style:
60
+ self.console.print(content, style=style)
61
+ else:
62
+ self.console.print(content)
63
+
64
+ async def confirm(
65
+ self,
66
+ message: str,
67
+ title: str = "Confirm",
68
+ default: bool = False,
69
+ ok_text: str = "Yes",
70
+ cancel_text: str = "No",
71
+ ) -> bool:
72
+ """
73
+ Display confirmation dialog (blocking).
74
+
75
+ Shows a panel with the title and message, then uses Rich Confirm
76
+ to wait for user input in the terminal.
77
+ """
78
+ # Show title panel if provided
79
+ if title and title != "Confirm":
80
+ self.panel(message, title=title, border_style="yellow")
81
+ prompt_message = f"Continue? ({ok_text}/{cancel_text})"
82
+ else:
83
+ prompt_message = message
84
+
85
+ try:
86
+ return Confirm.ask(prompt_message, console=self.console, default=default)
87
+ except KeyboardInterrupt:
88
+ return False
89
+
90
+ async def choice(
91
+ self,
92
+ message: str,
93
+ choices: List[str],
94
+ title: str = "Select",
95
+ default_index: int = 0,
96
+ ) -> int:
97
+ """
98
+ Display choice dialog (blocking).
99
+
100
+ Shows numbered choices and waits for user to input a number.
101
+ """
102
+ # Show title if provided
103
+ if title:
104
+ self.console.print(f"[bold yellow]{title}[/bold yellow]")
105
+
106
+ # Show message
107
+ if message:
108
+ self.console.print(message)
109
+ self.console.print()
110
+
111
+ # Show choices
112
+ for i, choice in enumerate(choices):
113
+ marker = "→" if i == default_index else " "
114
+ self.console.print(f" {marker} [{i+1}] {choice}")
115
+
116
+ self.console.print()
117
+
118
+ # Get user input
119
+ while True:
120
+ try:
121
+ choice_input = Prompt.ask(
122
+ "Select number (or 'c' to cancel)",
123
+ console=self.console,
124
+ default=str(default_index + 1),
125
+ )
126
+
127
+ # Check for cancel
128
+ if choice_input.lower() in ["c", "cancel", "q", "quit"]:
129
+ return -1
130
+
131
+ # Try to parse as number
132
+ index = int(choice_input) - 1
133
+ if 0 <= index < len(choices):
134
+ return index
135
+
136
+ self.console.print("[red]Invalid choice. Please try again.[/red]")
137
+
138
+ except (ValueError, KeyboardInterrupt):
139
+ return -1
140
+
141
+ async def input(
142
+ self,
143
+ message: str,
144
+ title: str = "Input",
145
+ default: str = "",
146
+ placeholder: str = "",
147
+ ) -> Optional[str]:
148
+ """
149
+ Display input dialog (blocking).
150
+
151
+ Uses Rich Prompt to get text input from user.
152
+ """
153
+ # Show title if provided
154
+ if title:
155
+ self.console.print(f"[bold blue]{title}[/bold blue]")
156
+
157
+ # Combine message with placeholder if provided
158
+ prompt_message = message
159
+ if placeholder:
160
+ prompt_message = f"{message} (e.g., {placeholder})"
161
+
162
+ try:
163
+ return Prompt.ask(prompt_message, console=self.console, default=default)
164
+ except KeyboardInterrupt:
165
+ return None
166
+
167
+ def print(self, *args, **kwargs) -> None:
168
+ """Generic print for compatibility."""
169
+ self.console.print(*args, **kwargs)