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.
- 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.2.dist-info/METADATA +476 -0
- minion_code-0.1.2.dist-info/RECORD +111 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/WHEEL +1 -1
- minion_code-0.1.2.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.2.dist-info}/licenses/LICENSE +0 -0
- {minion_code-0.1.0.dist-info → minion_code-0.1.2.dist-info}/top_level.txt +0 -0
minion_code/cli.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
CLI interface for MinionCodeAgent
|
|
4
|
+
Modern CLI interface for MinionCodeAgent with Textual TUI support
|
|
5
5
|
|
|
6
|
-
This CLI provides
|
|
6
|
+
This CLI provides both console and TUI interfaces:
|
|
7
|
+
- Default: Modern REPL TUI interface
|
|
8
|
+
- Console: Traditional console interface (--console flag)
|
|
7
9
|
"""
|
|
8
10
|
|
|
9
11
|
import asyncio
|
|
@@ -14,489 +16,596 @@ from typing import Optional
|
|
|
14
16
|
|
|
15
17
|
import typer
|
|
16
18
|
from rich.console import Console
|
|
17
|
-
from rich.panel import Panel
|
|
18
|
-
from rich.text import Text
|
|
19
|
-
from rich.markdown import Markdown
|
|
20
|
-
from rich.table import Table
|
|
21
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
22
|
-
from rich.prompt import Prompt
|
|
23
19
|
|
|
24
20
|
# Add project root to path
|
|
25
21
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
26
22
|
|
|
27
|
-
from minion_code import MinionCodeAgent
|
|
28
|
-
from minion_code.commands import command_registry
|
|
29
|
-
from minion_code.utils.mcp_loader import MCPToolsLoader
|
|
30
|
-
|
|
31
23
|
app = typer.Typer(
|
|
32
24
|
name="minion-code",
|
|
33
|
-
help="🤖 MinionCodeAgent CLI -
|
|
25
|
+
help="🤖 MinionCodeAgent CLI - Modern AI-powered code assistant",
|
|
34
26
|
add_completion=False,
|
|
35
|
-
rich_markup_mode="rich"
|
|
27
|
+
rich_markup_mode="rich",
|
|
36
28
|
)
|
|
37
29
|
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
except Exception as e:
|
|
84
|
-
self.console.print(f"❌ Failed to load MCP tools: {e}")
|
|
85
|
-
if self.verbose:
|
|
86
|
-
import traceback
|
|
87
|
-
self.console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
88
|
-
|
|
89
|
-
agent_task = progress.add_task("🔧 Setting up MinionCodeAgent...", total=None)
|
|
90
|
-
|
|
91
|
-
self.agent = await MinionCodeAgent.create(
|
|
92
|
-
name="CLI Code Assistant",
|
|
93
|
-
llm="sonnet", # 使用更稳定的模型配置
|
|
94
|
-
additional_tools=self.mcp_tools if self.mcp_tools else None
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
progress.update(agent_task, completed=True)
|
|
98
|
-
|
|
99
|
-
# Show setup summary
|
|
100
|
-
total_tools = len(self.agent.tools)
|
|
101
|
-
mcp_count = len(self.mcp_tools)
|
|
102
|
-
builtin_count = total_tools - mcp_count
|
|
103
|
-
|
|
104
|
-
summary_text = f"✅ Agent ready with [bold green]{total_tools}[/bold green] tools!"
|
|
105
|
-
if mcp_count > 0:
|
|
106
|
-
summary_text += f"\n🔌 MCP tools: [bold cyan]{mcp_count}[/bold cyan]"
|
|
107
|
-
summary_text += f"\n🛠️ Built-in tools: [bold blue]{builtin_count}[/bold blue]"
|
|
108
|
-
summary_text += f"\n⚠️ [bold yellow]Press Ctrl+C during processing to interrupt tasks[/bold yellow]"
|
|
109
|
-
|
|
110
|
-
success_panel = Panel(
|
|
111
|
-
summary_text,
|
|
112
|
-
title="[bold green]Setup Complete[/bold green]",
|
|
113
|
-
border_style="green"
|
|
31
|
+
def run_console_cli(
|
|
32
|
+
verbose: bool = False,
|
|
33
|
+
mcp_config: Optional[Path] = None,
|
|
34
|
+
resume_session_id: Optional[str] = None,
|
|
35
|
+
continue_last: bool = False,
|
|
36
|
+
initial_prompt: Optional[str] = None,
|
|
37
|
+
print_output: bool = False,
|
|
38
|
+
model: Optional[str] = None,
|
|
39
|
+
):
|
|
40
|
+
"""Run the traditional console CLI interface"""
|
|
41
|
+
from minion_code.cli_simple import InterruptibleCLI
|
|
42
|
+
|
|
43
|
+
cli = InterruptibleCLI(
|
|
44
|
+
verbose=verbose,
|
|
45
|
+
mcp_config=mcp_config,
|
|
46
|
+
resume_session_id=resume_session_id,
|
|
47
|
+
continue_last=continue_last,
|
|
48
|
+
initial_prompt=initial_prompt,
|
|
49
|
+
print_output=print_output,
|
|
50
|
+
model=model,
|
|
51
|
+
)
|
|
52
|
+
return asyncio.run(cli.run())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_tui_repl(
|
|
56
|
+
debug: bool = False,
|
|
57
|
+
verbose: bool = False,
|
|
58
|
+
initial_prompt: Optional[str] = None,
|
|
59
|
+
dir: Optional[str] = None,
|
|
60
|
+
resume_session_id: Optional[str] = None,
|
|
61
|
+
continue_last: bool = False,
|
|
62
|
+
model: Optional[str] = None,
|
|
63
|
+
):
|
|
64
|
+
"""Run the modern TUI REPL interface"""
|
|
65
|
+
try:
|
|
66
|
+
from minion_code.screens.REPL import run
|
|
67
|
+
|
|
68
|
+
run(
|
|
69
|
+
initial_prompt=initial_prompt,
|
|
70
|
+
debug=debug,
|
|
71
|
+
verbose=verbose,
|
|
72
|
+
resume_session_id=resume_session_id,
|
|
73
|
+
continue_last=continue_last,
|
|
74
|
+
model=model,
|
|
114
75
|
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Check if it's a command (starts with /)
|
|
195
|
-
if user_input.startswith('/'):
|
|
196
|
-
await self.process_command(user_input)
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
# Process with agent
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
console = Console()
|
|
78
|
+
console.print(f"❌ [bold red]TUI dependencies not available: {e}[/bold red]")
|
|
79
|
+
console.print(
|
|
80
|
+
"💡 [italic]Install TUI dependencies with: pip install textual rich[/italic]"
|
|
81
|
+
)
|
|
82
|
+
console.print("🔄 [italic]Falling back to console interface...[/italic]")
|
|
83
|
+
# Fallback to console CLI
|
|
84
|
+
run_console_cli(
|
|
85
|
+
verbose=verbose,
|
|
86
|
+
resume_session_id=resume_session_id,
|
|
87
|
+
continue_last=continue_last,
|
|
88
|
+
model=model,
|
|
89
|
+
)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
console = Console()
|
|
92
|
+
console.print(f"❌ [bold red]TUI error: {e}[/bold red]")
|
|
93
|
+
if verbose:
|
|
94
|
+
import traceback
|
|
95
|
+
|
|
96
|
+
console.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@app.command()
|
|
101
|
+
def main(
|
|
102
|
+
prompt_arg: Optional[str] = typer.Argument(
|
|
103
|
+
None, help="Initial prompt to send to the agent (like 'claude \"prompt\"')"
|
|
104
|
+
),
|
|
105
|
+
dir: Optional[str] = typer.Option(
|
|
106
|
+
None, "--dir", "-d", help="Change to specified directory before starting"
|
|
107
|
+
),
|
|
108
|
+
model: Optional[str] = typer.Option(
|
|
109
|
+
None,
|
|
110
|
+
"--model",
|
|
111
|
+
"-m",
|
|
112
|
+
help="LLM model to use (e.g., gpt-4o, claude-3-5-sonnet). If not specified, uses config file setting.",
|
|
113
|
+
),
|
|
114
|
+
verbose: bool = typer.Option(
|
|
115
|
+
False,
|
|
116
|
+
"--verbose",
|
|
117
|
+
"-v",
|
|
118
|
+
help="Enable verbose output with additional debugging information",
|
|
119
|
+
),
|
|
120
|
+
debug: bool = typer.Option(
|
|
121
|
+
False, "--debug", help="Enable debug mode for development"
|
|
122
|
+
),
|
|
123
|
+
prompt: Optional[str] = typer.Option(
|
|
124
|
+
None,
|
|
125
|
+
"--prompt",
|
|
126
|
+
"-p",
|
|
127
|
+
help="Initial prompt to send to the agent (alternative to positional arg)",
|
|
128
|
+
),
|
|
129
|
+
console: bool = typer.Option(
|
|
130
|
+
False, "--console", help="Use console interface instead of TUI"
|
|
131
|
+
),
|
|
132
|
+
config: Optional[str] = typer.Option(
|
|
133
|
+
None, "--config", "-c", help="Path to MCP configuration file (JSON format)"
|
|
134
|
+
),
|
|
135
|
+
continue_session: bool = typer.Option(
|
|
136
|
+
False, "--continue", help="Continue the most recent session for this project"
|
|
137
|
+
),
|
|
138
|
+
resume: Optional[str] = typer.Option(
|
|
139
|
+
None, "--resume", "-r", help="Resume a specific session by ID"
|
|
140
|
+
),
|
|
141
|
+
print_output: bool = typer.Option(
|
|
142
|
+
False,
|
|
143
|
+
"--print",
|
|
144
|
+
help="Print output and exit (non-interactive mode, console only)",
|
|
145
|
+
),
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
🤖 Start MinionCodeAgent - Modern AI-powered code assistant
|
|
149
|
+
|
|
150
|
+
By default starts the modern TUI REPL interface.
|
|
151
|
+
Use --console for traditional console interface.
|
|
152
|
+
"""
|
|
153
|
+
# Change directory if specified
|
|
154
|
+
if dir:
|
|
200
155
|
try:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
156
|
+
target_dir = Path(dir).resolve()
|
|
157
|
+
if not target_dir.exists():
|
|
158
|
+
console_obj = Console()
|
|
159
|
+
console_obj.print(
|
|
160
|
+
f"❌ [bold red]Directory does not exist: {dir}[/bold red]"
|
|
161
|
+
)
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
if not target_dir.is_dir():
|
|
164
|
+
console_obj = Console()
|
|
165
|
+
console_obj.print(
|
|
166
|
+
f"❌ [bold red]Path is not a directory: {dir}[/bold red]"
|
|
167
|
+
)
|
|
168
|
+
raise typer.Exit(1)
|
|
169
|
+
|
|
170
|
+
os.chdir(target_dir)
|
|
171
|
+
if verbose:
|
|
172
|
+
console_obj = Console()
|
|
173
|
+
console_obj.print(
|
|
174
|
+
f"📁 [bold green]Changed to directory: {target_dir}[/bold green]"
|
|
218
175
|
)
|
|
219
|
-
self.console.print(cancelled_panel)
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
# Display agent response with rich formatting
|
|
223
|
-
if "```" in response.answer:
|
|
224
|
-
agent_content = Markdown(response.answer)
|
|
225
|
-
else:
|
|
226
|
-
agent_content = response.answer
|
|
227
|
-
|
|
228
|
-
response_panel = Panel(
|
|
229
|
-
agent_content,
|
|
230
|
-
title="🤖 [bold green]Agent Response[/bold green]",
|
|
231
|
-
border_style="green"
|
|
232
|
-
)
|
|
233
|
-
self.console.print(response_panel)
|
|
234
|
-
|
|
235
|
-
if self.verbose:
|
|
236
|
-
self.console.print(f"[dim]Response length: {len(response.answer)} characters[/dim]")
|
|
237
|
-
|
|
238
|
-
except KeyboardInterrupt:
|
|
239
|
-
# Handle Ctrl+C during processing
|
|
240
|
-
self.interrupt_current_task()
|
|
241
|
-
cancelled_panel = Panel(
|
|
242
|
-
"⚠️ [bold yellow]Task interrupted by user![/bold yellow]",
|
|
243
|
-
title="[bold yellow]Interrupted[/bold yellow]",
|
|
244
|
-
border_style="yellow"
|
|
245
|
-
)
|
|
246
|
-
self.console.print(cancelled_panel)
|
|
247
176
|
except Exception as e:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
border_style="red"
|
|
177
|
+
console_obj = Console()
|
|
178
|
+
console_obj.print(
|
|
179
|
+
f"❌ [bold red]Failed to change directory: {e}[/bold red]"
|
|
252
180
|
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
# Split command and arguments
|
|
265
|
-
parts = command_input.split(' ', 1)
|
|
266
|
-
command_name = parts[0].lower()
|
|
267
|
-
args = parts[1] if len(parts) > 1 else ""
|
|
268
|
-
|
|
269
|
-
if self.verbose:
|
|
270
|
-
self.console.print(f"[dim]Executing command: {command_name} with args: {args}[/dim]")
|
|
271
|
-
|
|
272
|
-
# Get command class
|
|
273
|
-
command_class = command_registry.get_command(command_name)
|
|
274
|
-
if not command_class:
|
|
275
|
-
error_panel = Panel(
|
|
276
|
-
f"❌ [bold red]Unknown command: /{command_name}[/bold red]\n"
|
|
277
|
-
f"💡 [italic]Use '/help' to see available commands[/italic]",
|
|
278
|
-
title="[bold red]Error[/bold red]",
|
|
279
|
-
border_style="red"
|
|
280
|
-
)
|
|
281
|
-
self.console.print(error_panel)
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
# Create and execute command
|
|
285
|
-
try:
|
|
286
|
-
command_instance = command_class(self.console, self.agent)
|
|
287
|
-
|
|
288
|
-
# Special handling for quit command
|
|
289
|
-
if command_name in ["quit", "exit", "q", "bye"]:
|
|
290
|
-
command_instance._tui_instance = self
|
|
291
|
-
|
|
292
|
-
await command_instance.execute(args)
|
|
293
|
-
|
|
294
|
-
except Exception as e:
|
|
295
|
-
error_panel = Panel(
|
|
296
|
-
f"❌ [bold red]Error executing command /{command_name}: {e}[/bold red]",
|
|
297
|
-
title="[bold red]Command Error[/bold red]",
|
|
298
|
-
border_style="red"
|
|
181
|
+
raise typer.Exit(1)
|
|
182
|
+
|
|
183
|
+
# Validate MCP config if provided
|
|
184
|
+
mcp_config_path = None
|
|
185
|
+
if config:
|
|
186
|
+
mcp_config_path = Path(config).resolve()
|
|
187
|
+
if not mcp_config_path.exists():
|
|
188
|
+
console_obj = Console()
|
|
189
|
+
console_obj.print(
|
|
190
|
+
f"❌ [bold red]MCP config file does not exist: {config}[/bold red]"
|
|
299
191
|
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def show_tools(self):
|
|
307
|
-
"""Show available tools in a beautiful table."""
|
|
308
|
-
if not self.agent or not self.agent.tools:
|
|
309
|
-
self.console.print("❌ No tools available")
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
tools_table = Table(title="🛠️ Available Tools", show_header=True, header_style="bold magenta")
|
|
313
|
-
tools_table.add_column("Tool Name", style="cyan", no_wrap=True)
|
|
314
|
-
tools_table.add_column("Description", style="white")
|
|
315
|
-
tools_table.add_column("Source", style="yellow")
|
|
316
|
-
tools_table.add_column("Type", style="green")
|
|
317
|
-
|
|
318
|
-
# Separate MCP tools from built-in tools
|
|
319
|
-
mcp_tool_names = {tool.name for tool in self.mcp_tools} if self.mcp_tools else set()
|
|
320
|
-
|
|
321
|
-
for tool in self.agent.tools:
|
|
322
|
-
tool_type = "Read-only" if getattr(tool, 'readonly', False) else "Read-write"
|
|
323
|
-
source = "MCP" if tool.name in mcp_tool_names else "Built-in"
|
|
324
|
-
|
|
325
|
-
tools_table.add_row(
|
|
326
|
-
tool.name,
|
|
327
|
-
tool.description[:60] + "..." if len(tool.description) > 60 else tool.description,
|
|
328
|
-
source,
|
|
329
|
-
tool_type
|
|
192
|
+
raise typer.Exit(1)
|
|
193
|
+
if not mcp_config_path.is_file():
|
|
194
|
+
console_obj = Console()
|
|
195
|
+
console_obj.print(
|
|
196
|
+
f"❌ [bold red]MCP config path is not a file: {config}[/bold red]"
|
|
330
197
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
builtin_count = total_tools - mcp_count
|
|
338
|
-
|
|
339
|
-
summary_text = f"[dim]Total: {total_tools} tools"
|
|
340
|
-
if mcp_count > 0:
|
|
341
|
-
summary_text += f" (Built-in: {builtin_count}, MCP: {mcp_count})"
|
|
342
|
-
summary_text += "[/dim]"
|
|
343
|
-
|
|
344
|
-
self.console.print(summary_text)
|
|
345
|
-
|
|
346
|
-
def show_history(self):
|
|
347
|
-
"""Show conversation history in a beautiful format."""
|
|
348
|
-
if not self.agent:
|
|
349
|
-
return
|
|
350
|
-
|
|
351
|
-
history = self.agent.get_conversation_history()
|
|
352
|
-
if not history:
|
|
353
|
-
no_history_panel = Panel(
|
|
354
|
-
"📝 [italic]No conversation history yet.[/italic]",
|
|
355
|
-
title="[bold blue]History[/bold blue]",
|
|
356
|
-
border_style="blue"
|
|
198
|
+
raise typer.Exit(1)
|
|
199
|
+
|
|
200
|
+
if verbose:
|
|
201
|
+
console_obj = Console()
|
|
202
|
+
console_obj.print(
|
|
203
|
+
f"🔌 [bold green]Using MCP config: {mcp_config_path}[/bold green]"
|
|
357
204
|
)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
205
|
+
|
|
206
|
+
# Combine prompt sources (positional arg takes precedence)
|
|
207
|
+
initial_prompt = prompt_arg or prompt
|
|
208
|
+
|
|
209
|
+
# Choose interface based on flags
|
|
210
|
+
if console:
|
|
211
|
+
# Use console interface
|
|
212
|
+
run_console_cli(
|
|
213
|
+
verbose=verbose,
|
|
214
|
+
mcp_config=mcp_config_path,
|
|
215
|
+
resume_session_id=resume,
|
|
216
|
+
continue_last=continue_session,
|
|
217
|
+
initial_prompt=initial_prompt,
|
|
218
|
+
print_output=print_output,
|
|
219
|
+
model=model,
|
|
364
220
|
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
self.console.print(user_panel)
|
|
376
|
-
|
|
377
|
-
# Agent response
|
|
378
|
-
agent_response = entry['agent_response'][:200] + "..." if len(entry['agent_response']) > 200 else entry['agent_response']
|
|
379
|
-
agent_panel = Panel(
|
|
380
|
-
agent_response,
|
|
381
|
-
title="🤖 [bold green]Agent[/bold green]",
|
|
382
|
-
border_style="green"
|
|
383
|
-
)
|
|
384
|
-
self.console.print(agent_panel)
|
|
385
|
-
self.console.print() # Add spacing
|
|
386
|
-
|
|
387
|
-
async def run(self):
|
|
388
|
-
"""Run the CLI."""
|
|
389
|
-
# Welcome banner
|
|
390
|
-
welcome_panel = Panel(
|
|
391
|
-
"🚀 [bold blue]MinionCodeAgent CLI[/bold blue]\n"
|
|
392
|
-
"💡 [italic]Use '/help' for commands or just chat with the agent![/italic]\n"
|
|
393
|
-
"⚠️ [italic]Press Ctrl+C during processing to interrupt tasks[/italic]\n"
|
|
394
|
-
"🛑 [italic]Type '/quit' to exit[/italic]",
|
|
395
|
-
title="[bold magenta]Welcome[/bold magenta]",
|
|
396
|
-
border_style="magenta"
|
|
221
|
+
else:
|
|
222
|
+
# Use TUI interface (default)
|
|
223
|
+
run_tui_repl(
|
|
224
|
+
debug=debug,
|
|
225
|
+
verbose=verbose,
|
|
226
|
+
initial_prompt=initial_prompt,
|
|
227
|
+
dir=dir,
|
|
228
|
+
resume_session_id=resume,
|
|
229
|
+
continue_last=continue_session,
|
|
230
|
+
model=model,
|
|
397
231
|
)
|
|
398
|
-
self.console.print(welcome_panel)
|
|
399
|
-
|
|
400
|
-
await self.setup()
|
|
401
|
-
|
|
402
|
-
while self.running:
|
|
403
|
-
try:
|
|
404
|
-
# Use rich prompt for better input experience
|
|
405
|
-
user_input = Prompt.ask(
|
|
406
|
-
"\n[bold cyan]👤 You[/bold cyan]",
|
|
407
|
-
console=self.console
|
|
408
|
-
).strip()
|
|
409
|
-
|
|
410
|
-
if user_input:
|
|
411
|
-
await self.process_input(user_input)
|
|
412
|
-
|
|
413
|
-
except (EOFError, KeyboardInterrupt):
|
|
414
|
-
# Handle Ctrl+C at input prompt
|
|
415
|
-
if self.current_task and not self.current_task.done():
|
|
416
|
-
# If there's a running task, interrupt it
|
|
417
|
-
self.interrupt_current_task()
|
|
418
|
-
else:
|
|
419
|
-
# If no running task, exit
|
|
420
|
-
goodbye_panel = Panel(
|
|
421
|
-
"\n👋 [bold yellow]Goodbye![/bold yellow]",
|
|
422
|
-
title="[bold red]Exit[/bold red]",
|
|
423
|
-
border_style="red"
|
|
424
|
-
)
|
|
425
|
-
self.console.print(goodbye_panel)
|
|
426
|
-
break
|
|
427
|
-
|
|
428
|
-
# Cleanup resources
|
|
429
|
-
await self.cleanup()
|
|
430
232
|
|
|
431
233
|
|
|
432
234
|
@app.command()
|
|
433
|
-
def
|
|
235
|
+
def repl(
|
|
434
236
|
dir: Optional[str] = typer.Option(
|
|
237
|
+
None, "--dir", "-d", help="🗂️ Change to specified directory before starting"
|
|
238
|
+
),
|
|
239
|
+
model: Optional[str] = typer.Option(
|
|
435
240
|
None,
|
|
436
|
-
"--
|
|
437
|
-
"-
|
|
438
|
-
help="
|
|
241
|
+
"--model",
|
|
242
|
+
"-m",
|
|
243
|
+
help="🤖 LLM model to use (e.g., gpt-4o, claude-3-5-sonnet)",
|
|
439
244
|
),
|
|
440
245
|
verbose: bool = typer.Option(
|
|
441
246
|
False,
|
|
442
247
|
"--verbose",
|
|
443
248
|
"-v",
|
|
444
|
-
help="🔍 Enable verbose output with additional debugging information"
|
|
249
|
+
help="🔍 Enable verbose output with additional debugging information",
|
|
445
250
|
),
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
help="
|
|
251
|
+
debug: bool = typer.Option(
|
|
252
|
+
False, "--debug", help="🐛 Enable debug mode for development"
|
|
253
|
+
),
|
|
254
|
+
prompt: Optional[str] = typer.Option(
|
|
255
|
+
None, "--prompt", "-p", help="💬 Initial prompt to send to the agent"
|
|
256
|
+
),
|
|
257
|
+
):
|
|
258
|
+
"""
|
|
259
|
+
🖥️ Start the REPL (Read-Eval-Print Loop) TUI interface
|
|
260
|
+
|
|
261
|
+
A modern terminal interface with streaming responses and interactive features.
|
|
262
|
+
"""
|
|
263
|
+
# Change directory if specified
|
|
264
|
+
if dir:
|
|
265
|
+
try:
|
|
266
|
+
target_dir = Path(dir).resolve()
|
|
267
|
+
if not target_dir.exists():
|
|
268
|
+
console_obj = Console()
|
|
269
|
+
console_obj.print(
|
|
270
|
+
f"❌ [bold red]Directory does not exist: {dir}[/bold red]"
|
|
271
|
+
)
|
|
272
|
+
raise typer.Exit(1)
|
|
273
|
+
if not target_dir.is_dir():
|
|
274
|
+
console_obj = Console()
|
|
275
|
+
console_obj.print(
|
|
276
|
+
f"❌ [bold red]Path is not a directory: {dir}[/bold red]"
|
|
277
|
+
)
|
|
278
|
+
raise typer.Exit(1)
|
|
279
|
+
|
|
280
|
+
os.chdir(target_dir)
|
|
281
|
+
if verbose:
|
|
282
|
+
console_obj = Console()
|
|
283
|
+
console_obj.print(
|
|
284
|
+
f"📁 [bold green]Changed to directory: {target_dir}[/bold green]"
|
|
285
|
+
)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
console_obj = Console()
|
|
288
|
+
console_obj.print(
|
|
289
|
+
f"❌ [bold red]Failed to change directory: {e}[/bold red]"
|
|
290
|
+
)
|
|
291
|
+
raise typer.Exit(1)
|
|
292
|
+
|
|
293
|
+
# Run TUI REPL
|
|
294
|
+
run_tui_repl(
|
|
295
|
+
debug=debug, verbose=verbose, initial_prompt=prompt, dir=dir, model=model
|
|
451
296
|
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@app.command()
|
|
300
|
+
def console(
|
|
301
|
+
dir: Optional[str] = typer.Option(
|
|
302
|
+
None, "--dir", "-d", help="Change to specified directory before starting"
|
|
303
|
+
),
|
|
304
|
+
model: Optional[str] = typer.Option(
|
|
305
|
+
None, "--model", "-m", help="LLM model to use (e.g., gpt-4o, claude-3-5-sonnet)"
|
|
306
|
+
),
|
|
307
|
+
verbose: bool = typer.Option(
|
|
308
|
+
False,
|
|
309
|
+
"--verbose",
|
|
310
|
+
"-v",
|
|
311
|
+
help="Enable verbose output with additional debugging information",
|
|
312
|
+
),
|
|
313
|
+
config: Optional[str] = typer.Option(
|
|
314
|
+
None, "--config", "-c", help="Path to MCP configuration file (JSON format)"
|
|
315
|
+
),
|
|
316
|
+
continue_session: bool = typer.Option(
|
|
317
|
+
False, "--continue", help="Continue the most recent session for this project"
|
|
318
|
+
),
|
|
319
|
+
resume: Optional[str] = typer.Option(
|
|
320
|
+
None, "--resume", "-r", help="Resume a specific session by ID"
|
|
321
|
+
),
|
|
452
322
|
):
|
|
453
323
|
"""
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
324
|
+
🖥️ Start the traditional console CLI interface
|
|
325
|
+
|
|
326
|
+
Console-based interface for environments without TUI support.
|
|
457
327
|
"""
|
|
458
|
-
console = Console()
|
|
459
|
-
|
|
460
328
|
# Change directory if specified
|
|
461
329
|
if dir:
|
|
462
330
|
try:
|
|
463
331
|
target_dir = Path(dir).resolve()
|
|
464
332
|
if not target_dir.exists():
|
|
465
|
-
|
|
333
|
+
console_obj = Console()
|
|
334
|
+
console_obj.print(
|
|
335
|
+
f"❌ [bold red]Directory does not exist: {dir}[/bold red]"
|
|
336
|
+
)
|
|
466
337
|
raise typer.Exit(1)
|
|
467
338
|
if not target_dir.is_dir():
|
|
468
|
-
|
|
339
|
+
console_obj = Console()
|
|
340
|
+
console_obj.print(
|
|
341
|
+
f"❌ [bold red]Path is not a directory: {dir}[/bold red]"
|
|
342
|
+
)
|
|
469
343
|
raise typer.Exit(1)
|
|
470
|
-
|
|
344
|
+
|
|
471
345
|
os.chdir(target_dir)
|
|
472
346
|
if verbose:
|
|
473
|
-
|
|
347
|
+
console_obj = Console()
|
|
348
|
+
console_obj.print(
|
|
349
|
+
f"📁 [bold green]Changed to directory: {target_dir}[/bold green]"
|
|
350
|
+
)
|
|
474
351
|
except Exception as e:
|
|
475
|
-
|
|
352
|
+
console_obj = Console()
|
|
353
|
+
console_obj.print(
|
|
354
|
+
f"❌ [bold red]Failed to change directory: {e}[/bold red]"
|
|
355
|
+
)
|
|
476
356
|
raise typer.Exit(1)
|
|
477
|
-
|
|
357
|
+
|
|
478
358
|
# Validate MCP config if provided
|
|
479
359
|
mcp_config_path = None
|
|
480
360
|
if config:
|
|
481
361
|
mcp_config_path = Path(config).resolve()
|
|
482
362
|
if not mcp_config_path.exists():
|
|
483
|
-
|
|
363
|
+
console_obj = Console()
|
|
364
|
+
console_obj.print(
|
|
365
|
+
f"❌ [bold red]MCP config file does not exist: {config}[/bold red]"
|
|
366
|
+
)
|
|
484
367
|
raise typer.Exit(1)
|
|
485
368
|
if not mcp_config_path.is_file():
|
|
486
|
-
|
|
369
|
+
console_obj = Console()
|
|
370
|
+
console_obj.print(
|
|
371
|
+
f"❌ [bold red]MCP config path is not a file: {config}[/bold red]"
|
|
372
|
+
)
|
|
487
373
|
raise typer.Exit(1)
|
|
488
|
-
|
|
374
|
+
|
|
489
375
|
if verbose:
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
376
|
+
console_obj = Console()
|
|
377
|
+
console_obj.print(
|
|
378
|
+
f"🔌 [bold green]Using MCP config: {mcp_config_path}[/bold green]"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
# Run console CLI
|
|
382
|
+
run_console_cli(
|
|
383
|
+
verbose=verbose,
|
|
384
|
+
mcp_config=mcp_config_path,
|
|
385
|
+
resume_session_id=resume,
|
|
386
|
+
continue_last=continue_session,
|
|
387
|
+
model=model,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@app.command(name="model")
|
|
392
|
+
def model_cmd(
|
|
393
|
+
model_name: Optional[str] = typer.Argument(
|
|
394
|
+
None,
|
|
395
|
+
help="Model name to set (e.g., gpt-4o, claude-3-5-sonnet). If not provided, shows current model.",
|
|
396
|
+
),
|
|
397
|
+
clear: bool = typer.Option(
|
|
398
|
+
False, "--clear", "-c", help="Clear the saved model (use default)"
|
|
399
|
+
),
|
|
400
|
+
):
|
|
401
|
+
"""
|
|
402
|
+
🤖 Configure the default LLM model.
|
|
403
|
+
|
|
404
|
+
Set, view, or clear the default model used by minion-code.
|
|
405
|
+
The model setting is saved to ~/.minion/minion-code.json.
|
|
406
|
+
|
|
407
|
+
Examples:
|
|
408
|
+
# View current model
|
|
409
|
+
mcode model
|
|
410
|
+
|
|
411
|
+
# Set default model
|
|
412
|
+
mcode model gpt-4o
|
|
413
|
+
mcode model claude-3-5-sonnet
|
|
414
|
+
|
|
415
|
+
# Clear model (use default)
|
|
416
|
+
mcode model --clear
|
|
417
|
+
"""
|
|
418
|
+
import json
|
|
419
|
+
from pathlib import Path
|
|
420
|
+
from rich.console import Console
|
|
421
|
+
|
|
422
|
+
console_obj = Console()
|
|
423
|
+
config_dir = Path.home() / ".minion"
|
|
424
|
+
config_file = config_dir / "minion-code.json"
|
|
425
|
+
|
|
426
|
+
# Load current config
|
|
427
|
+
config = {}
|
|
428
|
+
if config_file.exists():
|
|
429
|
+
try:
|
|
430
|
+
with open(config_file, "r") as f:
|
|
431
|
+
config = json.load(f)
|
|
432
|
+
except Exception:
|
|
433
|
+
pass
|
|
434
|
+
|
|
435
|
+
if clear:
|
|
436
|
+
# Clear model setting
|
|
437
|
+
if "model" in config:
|
|
438
|
+
del config["model"]
|
|
439
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
440
|
+
with open(config_file, "w") as f:
|
|
441
|
+
json.dump(config, f, indent=2)
|
|
442
|
+
console_obj.print(
|
|
443
|
+
"✅ [green]Model setting cleared. Will use default model.[/green]"
|
|
444
|
+
)
|
|
445
|
+
else:
|
|
446
|
+
console_obj.print("ℹ️ [dim]No model setting to clear.[/dim]")
|
|
447
|
+
elif model_name:
|
|
448
|
+
# Set model
|
|
449
|
+
config["model"] = model_name
|
|
450
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
451
|
+
with open(config_file, "w") as f:
|
|
452
|
+
json.dump(config, f, indent=2)
|
|
453
|
+
console_obj.print(
|
|
454
|
+
f"✅ [green]Default model set to:[/green] [bold cyan]{model_name}[/bold cyan]"
|
|
455
|
+
)
|
|
456
|
+
console_obj.print(f"📁 [dim]Config saved to: {config_file}[/dim]")
|
|
457
|
+
else:
|
|
458
|
+
# Show current model
|
|
459
|
+
current_model = config.get("model")
|
|
460
|
+
if current_model:
|
|
461
|
+
console_obj.print(
|
|
462
|
+
f"🤖 [bold]Current default model:[/bold] [cyan]{current_model}[/cyan]"
|
|
463
|
+
)
|
|
464
|
+
else:
|
|
465
|
+
console_obj.print(
|
|
466
|
+
"🤖 [bold]No default model set[/bold] (using built-in default)"
|
|
467
|
+
)
|
|
468
|
+
console_obj.print(f"\n💡 [dim]Set with: mcode model <model-name>[/dim]")
|
|
469
|
+
console_obj.print(f"💡 [dim]Clear with: mcode model --clear[/dim]")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@app.command()
|
|
473
|
+
def serve(
|
|
474
|
+
host: str = typer.Option(
|
|
475
|
+
"0.0.0.0", "--host", "-H", help="Host to bind the server to"
|
|
476
|
+
),
|
|
477
|
+
port: int = typer.Option(8000, "--port", "-p", help="Port to listen on"),
|
|
478
|
+
reload: bool = typer.Option(
|
|
479
|
+
False, "--reload", "-r", help="Enable auto-reload for development"
|
|
480
|
+
),
|
|
481
|
+
log_level: str = typer.Option(
|
|
482
|
+
"info", "--log-level", "-l", help="Logging level (debug, info, warning, error)"
|
|
483
|
+
),
|
|
484
|
+
):
|
|
485
|
+
"""
|
|
486
|
+
🌐 Start the Web API server.
|
|
487
|
+
|
|
488
|
+
Provides HTTP/SSE API for cross-process frontend communication.
|
|
489
|
+
Similar to claude.ai, uses Server-Sent Events for streaming responses.
|
|
490
|
+
|
|
491
|
+
Examples:
|
|
492
|
+
# Start server on default port (8000)
|
|
493
|
+
mcode serve
|
|
494
|
+
|
|
495
|
+
# Start on custom port with auto-reload
|
|
496
|
+
mcode serve --port 3001 --reload
|
|
497
|
+
|
|
498
|
+
# Production mode with specific host
|
|
499
|
+
mcode serve --host 127.0.0.1 --port 8080
|
|
500
|
+
"""
|
|
501
|
+
from rich.console import Console
|
|
502
|
+
|
|
503
|
+
console_obj = Console()
|
|
504
|
+
|
|
505
|
+
console_obj.print(f"🌐 [bold green]Starting Minion Code Web API[/bold green]")
|
|
506
|
+
console_obj.print(f"📡 Server: http://{host}:{port}")
|
|
507
|
+
console_obj.print(f"📚 API Docs: http://{host}:{port}/docs")
|
|
508
|
+
console_obj.print()
|
|
509
|
+
|
|
510
|
+
from minion_code.web.server import run_server
|
|
511
|
+
|
|
512
|
+
run_server(host=host, port=port, reload=reload, log_level=log_level)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@app.command()
|
|
516
|
+
def acp(
|
|
517
|
+
directory: Optional[str] = typer.Option(
|
|
518
|
+
None, "--dir", "-d", help="Working directory for the agent"
|
|
519
|
+
),
|
|
520
|
+
model: Optional[str] = typer.Option(
|
|
521
|
+
None,
|
|
522
|
+
"--model",
|
|
523
|
+
"-m",
|
|
524
|
+
help="LLM model to use (e.g., gpt-4o, claude-3-5-sonnet). If not specified, uses config file setting.",
|
|
525
|
+
),
|
|
526
|
+
verbose: bool = typer.Option(
|
|
527
|
+
False, "--verbose", "-v", help="Enable verbose (debug) logging"
|
|
528
|
+
),
|
|
529
|
+
log_level: str = typer.Option(
|
|
530
|
+
"info", "--log-level", "-l", help="Logging level (debug, info, warning, error)"
|
|
531
|
+
),
|
|
532
|
+
dangerously_skip_permissions: bool = typer.Option(
|
|
533
|
+
False,
|
|
534
|
+
"--dangerously-skip-permissions",
|
|
535
|
+
help="Skip permission prompts for tool calls (dangerous!)",
|
|
536
|
+
),
|
|
537
|
+
):
|
|
538
|
+
"""
|
|
539
|
+
🔌 Start as ACP (Agent Client Protocol) agent.
|
|
540
|
+
|
|
541
|
+
Runs minion-code as an ACP-compatible agent over stdio.
|
|
542
|
+
This allows integration with ACP clients like Zed editor.
|
|
543
|
+
|
|
544
|
+
The agent communicates via JSON-RPC over stdin/stdout,
|
|
545
|
+
with all other output redirected to stderr.
|
|
546
|
+
|
|
547
|
+
Examples:
|
|
548
|
+
# Start ACP agent
|
|
549
|
+
mcode acp
|
|
550
|
+
|
|
551
|
+
# Start with specific working directory
|
|
552
|
+
mcode acp --dir /path/to/project
|
|
553
|
+
|
|
554
|
+
# Start with specific model
|
|
555
|
+
mcode acp --model gpt-4o
|
|
556
|
+
|
|
557
|
+
# Start with verbose logging
|
|
558
|
+
mcode acp --verbose
|
|
559
|
+
|
|
560
|
+
# Start without permission prompts (dangerous!)
|
|
561
|
+
mcode acp --dangerously-skip-permissions
|
|
562
|
+
"""
|
|
563
|
+
# Handle verbose flag
|
|
564
|
+
if verbose:
|
|
565
|
+
log_level = "debug"
|
|
566
|
+
|
|
567
|
+
from minion_code.acp_server.main import main as acp_main
|
|
568
|
+
|
|
569
|
+
acp_main(
|
|
570
|
+
log_level=log_level,
|
|
571
|
+
dangerously_skip_permissions=dangerously_skip_permissions,
|
|
572
|
+
cwd=directory,
|
|
573
|
+
model=model,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def run():
|
|
578
|
+
"""Entry point for pyproject.toml scripts."""
|
|
579
|
+
_maybe_insert_main_command()
|
|
580
|
+
app()
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _maybe_insert_main_command():
|
|
584
|
+
"""Insert 'main' command if not provided, to enable 'mcode "prompt"' usage."""
|
|
585
|
+
known_commands = {
|
|
586
|
+
"main",
|
|
587
|
+
"repl",
|
|
588
|
+
"console",
|
|
589
|
+
"serve",
|
|
590
|
+
"acp",
|
|
591
|
+
"model",
|
|
592
|
+
"--help",
|
|
593
|
+
"-h",
|
|
594
|
+
}
|
|
595
|
+
args = sys.argv[1:]
|
|
596
|
+
|
|
597
|
+
if not args:
|
|
598
|
+
# No args, insert 'main'
|
|
599
|
+
sys.argv.insert(1, "main")
|
|
600
|
+
elif args[0] in ("--help", "-h"):
|
|
601
|
+
# Help requested, don't modify
|
|
602
|
+
pass
|
|
603
|
+
elif args[0] not in known_commands:
|
|
604
|
+
# First arg is not a known command - could be a prompt or option
|
|
605
|
+
# Insert 'main' to treat it as: mcode main "prompt" or mcode main --option
|
|
606
|
+
sys.argv.insert(1, "main")
|
|
499
607
|
|
|
500
608
|
|
|
501
609
|
if __name__ == "__main__":
|
|
502
|
-
|
|
610
|
+
_maybe_insert_main_command()
|
|
611
|
+
app()
|