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
@@ -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
- def __init__(self, console, agent=None):
25
- self.console = console
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('_command.py') and not filename.startswith('_'):
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'minion_code.commands.{modname}')
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 (isinstance(attr, type) and
60
- issubclass(attr, BaseCommand) and
61
- attr != BaseCommand and
62
- hasattr(attr, 'name') and attr.name):
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, 'aliases', []):
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 {name: cmd for name, cmd in self.commands.items()
81
- if cmd.name == name}
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 rich.panel import Panel
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 [--force]"
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
- error_panel = Panel(
24
- "❌ [bold red]Agent not initialized[/bold red]",
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
- no_history_panel = Panel(
36
- "📝 [italic]No conversation history to clear.[/italic]",
37
- title="[bold blue]Info[/bold blue]",
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
- # Confirm before clearing unless --force is used
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
- success_panel = Panel(
66
- f"🗑️ [bold green]Successfully cleared {len(history)} messages from history.[/bold green]",
67
- title="[bold green]History Cleared[/bold green]",
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 rich.table import Table
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
- error_panel = Panel(
39
- f"❌ [bold red]Command '/{command_name}' not found[/bold red]",
40
- title="[bold red]Error[/bold red]",
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.console, self.agent)
44
+ temp_command = command_class(self.output, self.agent)
48
45
  help_text = temp_command.get_help()
49
-
50
- help_panel = Panel(
51
- Markdown(help_text),
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
- self.console.print(help_panel)
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
- help_table = Table(
64
- title="📚 Available Commands",
65
- show_header=True,
66
- header_style="bold blue"
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
- help_table.add_row(
75
- f"/{name}",
76
- command_class.description,
77
- aliases or "-"
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
- usage_panel = Panel(
84
- "💡 [italic]Commands must start with '/' (e.g., /help, /tools)[/italic]\n"
85
- "💬 [italic]Regular messages are sent to the AI agent[/italic]\n"
86
- "🔍 [italic]Use '/help <command>' for detailed help on a specific command[/italic]",
87
- title="[bold green]Usage Tips[/bold green]",
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 rich.panel import Panel
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
- error_panel = Panel(
24
- "❌ [bold red]Agent not initialized[/bold red]",
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
- error_panel = Panel(
40
- f"❌ [bold red]Invalid count: '{args.strip()}'. Using default (5)[/bold red]",
41
- title="[bold red]Warning[/bold red]",
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
- self.console.print(error_panel)
45
-
40
+
46
41
  history = self.agent.get_conversation_history()
47
42
  if not history:
48
- no_history_panel = Panel(
49
- "📝 [italic]No conversation history yet.[/italic]",
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
- header_panel = Panel(
58
- f"📝 [bold blue]Conversation History (showing last {min(count, len(history))} of {len(history)} messages)[/bold blue]",
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
- self.console.print(header_panel)
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['user_message']
61
+ user_msg = entry["user_message"]
71
62
  if len(user_msg) > 150:
72
63
  user_msg = user_msg[:150] + "..."
73
-
74
- user_panel = Panel(
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
- self.console.print(user_panel)
80
-
68
+
81
69
  # Agent response
82
- agent_msg = entry['agent_response']
70
+ agent_msg = entry["agent_response"]
83
71
  if len(agent_msg) > 200:
84
72
  agent_msg = agent_msg[:200] + "..."
85
-
86
- agent_panel = Panel(
87
- agent_msg,
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.console.print()
95
-
77
+ self.output.text("")
78
+
96
79
  # Show summary if there are more messages
97
80
  if len(history) > count:
98
- summary_panel = Panel(
99
- f"💡 [italic]Showing {count} most recent messages. "
100
- f"Use '/history {len(history)}' to see all {len(history)} messages.[/italic]",
101
- title="[bold yellow]Note[/bold yellow]",
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)