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/commands/__init__.py
CHANGED
|
@@ -5,31 +5,73 @@ Command system for MinionCode TUI
|
|
|
5
5
|
|
|
6
6
|
This module provides a command system similar to Claude Code or Gemini CLI,
|
|
7
7
|
where commands are prefixed with '/' and each command is implemented in a separate file.
|
|
8
|
+
|
|
9
|
+
Command Types:
|
|
10
|
+
- LOCAL: Direct execution, returns result immediately (e.g., /clear, /help)
|
|
11
|
+
- LOCAL_JSX: Requires UI interaction, returns a component (e.g., /config, /model)
|
|
12
|
+
- PROMPT: Replaces user input and sends to LLM (e.g., /bug, custom .md commands)
|
|
8
13
|
"""
|
|
9
14
|
|
|
10
15
|
import importlib
|
|
11
16
|
import pkgutil
|
|
12
|
-
from typing import Dict, Type, Optional
|
|
17
|
+
from typing import Dict, Type, Optional, Union
|
|
13
18
|
from abc import ABC, abstractmethod
|
|
19
|
+
from enum import Enum
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CommandType(Enum):
|
|
23
|
+
"""Types of commands based on execution flow."""
|
|
24
|
+
|
|
25
|
+
LOCAL = "local" # Direct execution, returns result
|
|
26
|
+
LOCAL_JSX = "local_jsx" # Requires UI interaction
|
|
27
|
+
PROMPT = "prompt" # Replaces user input, sends to LLM
|
|
14
28
|
|
|
15
29
|
|
|
16
30
|
class BaseCommand(ABC):
|
|
17
31
|
"""Base class for all commands."""
|
|
18
|
-
|
|
32
|
+
|
|
19
33
|
name: str = ""
|
|
20
34
|
description: str = ""
|
|
21
35
|
usage: str = ""
|
|
22
36
|
aliases: list = []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
command_type: CommandType = CommandType.LOCAL # Default to LOCAL
|
|
38
|
+
is_skill: bool = False # Whether this is a skill (affects display)
|
|
39
|
+
|
|
40
|
+
def __init__(self, output, agent=None):
|
|
41
|
+
"""
|
|
42
|
+
Initialize command.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
output: OutputAdapter instance for UI output (RichAdapter or TextualAdapter)
|
|
46
|
+
agent: Optional agent instance
|
|
47
|
+
"""
|
|
48
|
+
self.output = output
|
|
26
49
|
self.agent = agent
|
|
27
|
-
|
|
50
|
+
|
|
51
|
+
# Backward compatibility: expose console attribute for Rich adapter
|
|
52
|
+
# This allows old code using self.console to still work
|
|
53
|
+
if hasattr(output, "console"):
|
|
54
|
+
self.console = output.console
|
|
55
|
+
|
|
28
56
|
@abstractmethod
|
|
29
57
|
async def execute(self, args: str) -> None:
|
|
30
58
|
"""Execute the command with given arguments."""
|
|
31
59
|
pass
|
|
32
|
-
|
|
60
|
+
|
|
61
|
+
async def get_prompt(self, args: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Get the expanded prompt for PROMPT type commands.
|
|
64
|
+
Override this method for PROMPT type commands.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
args: Command arguments from user input
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Expanded prompt string to send to LLM
|
|
71
|
+
"""
|
|
72
|
+
# Default implementation just returns the args
|
|
73
|
+
return args
|
|
74
|
+
|
|
33
75
|
def get_help(self) -> str:
|
|
34
76
|
"""Get help text for this command."""
|
|
35
77
|
return f"**/{self.name}** - {self.description}\n\nUsage: {self.usage}"
|
|
@@ -37,54 +79,119 @@ class BaseCommand(ABC):
|
|
|
37
79
|
|
|
38
80
|
class CommandRegistry:
|
|
39
81
|
"""Registry for managing commands."""
|
|
40
|
-
|
|
82
|
+
|
|
41
83
|
def __init__(self):
|
|
42
84
|
self.commands: Dict[str, Type[BaseCommand]] = {}
|
|
85
|
+
self._skills_loaded = False
|
|
43
86
|
self._load_commands()
|
|
44
|
-
|
|
87
|
+
|
|
45
88
|
def _load_commands(self):
|
|
46
89
|
"""Dynamically load all command modules."""
|
|
47
90
|
import os
|
|
91
|
+
|
|
48
92
|
commands_dir = os.path.dirname(__file__)
|
|
49
|
-
|
|
93
|
+
|
|
50
94
|
for filename in os.listdir(commands_dir):
|
|
51
|
-
if filename.endswith(
|
|
95
|
+
if filename.endswith("_command.py") and not filename.startswith("_"):
|
|
96
|
+
# Skip skill_command.py as it's loaded dynamically
|
|
97
|
+
if filename == "skill_command.py":
|
|
98
|
+
continue
|
|
99
|
+
|
|
52
100
|
modname = filename[:-3] # Remove .py extension
|
|
53
101
|
try:
|
|
54
|
-
module = importlib.import_module(f
|
|
55
|
-
|
|
102
|
+
module = importlib.import_module(f"minion_code.commands.{modname}")
|
|
103
|
+
|
|
56
104
|
# Look for command classes in the module
|
|
57
105
|
for attr_name in dir(module):
|
|
58
106
|
attr = getattr(module, attr_name)
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
attr
|
|
62
|
-
|
|
63
|
-
|
|
107
|
+
if (
|
|
108
|
+
isinstance(attr, type)
|
|
109
|
+
and issubclass(attr, BaseCommand)
|
|
110
|
+
and attr != BaseCommand
|
|
111
|
+
and hasattr(attr, "name")
|
|
112
|
+
and attr.name
|
|
113
|
+
):
|
|
114
|
+
|
|
64
115
|
self.commands[attr.name] = attr
|
|
65
|
-
|
|
116
|
+
|
|
66
117
|
# Register aliases
|
|
67
|
-
for alias in getattr(attr,
|
|
118
|
+
for alias in getattr(attr, "aliases", []):
|
|
68
119
|
self.commands[alias] = attr
|
|
69
|
-
|
|
120
|
+
|
|
70
121
|
except ImportError as e:
|
|
71
122
|
print(f"Failed to load command module {modname}: {e}")
|
|
72
|
-
|
|
123
|
+
|
|
124
|
+
def _load_skills(self):
|
|
125
|
+
"""Load skills as commands."""
|
|
126
|
+
if self._skills_loaded:
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
from minion_code.skills.skill_loader import load_skills
|
|
131
|
+
from minion_code.commands.skill_command import create_skill_command
|
|
132
|
+
|
|
133
|
+
registry = load_skills()
|
|
134
|
+
for skill in registry.list_all():
|
|
135
|
+
# Create a command class for each skill
|
|
136
|
+
skill_cmd_class = create_skill_command(skill)
|
|
137
|
+
self.commands[skill.name] = skill_cmd_class
|
|
138
|
+
|
|
139
|
+
self._skills_loaded = True
|
|
140
|
+
except ImportError as e:
|
|
141
|
+
print(f"Failed to load skills: {e}")
|
|
142
|
+
|
|
73
143
|
def get_command(self, name: str) -> Optional[Type[BaseCommand]]:
|
|
74
144
|
"""Get a command class by name."""
|
|
145
|
+
# Try to load skills if command not found
|
|
146
|
+
if name not in self.commands and not self._skills_loaded:
|
|
147
|
+
self._load_skills()
|
|
75
148
|
return self.commands.get(name)
|
|
76
|
-
|
|
149
|
+
|
|
77
150
|
def list_commands(self) -> Dict[str, Type[BaseCommand]]:
|
|
78
|
-
"""List all available commands."""
|
|
79
|
-
# Return only primary commands (not aliases)
|
|
80
|
-
return {
|
|
81
|
-
|
|
82
|
-
|
|
151
|
+
"""List all available commands (excluding skills)."""
|
|
152
|
+
# Return only primary commands (not aliases, not skills)
|
|
153
|
+
return {
|
|
154
|
+
name: cmd
|
|
155
|
+
for name, cmd in self.commands.items()
|
|
156
|
+
if cmd.name == name and not getattr(cmd, "is_skill", False)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
def list_skills(self) -> Dict[str, Type[BaseCommand]]:
|
|
160
|
+
"""List all available skills as commands."""
|
|
161
|
+
# Ensure skills are loaded
|
|
162
|
+
if not self._skills_loaded:
|
|
163
|
+
self._load_skills()
|
|
164
|
+
return {
|
|
165
|
+
name: cmd
|
|
166
|
+
for name, cmd in self.commands.items()
|
|
167
|
+
if cmd.name == name and getattr(cmd, "is_skill", False)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
def list_all(self) -> Dict[str, Type[BaseCommand]]:
|
|
171
|
+
"""List all available commands and skills."""
|
|
172
|
+
if not self._skills_loaded:
|
|
173
|
+
self._load_skills()
|
|
174
|
+
return {name: cmd for name, cmd in self.commands.items() if cmd.name == name}
|
|
175
|
+
|
|
83
176
|
def reload_commands(self):
|
|
84
177
|
"""Reload all commands."""
|
|
85
178
|
self.commands.clear()
|
|
179
|
+
self._skills_loaded = False
|
|
86
180
|
self._load_commands()
|
|
87
181
|
|
|
182
|
+
def reload_skills(self):
|
|
183
|
+
"""Reload skills only."""
|
|
184
|
+
# Remove existing skill commands
|
|
185
|
+
skills_to_remove = [
|
|
186
|
+
name
|
|
187
|
+
for name, cmd in self.commands.items()
|
|
188
|
+
if getattr(cmd, "is_skill", False)
|
|
189
|
+
]
|
|
190
|
+
for name in skills_to_remove:
|
|
191
|
+
del self.commands[name]
|
|
192
|
+
self._skills_loaded = False
|
|
193
|
+
self._load_skills()
|
|
194
|
+
|
|
88
195
|
|
|
89
196
|
# Global command registry
|
|
90
|
-
command_registry = CommandRegistry()
|
|
197
|
+
command_registry = CommandRegistry()
|
|
@@ -4,67 +4,40 @@
|
|
|
4
4
|
Clear command - Clear conversation history
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from rich.prompt import Confirm
|
|
9
|
-
from minion_code.commands import BaseCommand
|
|
7
|
+
from minion_code.commands import BaseCommand, CommandType
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class ClearCommand(BaseCommand):
|
|
13
11
|
"""Clear conversation history."""
|
|
14
|
-
|
|
12
|
+
|
|
15
13
|
name = "clear"
|
|
16
14
|
description = "Clear the conversation history"
|
|
17
|
-
usage = "/clear
|
|
15
|
+
usage = "/clear"
|
|
18
16
|
aliases = ["c", "reset"]
|
|
19
|
-
|
|
17
|
+
command_type = CommandType.LOCAL
|
|
18
|
+
|
|
20
19
|
async def execute(self, args: str) -> None:
|
|
21
20
|
"""Execute the clear command."""
|
|
22
21
|
if not self.agent:
|
|
23
|
-
|
|
24
|
-
"❌
|
|
25
|
-
title="[bold red]Error[/bold red]",
|
|
26
|
-
border_style="red"
|
|
22
|
+
self.output.panel(
|
|
23
|
+
"❌ Agent not initialized", title="Error", border_style="red"
|
|
27
24
|
)
|
|
28
|
-
self.console.print(error_panel)
|
|
29
25
|
return
|
|
30
|
-
|
|
31
|
-
force = "--force" in args or "-f" in args
|
|
32
|
-
|
|
26
|
+
|
|
33
27
|
history = self.agent.get_conversation_history()
|
|
34
28
|
if not history:
|
|
35
|
-
|
|
36
|
-
"📝
|
|
37
|
-
title="
|
|
38
|
-
border_style="blue"
|
|
29
|
+
self.output.panel(
|
|
30
|
+
"📝 No conversation history to clear.",
|
|
31
|
+
title="Info",
|
|
32
|
+
border_style="blue",
|
|
39
33
|
)
|
|
40
|
-
self.console.print(no_history_panel)
|
|
41
34
|
return
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
if not force:
|
|
45
|
-
confirm_panel = Panel(
|
|
46
|
-
f"⚠️ [bold yellow]This will clear {len(history)} messages from history.[/bold yellow]\n"
|
|
47
|
-
"This action cannot be undone.",
|
|
48
|
-
title="[bold yellow]Confirm Clear[/bold yellow]",
|
|
49
|
-
border_style="yellow"
|
|
50
|
-
)
|
|
51
|
-
self.console.print(confirm_panel)
|
|
52
|
-
|
|
53
|
-
if not Confirm.ask("Are you sure you want to clear the history?", console=self.console):
|
|
54
|
-
cancel_panel = Panel(
|
|
55
|
-
"❌ [bold blue]Clear operation cancelled.[/bold blue]",
|
|
56
|
-
title="[bold blue]Cancelled[/bold blue]",
|
|
57
|
-
border_style="blue"
|
|
58
|
-
)
|
|
59
|
-
self.console.print(cancel_panel)
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
# Clear the history
|
|
35
|
+
|
|
36
|
+
# Clear the history directly without confirmation
|
|
63
37
|
self.agent.clear_conversation_history()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
f"🗑️
|
|
67
|
-
title="
|
|
68
|
-
border_style="green"
|
|
38
|
+
|
|
39
|
+
self.output.panel(
|
|
40
|
+
f"🗑️ Successfully cleared {len(history)} messages from history.",
|
|
41
|
+
title="History Cleared",
|
|
42
|
+
border_style="green",
|
|
69
43
|
)
|
|
70
|
-
self.console.print(success_panel)
|
|
@@ -4,87 +4,71 @@
|
|
|
4
4
|
Help command - Show available commands and their usage
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from rich.panel import Panel
|
|
9
|
-
from rich.markdown import Markdown
|
|
10
|
-
from minion_code.commands import BaseCommand
|
|
7
|
+
from minion_code.commands import BaseCommand, CommandType
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
class HelpCommand(BaseCommand):
|
|
14
11
|
"""Show help information for commands."""
|
|
15
|
-
|
|
12
|
+
|
|
16
13
|
name = "help"
|
|
17
14
|
description = "Show available commands and their usage"
|
|
18
15
|
usage = "/help [command_name]"
|
|
19
16
|
aliases = ["h", "?"]
|
|
20
|
-
|
|
17
|
+
command_type = CommandType.LOCAL
|
|
18
|
+
|
|
21
19
|
async def execute(self, args: str) -> None:
|
|
22
20
|
"""Execute the help command."""
|
|
23
21
|
args = args.strip()
|
|
24
|
-
|
|
22
|
+
|
|
25
23
|
if args:
|
|
26
24
|
# Show help for specific command
|
|
27
25
|
await self._show_command_help(args)
|
|
28
26
|
else:
|
|
29
27
|
# Show general help
|
|
30
28
|
await self._show_general_help()
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
async def _show_command_help(self, command_name: str) -> None:
|
|
33
31
|
"""Show help for a specific command."""
|
|
34
32
|
from minion_code.commands import command_registry
|
|
35
|
-
|
|
33
|
+
|
|
36
34
|
command_class = command_registry.get_command(command_name)
|
|
37
35
|
if not command_class:
|
|
38
|
-
|
|
39
|
-
f"❌
|
|
40
|
-
title="
|
|
41
|
-
border_style="red"
|
|
36
|
+
self.output.panel(
|
|
37
|
+
f"❌ Command '/{command_name}' not found",
|
|
38
|
+
title="Error",
|
|
39
|
+
border_style="red",
|
|
42
40
|
)
|
|
43
|
-
self.console.print(error_panel)
|
|
44
41
|
return
|
|
45
|
-
|
|
42
|
+
|
|
46
43
|
# Create temporary command instance to get help
|
|
47
|
-
temp_command = command_class(self.
|
|
44
|
+
temp_command = command_class(self.output, self.agent)
|
|
48
45
|
help_text = temp_command.get_help()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
title=f"[bold blue]Help: /{command_name}[/bold blue]",
|
|
53
|
-
border_style="blue"
|
|
46
|
+
|
|
47
|
+
self.output.panel(
|
|
48
|
+
help_text, title=f"Help: /{command_name}", border_style="blue"
|
|
54
49
|
)
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
|
|
57
51
|
async def _show_general_help(self) -> None:
|
|
58
52
|
"""Show general help with all commands."""
|
|
59
53
|
from minion_code.commands import command_registry
|
|
60
|
-
|
|
54
|
+
|
|
61
55
|
commands = command_registry.list_commands()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
help_table.add_column("Command", style="cyan", no_wrap=True)
|
|
69
|
-
help_table.add_column("Description", style="white")
|
|
70
|
-
help_table.add_column("Aliases", style="yellow")
|
|
71
|
-
|
|
56
|
+
|
|
57
|
+
# Prepare table data
|
|
58
|
+
headers = ["Command", "Description", "Aliases"]
|
|
59
|
+
rows = []
|
|
60
|
+
|
|
72
61
|
for name, command_class in sorted(commands.items()):
|
|
73
62
|
aliases = ", ".join(f"/{alias}" for alias in command_class.aliases)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
self.console.print(help_table)
|
|
81
|
-
|
|
63
|
+
rows.append([f"/{name}", command_class.description, aliases or "-"])
|
|
64
|
+
|
|
65
|
+
self.output.table(headers, rows, title="📚 Available Commands")
|
|
66
|
+
|
|
82
67
|
# Show usage info
|
|
83
|
-
|
|
84
|
-
"💡
|
|
85
|
-
"💬
|
|
86
|
-
"🔍
|
|
87
|
-
title="
|
|
88
|
-
border_style="green"
|
|
68
|
+
self.output.panel(
|
|
69
|
+
"💡 Commands must start with '/' (e.g., /help, /tools)\n"
|
|
70
|
+
"💬 Regular messages are sent to the AI agent\n"
|
|
71
|
+
"🔍 Use '/help <command>' for detailed help on a specific command",
|
|
72
|
+
title="Usage Tips",
|
|
73
|
+
border_style="green",
|
|
89
74
|
)
|
|
90
|
-
self.console.print(usage_panel)
|
|
@@ -4,30 +4,26 @@
|
|
|
4
4
|
History command - Show conversation history
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from rich.table import Table
|
|
9
|
-
from minion_code.commands import BaseCommand
|
|
7
|
+
from minion_code.commands import BaseCommand, CommandType
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class HistoryCommand(BaseCommand):
|
|
13
11
|
"""Show conversation history."""
|
|
14
|
-
|
|
12
|
+
|
|
15
13
|
name = "history"
|
|
16
14
|
description = "Show conversation history with the agent"
|
|
17
15
|
usage = "/history [count]"
|
|
18
16
|
aliases = ["hist", "h"]
|
|
19
|
-
|
|
17
|
+
command_type = CommandType.LOCAL
|
|
18
|
+
|
|
20
19
|
async def execute(self, args: str) -> None:
|
|
21
20
|
"""Execute the history command."""
|
|
22
21
|
if not self.agent:
|
|
23
|
-
|
|
24
|
-
"❌
|
|
25
|
-
title="[bold red]Error[/bold red]",
|
|
26
|
-
border_style="red"
|
|
22
|
+
self.output.panel(
|
|
23
|
+
"❌ Agent not initialized", title="Error", border_style="red"
|
|
27
24
|
)
|
|
28
|
-
self.console.print(error_panel)
|
|
29
25
|
return
|
|
30
|
-
|
|
26
|
+
|
|
31
27
|
# Parse count argument
|
|
32
28
|
count = 5 # default
|
|
33
29
|
if args.strip():
|
|
@@ -36,69 +32,55 @@ class HistoryCommand(BaseCommand):
|
|
|
36
32
|
if count <= 0:
|
|
37
33
|
count = 5
|
|
38
34
|
except ValueError:
|
|
39
|
-
|
|
40
|
-
f"❌
|
|
41
|
-
title="
|
|
42
|
-
border_style="yellow"
|
|
35
|
+
self.output.panel(
|
|
36
|
+
f"❌ Invalid count: '{args.strip()}'. Using default (5)",
|
|
37
|
+
title="Warning",
|
|
38
|
+
border_style="yellow",
|
|
43
39
|
)
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
|
|
46
41
|
history = self.agent.get_conversation_history()
|
|
47
42
|
if not history:
|
|
48
|
-
|
|
49
|
-
"📝
|
|
50
|
-
title="[bold blue]History[/bold blue]",
|
|
51
|
-
border_style="blue"
|
|
43
|
+
self.output.panel(
|
|
44
|
+
"📝 No conversation history yet.", title="History", border_style="blue"
|
|
52
45
|
)
|
|
53
|
-
self.console.print(no_history_panel)
|
|
54
46
|
return
|
|
55
|
-
|
|
47
|
+
|
|
56
48
|
# Show header
|
|
57
|
-
|
|
58
|
-
f"📝
|
|
59
|
-
border_style="blue"
|
|
49
|
+
self.output.panel(
|
|
50
|
+
f"📝 Conversation History (showing last {min(count, len(history))} of {len(history)} messages)",
|
|
51
|
+
border_style="blue",
|
|
60
52
|
)
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
|
|
63
54
|
# Show recent messages
|
|
64
55
|
recent_history = history[-count:] if count < len(history) else history
|
|
65
|
-
|
|
56
|
+
|
|
66
57
|
for i, entry in enumerate(recent_history, 1):
|
|
67
58
|
message_num = len(history) - len(recent_history) + i
|
|
68
|
-
|
|
59
|
+
|
|
69
60
|
# User message
|
|
70
|
-
user_msg = entry[
|
|
61
|
+
user_msg = entry["user_message"]
|
|
71
62
|
if len(user_msg) > 150:
|
|
72
63
|
user_msg = user_msg[:150] + "..."
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
user_msg,
|
|
76
|
-
title=f"👤 [bold cyan]You (#{message_num})[/bold cyan]",
|
|
77
|
-
border_style="cyan"
|
|
64
|
+
|
|
65
|
+
self.output.panel(
|
|
66
|
+
user_msg, title=f"👤 You (#{message_num})", border_style="cyan"
|
|
78
67
|
)
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
|
|
81
69
|
# Agent response
|
|
82
|
-
agent_msg = entry[
|
|
70
|
+
agent_msg = entry["agent_response"]
|
|
83
71
|
if len(agent_msg) > 200:
|
|
84
72
|
agent_msg = agent_msg[:200] + "..."
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
title="🤖 [bold green]Agent[/bold green]",
|
|
89
|
-
border_style="green"
|
|
90
|
-
)
|
|
91
|
-
self.console.print(agent_panel)
|
|
92
|
-
|
|
73
|
+
|
|
74
|
+
self.output.panel(agent_msg, title="🤖 Agent", border_style="green")
|
|
75
|
+
|
|
93
76
|
if i < len(recent_history): # Don't add spacing after last message
|
|
94
|
-
self.
|
|
95
|
-
|
|
77
|
+
self.output.text("")
|
|
78
|
+
|
|
96
79
|
# Show summary if there are more messages
|
|
97
80
|
if len(history) > count:
|
|
98
|
-
|
|
99
|
-
f"💡
|
|
100
|
-
f"Use '/history {len(history)}' to see all {len(history)} messages.
|
|
101
|
-
title="
|
|
102
|
-
border_style="yellow"
|
|
81
|
+
self.output.panel(
|
|
82
|
+
f"💡 Showing {count} most recent messages. "
|
|
83
|
+
f"Use '/history {len(history)}' to see all {len(history)} messages.",
|
|
84
|
+
title="Note",
|
|
85
|
+
border_style="yellow",
|
|
103
86
|
)
|
|
104
|
-
self.console.print(summary_panel)
|