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.
- examples/cli_entrypoint.py +60 -0
- examples/{agent_with_todos.py → components/agent_with_todos.py} +58 -47
- examples/{message_response_children_demo.py → components/message_response_children_demo.py} +61 -55
- examples/components/messages_component.py +199 -0
- examples/file_freshness_example.py +22 -22
- examples/file_watching_example.py +32 -26
- examples/interruptible_tui.py +921 -3
- examples/repl_tui.py +129 -0
- examples/skills/example_usage.py +57 -0
- examples/start.py +173 -0
- minion_code/__init__.py +1 -1
- minion_code/acp_server/__init__.py +34 -0
- minion_code/acp_server/agent.py +539 -0
- minion_code/acp_server/hooks.py +354 -0
- minion_code/acp_server/main.py +194 -0
- minion_code/acp_server/permissions.py +142 -0
- minion_code/acp_server/test_client.py +104 -0
- minion_code/adapters/__init__.py +22 -0
- minion_code/adapters/output_adapter.py +207 -0
- minion_code/adapters/rich_adapter.py +169 -0
- minion_code/adapters/textual_adapter.py +254 -0
- minion_code/agents/__init__.py +2 -2
- minion_code/agents/code_agent.py +517 -104
- minion_code/agents/hooks.py +378 -0
- minion_code/cli.py +538 -429
- minion_code/cli_simple.py +665 -0
- minion_code/commands/__init__.py +136 -29
- minion_code/commands/clear_command.py +19 -46
- minion_code/commands/help_command.py +33 -49
- minion_code/commands/history_command.py +37 -55
- minion_code/commands/model_command.py +194 -0
- minion_code/commands/quit_command.py +9 -12
- minion_code/commands/resume_command.py +181 -0
- minion_code/commands/skill_command.py +89 -0
- minion_code/commands/status_command.py +48 -73
- minion_code/commands/tools_command.py +54 -52
- minion_code/commands/version_command.py +34 -69
- minion_code/components/ConfirmDialog.py +430 -0
- minion_code/components/Message.py +318 -97
- minion_code/components/MessageResponse.py +30 -29
- minion_code/components/Messages.py +351 -0
- minion_code/components/PromptInput.py +499 -245
- minion_code/components/__init__.py +24 -17
- minion_code/const.py +7 -0
- minion_code/screens/REPL.py +1453 -469
- minion_code/screens/__init__.py +1 -1
- minion_code/services/__init__.py +20 -20
- minion_code/services/event_system.py +19 -14
- minion_code/services/file_freshness_service.py +223 -170
- minion_code/skills/__init__.py +25 -0
- minion_code/skills/skill.py +128 -0
- minion_code/skills/skill_loader.py +198 -0
- minion_code/skills/skill_registry.py +177 -0
- minion_code/subagents/__init__.py +31 -0
- minion_code/subagents/builtin/__init__.py +30 -0
- minion_code/subagents/builtin/claude_code_guide.py +32 -0
- minion_code/subagents/builtin/explore.py +36 -0
- minion_code/subagents/builtin/general_purpose.py +19 -0
- minion_code/subagents/builtin/plan.py +61 -0
- minion_code/subagents/subagent.py +116 -0
- minion_code/subagents/subagent_loader.py +147 -0
- minion_code/subagents/subagent_registry.py +151 -0
- minion_code/tools/__init__.py +8 -2
- minion_code/tools/bash_tool.py +16 -3
- minion_code/tools/file_edit_tool.py +201 -104
- minion_code/tools/file_read_tool.py +183 -26
- minion_code/tools/file_write_tool.py +17 -3
- minion_code/tools/glob_tool.py +23 -2
- minion_code/tools/grep_tool.py +229 -21
- minion_code/tools/ls_tool.py +28 -3
- minion_code/tools/multi_edit_tool.py +89 -84
- minion_code/tools/python_interpreter_tool.py +9 -1
- minion_code/tools/skill_tool.py +210 -0
- minion_code/tools/task_tool.py +287 -0
- minion_code/tools/todo_read_tool.py +28 -24
- minion_code/tools/todo_write_tool.py +82 -65
- minion_code/{types.py → type_defs.py} +15 -2
- minion_code/utils/__init__.py +45 -17
- minion_code/utils/config.py +610 -0
- minion_code/utils/history.py +114 -0
- minion_code/utils/logs.py +53 -0
- minion_code/utils/mcp_loader.py +153 -55
- minion_code/utils/output_truncator.py +233 -0
- minion_code/utils/session_storage.py +369 -0
- minion_code/utils/todo_file_utils.py +26 -22
- minion_code/utils/todo_storage.py +43 -33
- minion_code/web/__init__.py +9 -0
- minion_code/web/adapters/__init__.py +5 -0
- minion_code/web/adapters/web_adapter.py +524 -0
- minion_code/web/api/__init__.py +7 -0
- minion_code/web/api/chat.py +277 -0
- minion_code/web/api/interactions.py +136 -0
- minion_code/web/api/sessions.py +135 -0
- minion_code/web/server.py +149 -0
- minion_code/web/services/__init__.py +5 -0
- minion_code/web/services/session_manager.py +420 -0
- minion_code-0.1.1.dist-info/METADATA +475 -0
- minion_code-0.1.1.dist-info/RECORD +111 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/WHEEL +1 -1
- minion_code-0.1.1.dist-info/entry_points.txt +6 -0
- tests/test_adapter.py +67 -0
- tests/test_adapter_simple.py +79 -0
- tests/test_file_read_tool.py +144 -0
- tests/test_readonly_tools.py +0 -2
- tests/test_skills.py +441 -0
- examples/advance_tui.py +0 -508
- examples/rich_example.py +0 -4
- examples/simple_file_watching.py +0 -57
- examples/simple_tui.py +0 -267
- examples/simple_usage.py +0 -69
- minion_code-0.1.0.dist-info/METADATA +0 -350
- minion_code-0.1.0.dist-info/RECORD +0 -59
- minion_code-0.1.0.dist-info/entry_points.txt +0 -4
- {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {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)
|