minion-code 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. examples/cli_entrypoint.py +60 -0
  2. examples/{agent_with_todos.py → components/agent_with_todos.py} +58 -47
  3. examples/{message_response_children_demo.py → components/message_response_children_demo.py} +61 -55
  4. examples/components/messages_component.py +199 -0
  5. examples/file_freshness_example.py +22 -22
  6. examples/file_watching_example.py +32 -26
  7. examples/interruptible_tui.py +921 -3
  8. examples/repl_tui.py +129 -0
  9. examples/skills/example_usage.py +57 -0
  10. examples/start.py +173 -0
  11. minion_code/__init__.py +1 -1
  12. minion_code/acp_server/__init__.py +34 -0
  13. minion_code/acp_server/agent.py +539 -0
  14. minion_code/acp_server/hooks.py +354 -0
  15. minion_code/acp_server/main.py +194 -0
  16. minion_code/acp_server/permissions.py +142 -0
  17. minion_code/acp_server/test_client.py +104 -0
  18. minion_code/adapters/__init__.py +22 -0
  19. minion_code/adapters/output_adapter.py +207 -0
  20. minion_code/adapters/rich_adapter.py +169 -0
  21. minion_code/adapters/textual_adapter.py +254 -0
  22. minion_code/agents/__init__.py +2 -2
  23. minion_code/agents/code_agent.py +517 -104
  24. minion_code/agents/hooks.py +378 -0
  25. minion_code/cli.py +538 -429
  26. minion_code/cli_simple.py +665 -0
  27. minion_code/commands/__init__.py +136 -29
  28. minion_code/commands/clear_command.py +19 -46
  29. minion_code/commands/help_command.py +33 -49
  30. minion_code/commands/history_command.py +37 -55
  31. minion_code/commands/model_command.py +194 -0
  32. minion_code/commands/quit_command.py +9 -12
  33. minion_code/commands/resume_command.py +181 -0
  34. minion_code/commands/skill_command.py +89 -0
  35. minion_code/commands/status_command.py +48 -73
  36. minion_code/commands/tools_command.py +54 -52
  37. minion_code/commands/version_command.py +34 -69
  38. minion_code/components/ConfirmDialog.py +430 -0
  39. minion_code/components/Message.py +318 -97
  40. minion_code/components/MessageResponse.py +30 -29
  41. minion_code/components/Messages.py +351 -0
  42. minion_code/components/PromptInput.py +499 -245
  43. minion_code/components/__init__.py +24 -17
  44. minion_code/const.py +7 -0
  45. minion_code/screens/REPL.py +1453 -469
  46. minion_code/screens/__init__.py +1 -1
  47. minion_code/services/__init__.py +20 -20
  48. minion_code/services/event_system.py +19 -14
  49. minion_code/services/file_freshness_service.py +223 -170
  50. minion_code/skills/__init__.py +25 -0
  51. minion_code/skills/skill.py +128 -0
  52. minion_code/skills/skill_loader.py +198 -0
  53. minion_code/skills/skill_registry.py +177 -0
  54. minion_code/subagents/__init__.py +31 -0
  55. minion_code/subagents/builtin/__init__.py +30 -0
  56. minion_code/subagents/builtin/claude_code_guide.py +32 -0
  57. minion_code/subagents/builtin/explore.py +36 -0
  58. minion_code/subagents/builtin/general_purpose.py +19 -0
  59. minion_code/subagents/builtin/plan.py +61 -0
  60. minion_code/subagents/subagent.py +116 -0
  61. minion_code/subagents/subagent_loader.py +147 -0
  62. minion_code/subagents/subagent_registry.py +151 -0
  63. minion_code/tools/__init__.py +8 -2
  64. minion_code/tools/bash_tool.py +16 -3
  65. minion_code/tools/file_edit_tool.py +201 -104
  66. minion_code/tools/file_read_tool.py +183 -26
  67. minion_code/tools/file_write_tool.py +17 -3
  68. minion_code/tools/glob_tool.py +23 -2
  69. minion_code/tools/grep_tool.py +229 -21
  70. minion_code/tools/ls_tool.py +28 -3
  71. minion_code/tools/multi_edit_tool.py +89 -84
  72. minion_code/tools/python_interpreter_tool.py +9 -1
  73. minion_code/tools/skill_tool.py +210 -0
  74. minion_code/tools/task_tool.py +287 -0
  75. minion_code/tools/todo_read_tool.py +28 -24
  76. minion_code/tools/todo_write_tool.py +82 -65
  77. minion_code/{types.py → type_defs.py} +15 -2
  78. minion_code/utils/__init__.py +45 -17
  79. minion_code/utils/config.py +610 -0
  80. minion_code/utils/history.py +114 -0
  81. minion_code/utils/logs.py +53 -0
  82. minion_code/utils/mcp_loader.py +153 -55
  83. minion_code/utils/output_truncator.py +233 -0
  84. minion_code/utils/session_storage.py +369 -0
  85. minion_code/utils/todo_file_utils.py +26 -22
  86. minion_code/utils/todo_storage.py +43 -33
  87. minion_code/web/__init__.py +9 -0
  88. minion_code/web/adapters/__init__.py +5 -0
  89. minion_code/web/adapters/web_adapter.py +524 -0
  90. minion_code/web/api/__init__.py +7 -0
  91. minion_code/web/api/chat.py +277 -0
  92. minion_code/web/api/interactions.py +136 -0
  93. minion_code/web/api/sessions.py +135 -0
  94. minion_code/web/server.py +149 -0
  95. minion_code/web/services/__init__.py +5 -0
  96. minion_code/web/services/session_manager.py +420 -0
  97. minion_code-0.1.1.dist-info/METADATA +475 -0
  98. minion_code-0.1.1.dist-info/RECORD +111 -0
  99. {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/WHEEL +1 -1
  100. minion_code-0.1.1.dist-info/entry_points.txt +6 -0
  101. tests/test_adapter.py +67 -0
  102. tests/test_adapter_simple.py +79 -0
  103. tests/test_file_read_tool.py +144 -0
  104. tests/test_readonly_tools.py +0 -2
  105. tests/test_skills.py +441 -0
  106. examples/advance_tui.py +0 -508
  107. examples/rich_example.py +0 -4
  108. examples/simple_file_watching.py +0 -57
  109. examples/simple_tui.py +0 -267
  110. examples/simple_usage.py +0 -69
  111. minion_code-0.1.0.dist-info/METADATA +0 -350
  112. minion_code-0.1.0.dist-info/RECORD +0 -59
  113. minion_code-0.1.0.dist-info/entry_points.txt +0 -4
  114. {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/licenses/LICENSE +0 -0
  115. {minion_code-0.1.0.dist-info → minion_code-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Model command - View and configure the LLM model
5
+ """
6
+
7
+ import json
8
+ import yaml
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+ from minion_code.commands import BaseCommand, CommandType
12
+
13
+
14
+ class ModelCommand(BaseCommand):
15
+ """View or set the default LLM model."""
16
+
17
+ name = "model"
18
+ description = "View or set the default LLM model"
19
+ usage = "/model [model_name] or /model --clear or /model --list"
20
+ aliases = ["llm"]
21
+ command_type = CommandType.LOCAL
22
+
23
+ # Config file path
24
+ CONFIG_DIR = Path.home() / ".minion"
25
+ CONFIG_FILE = CONFIG_DIR / "minion-code.json"
26
+ MINION_CONFIG_FILE = CONFIG_DIR / "config.yaml"
27
+
28
+ def _load_config(self) -> dict:
29
+ """Load config from file."""
30
+ if self.CONFIG_FILE.exists():
31
+ try:
32
+ with open(self.CONFIG_FILE, "r") as f:
33
+ return json.load(f)
34
+ except Exception:
35
+ pass
36
+ return {}
37
+
38
+ def _save_config(self, config: dict) -> None:
39
+ """Save config to file."""
40
+ self.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
41
+ with open(self.CONFIG_FILE, "w") as f:
42
+ json.dump(config, f, indent=2)
43
+
44
+ def _get_available_models(self) -> List[str]:
45
+ """Get list of available models from minion config.yaml.
46
+
47
+ Uses the same logic as minion's load_config():
48
+ 1. First load user config (~/.minion/config.yaml)
49
+ 2. Then load base config (MINION_ROOT/config/config.yaml) which OVERWRITES user config
50
+ """
51
+ config_dict = {}
52
+
53
+ # First load user config
54
+ if self.MINION_CONFIG_FILE.exists():
55
+ try:
56
+ with open(self.MINION_CONFIG_FILE, "r") as f:
57
+ config_dict = yaml.safe_load(f) or {}
58
+ except Exception:
59
+ pass
60
+
61
+ # Then load base config (MINION_ROOT), which overwrites user config
62
+ try:
63
+ from minion.const import get_minion_root
64
+
65
+ minion_root = get_minion_root()
66
+ base_config_path = Path(minion_root) / "config" / "config.yaml"
67
+ if base_config_path.exists():
68
+ with open(base_config_path, "r") as f:
69
+ base_config = yaml.safe_load(f) or {}
70
+ config_dict.update(base_config) # Overwrite, same as minion
71
+ except Exception:
72
+ pass
73
+
74
+ models = list(config_dict.get("models", {}).keys())
75
+ return sorted(models)
76
+
77
+ def _update_agent_model(self, model_name: str) -> bool:
78
+ """Update the agent's model at runtime."""
79
+ if not self.agent:
80
+ return False
81
+
82
+ try:
83
+ from minion.types.llm_types import create_llm_from_model
84
+
85
+ # Create new LLM provider with the new model
86
+ new_llm = create_llm_from_model(model_name)
87
+ self.agent.llm = new_llm
88
+
89
+ # Update llms dict if it exists
90
+ if hasattr(self.agent, "llms"):
91
+ self.agent.llms["main"] = new_llm
92
+
93
+ return True
94
+ except Exception as e:
95
+ self.output.warning(f"Could not update runtime model: {e}")
96
+ return False
97
+
98
+ def _set_model(self, model_name: str) -> None:
99
+ """Set the model and update config."""
100
+ config = self._load_config()
101
+ config["model"] = model_name
102
+ self._save_config(config)
103
+
104
+ # Try to update the running agent's model
105
+ runtime_updated = self._update_agent_model(model_name)
106
+
107
+ if runtime_updated:
108
+ self.output.success(f"Model changed to: {model_name}")
109
+ else:
110
+ self.output.success(f"Default model set to: {model_name}")
111
+ self.output.warning("Restart session to use the new model.")
112
+
113
+ self.output.info(f"Config saved to: {self.CONFIG_FILE}")
114
+
115
+ async def execute(self, args: str) -> None:
116
+ """Execute the model command."""
117
+ args = args.strip()
118
+ config = self._load_config()
119
+
120
+ if args == "--clear" or args == "-c":
121
+ # Clear model setting
122
+ if "model" in config:
123
+ del config["model"]
124
+ self._save_config(config)
125
+ self.output.success("Model setting cleared. Will use default model.")
126
+ else:
127
+ self.output.info("No model setting to clear.")
128
+
129
+ elif args == "--list" or args == "-l":
130
+ # List available models
131
+ models = self._get_available_models()
132
+ if models:
133
+ self.output.info(f"Available models ({len(models)}):")
134
+ for model in models:
135
+ self.output.text(f" - {model}")
136
+ else:
137
+ self.output.warning("No models found in config.yaml")
138
+
139
+ elif args:
140
+ # Set model directly
141
+ self._set_model(args)
142
+
143
+ else:
144
+ # Interactive mode: show current model and let user choose
145
+ current_config_model = config.get("model")
146
+
147
+ # Get current agent model
148
+ current_agent_model = None
149
+ if self.agent:
150
+ current_agent_model = getattr(self.agent, "llm", None)
151
+
152
+ # Display current info
153
+ headers = ["Setting", "Value"]
154
+ rows = []
155
+
156
+ if current_agent_model:
157
+ rows.append(["Current Session Model", str(current_agent_model)])
158
+
159
+ if current_config_model:
160
+ rows.append(["Config File Model", current_config_model])
161
+ else:
162
+ rows.append(["Config File Model", "(not set - using default)"])
163
+
164
+ self.output.table(headers, rows, title="Model Configuration")
165
+
166
+ # Get available models and show selection
167
+ models = self._get_available_models()
168
+ if models:
169
+ # Add current model indicator
170
+ choices = []
171
+ for model in models:
172
+ if model == current_config_model:
173
+ choices.append(f"{model} (current)")
174
+ else:
175
+ choices.append(model)
176
+
177
+ # Ask user to select (returns index, -1 if cancelled)
178
+ selected_index = await self.output.choice(
179
+ message="Select a model:", choices=choices, title="Available Models"
180
+ )
181
+
182
+ if selected_index >= 0 and selected_index < len(models):
183
+ # Get the model name from original models list (without " (current)" suffix)
184
+ model_name = models[selected_index]
185
+ if model_name != current_config_model:
186
+ self._set_model(model_name)
187
+ else:
188
+ self.output.info("Model unchanged.")
189
+ else:
190
+ # No models found, show usage hints
191
+ self.output.info("\nUsage:")
192
+ self.output.text(" /model <name> - Set model (e.g., /model gpt-4o)")
193
+ self.output.text(" /model --list - List available models")
194
+ self.output.text(" /model --clear - Clear saved model setting")
@@ -4,29 +4,26 @@
4
4
  Quit command - Exit the application
5
5
  """
6
6
 
7
- from rich.panel import Panel
8
- from minion_code.commands import BaseCommand
7
+ from minion_code.commands import BaseCommand, CommandType
9
8
 
10
9
 
11
10
  class QuitCommand(BaseCommand):
12
11
  """Exit the application."""
13
-
12
+
14
13
  name = "quit"
15
14
  description = "Exit the application"
16
15
  usage = "/quit"
17
16
  aliases = ["exit", "q", "bye"]
18
-
17
+ command_type = CommandType.LOCAL
18
+
19
19
  async def execute(self, args: str) -> None:
20
20
  """Execute the quit command."""
21
- goodbye_panel = Panel(
22
- "👋 [bold yellow]Goodbye! Thanks for using MinionCode![/bold yellow]",
23
- title="[bold red]Exit[/bold red]",
24
- border_style="red"
21
+ self.output.panel(
22
+ "👋 Goodbye! Thanks for using MinionCode!", title="Exit", border_style="red"
25
23
  )
26
- self.console.print(goodbye_panel)
27
-
24
+
28
25
  # Set a flag that the TUI can check and cleanup resources
29
- if hasattr(self, '_tui_instance'):
26
+ if hasattr(self, "_tui_instance"):
30
27
  self._tui_instance.running = False
31
28
  # Cleanup MCP resources
32
- await self._tui_instance.cleanup()
29
+ await self._tui_instance.cleanup()
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Resume command - Resume a previous session
5
+ """
6
+
7
+ import os
8
+ from minion_code.commands import BaseCommand, CommandType
9
+ from minion_code.utils.session_storage import (
10
+ list_sessions,
11
+ load_session,
12
+ get_latest_session_id,
13
+ SessionMetadata,
14
+ )
15
+
16
+
17
+ class ResumeCommand(BaseCommand):
18
+ """Resume a previous conversation session."""
19
+
20
+ name = "resume"
21
+ description = "Resume a previous session or list available sessions"
22
+ usage = "/resume [session_id] or /resume list"
23
+ aliases = ["r"]
24
+ command_type = CommandType.LOCAL
25
+
26
+ async def execute(self, args: str) -> None:
27
+ """Execute the resume command.
28
+
29
+ Args:
30
+ args: Optional session_id or 'list' to show available sessions
31
+ """
32
+ args = args.strip()
33
+ current_project = os.getcwd()
34
+
35
+ # /resume list - show available sessions
36
+ if args.lower() == "list" or args.lower() == "ls":
37
+ await self._list_sessions(current_project)
38
+ return
39
+
40
+ # /resume <session_id> - resume specific session
41
+ if args:
42
+ await self._resume_session(args)
43
+ return
44
+
45
+ # /resume (no args) - resume latest session for current project
46
+ latest_id = get_latest_session_id(project_path=current_project)
47
+ if latest_id:
48
+ await self._resume_session(latest_id)
49
+ else:
50
+ self.output.panel(
51
+ "No previous sessions found for this project.\n\n"
52
+ "Use `/resume list` to see all available sessions.",
53
+ title="No Sessions",
54
+ border_style="yellow",
55
+ )
56
+
57
+ async def _list_sessions(self, project_path: str) -> None:
58
+ """List available sessions."""
59
+ # Get sessions for current project
60
+ project_sessions = list_sessions(project_path=project_path, limit=10)
61
+
62
+ # Get all sessions
63
+ all_sessions = list_sessions(limit=20)
64
+
65
+ if not all_sessions:
66
+ self.output.panel(
67
+ "No saved sessions found.\n\n"
68
+ "Sessions are automatically saved during conversations.",
69
+ title="No Sessions",
70
+ border_style="yellow",
71
+ )
72
+ return
73
+
74
+ # Build output
75
+ lines = []
76
+
77
+ # Current project sessions
78
+ if project_sessions:
79
+ lines.append("**This Project:**")
80
+ lines.append("")
81
+ for session in project_sessions[:5]:
82
+ lines.append(self._format_session_line(session))
83
+ lines.append("")
84
+
85
+ # Other sessions
86
+ other_sessions = [s for s in all_sessions if s.project_path != project_path]
87
+ if other_sessions:
88
+ lines.append("**Other Projects:**")
89
+ lines.append("")
90
+ for session in other_sessions[:5]:
91
+ lines.append(self._format_session_line(session, show_path=True))
92
+ lines.append("")
93
+
94
+ lines.append("---")
95
+ lines.append("Use `/resume <id>` to restore a session")
96
+ lines.append("Use `/resume` to restore the latest session")
97
+
98
+ self.output.panel(
99
+ "\n".join(lines), title="Available Sessions", border_style="blue"
100
+ )
101
+
102
+ def _format_session_line(
103
+ self, session: SessionMetadata, show_path: bool = False
104
+ ) -> str:
105
+ """Format a session for display."""
106
+ # Parse timestamp for display
107
+ try:
108
+ from datetime import datetime
109
+
110
+ updated = datetime.fromisoformat(session.updated_at)
111
+ time_str = updated.strftime("%m/%d %H:%M")
112
+ except:
113
+ time_str = session.updated_at[:16]
114
+
115
+ title = session.title or "(no title)"
116
+ if len(title) > 40:
117
+ title = title[:40] + "..."
118
+
119
+ line = f" `{session.session_id}` - {title} ({session.message_count} msgs, {time_str})"
120
+
121
+ if show_path:
122
+ # Show shortened path
123
+ path = session.project_path
124
+ home = os.path.expanduser("~")
125
+ if path.startswith(home):
126
+ path = "~" + path[len(home) :]
127
+ if len(path) > 30:
128
+ path = "..." + path[-27:]
129
+ line += f"\n {path}"
130
+
131
+ return line
132
+
133
+ async def _resume_session(self, session_id: str) -> None:
134
+ """Resume a specific session."""
135
+ session = load_session(session_id)
136
+
137
+ if not session:
138
+ self.output.panel(
139
+ f"Session `{session_id}` not found.\n\n"
140
+ "Use `/resume list` to see available sessions.",
141
+ title="Session Not Found",
142
+ border_style="red",
143
+ )
144
+ return
145
+
146
+ # Check if agent supports session restoration
147
+ if not self.agent:
148
+ self.output.panel(
149
+ "Agent not initialized. Cannot restore session.",
150
+ title="Error",
151
+ border_style="red",
152
+ )
153
+ return
154
+
155
+ # Check if agent has restore_session method
156
+ if not hasattr(self.agent, "restore_session"):
157
+ self.output.panel(
158
+ "Session restoration not yet implemented in agent.\n\n"
159
+ f"Session `{session_id}` has {len(session.messages)} messages:\n"
160
+ f"Title: {session.metadata.title or '(none)'}\n"
161
+ f"Project: {session.metadata.project_path}",
162
+ title="Coming Soon",
163
+ border_style="yellow",
164
+ )
165
+ return
166
+
167
+ # Restore the session
168
+ try:
169
+ await self.agent.restore_session(session)
170
+
171
+ self.output.panel(
172
+ f"Restored session `{session_id}` with {len(session.messages)} messages.\n\n"
173
+ f"Title: {session.metadata.title or '(none)'}\n"
174
+ f"You can continue the conversation now.",
175
+ title="Session Restored",
176
+ border_style="green",
177
+ )
178
+ except Exception as e:
179
+ self.output.panel(
180
+ f"Failed to restore session: {e}", title="Error", border_style="red"
181
+ )
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Skill command - wrapper to execute skills as slash commands.
5
+ """
6
+
7
+ from . import BaseCommand, CommandType
8
+
9
+
10
+ class SkillCommand(BaseCommand):
11
+ """
12
+ Command wrapper for executing skills.
13
+
14
+ Skills are dynamically loaded and can be executed like /skill-name.
15
+ This is a PROMPT type command - the skill content is expanded
16
+ and sent to the LLM.
17
+ """
18
+
19
+ name: str = "" # Set dynamically
20
+ description: str = "" # Set dynamically
21
+ usage: str = "" # Set dynamically
22
+ command_type: CommandType = CommandType.PROMPT
23
+ is_skill: bool = True
24
+
25
+ def __init__(self, output, agent=None, skill=None):
26
+ """
27
+ Initialize the skill command.
28
+
29
+ Args:
30
+ output: OutputAdapter instance
31
+ agent: Optional agent instance
32
+ skill: Skill object to wrap
33
+ """
34
+ super().__init__(output, agent)
35
+ self.skill = skill
36
+
37
+ if skill:
38
+ self.name = skill.name
39
+ self.description = skill.description
40
+ self.usage = f"/{skill.name}"
41
+
42
+ async def execute(self, args: str) -> None:
43
+ """
44
+ Execute is not used for PROMPT commands.
45
+ The get_prompt method is used instead.
46
+ """
47
+ pass
48
+
49
+ async def get_prompt(self, args: str) -> str:
50
+ """
51
+ Get the expanded prompt for this skill.
52
+
53
+ Args:
54
+ args: Command arguments (usually ignored for skills)
55
+
56
+ Returns:
57
+ Expanded prompt string containing skill instructions
58
+ """
59
+ if not self.skill:
60
+ return f"Error: Skill not found"
61
+
62
+ # Build the skill activation message
63
+ header = f'<command-message>The "{self.skill.name}" skill is loading</command-message>'
64
+ skill_content = self.skill.get_prompt()
65
+
66
+ return f"{header}\n\n{skill_content}"
67
+
68
+
69
+ def create_skill_command(skill) -> type:
70
+ """
71
+ Dynamically create a SkillCommand class for a specific skill.
72
+
73
+ Args:
74
+ skill: Skill object
75
+
76
+ Returns:
77
+ SkillCommand subclass configured for the skill
78
+ """
79
+
80
+ class DynamicSkillCommand(SkillCommand):
81
+ name = skill.name
82
+ description = skill.description
83
+ usage = f"/{skill.name}"
84
+
85
+ def __init__(self, output, agent=None):
86
+ super().__init__(output, agent, skill=skill)
87
+
88
+ DynamicSkillCommand.__name__ = f"{skill.name.replace('-', '_').title()}SkillCommand"
89
+ return DynamicSkillCommand
@@ -4,112 +4,87 @@
4
4
  Status command - Show system status and information
5
5
  """
6
6
 
7
- from rich.panel import Panel
8
- from rich.table import Table
9
- from rich.text import Text
10
7
  import sys
11
8
  import platform
12
9
  from datetime import datetime
13
- from minion_code.commands import BaseCommand
10
+ from minion_code.commands import BaseCommand, CommandType
14
11
 
15
12
 
16
13
  class StatusCommand(BaseCommand):
17
14
  """Show system status and information."""
18
-
15
+
19
16
  name = "status"
20
17
  description = "Show system status, agent info, and statistics"
21
18
  usage = "/status"
22
19
  aliases = ["info", "stat"]
23
-
20
+ command_type = CommandType.LOCAL
21
+
24
22
  async def execute(self, args: str) -> None:
25
23
  """Execute the status command."""
26
- # Create status table
27
- status_table = Table(
28
- title="📊 System Status",
29
- show_header=True,
30
- header_style="bold blue"
31
- )
32
- status_table.add_column("Component", style="cyan", no_wrap=True)
33
- status_table.add_column("Status", style="white")
34
- status_table.add_column("Details", style="yellow")
35
-
24
+ # Prepare table data
25
+ headers = ["Component", "Status", "Details"]
26
+ rows = []
27
+
36
28
  # System info
37
- status_table.add_row(
38
- "System",
39
- "✅ Running",
40
- f"{platform.system()} {platform.release()}"
41
- )
42
-
43
- status_table.add_row(
44
- "Python",
45
- "✅ Active",
46
- f"{sys.version.split()[0]}"
29
+ rows.append(
30
+ ["System", "✅ Running", f"{platform.system()} {platform.release()}"]
47
31
  )
48
-
32
+
33
+ rows.append(["Python", "✅ Active", f"{sys.version.split()[0]}"])
34
+
49
35
  # Agent info
50
36
  if self.agent:
51
37
  history = self.agent.get_conversation_history()
52
38
  tools_count = len(self.agent.tools) if self.agent.tools else 0
53
-
54
- status_table.add_row(
55
- "Agent",
56
- "✅ Ready",
57
- f"{tools_count} tools loaded"
58
- )
59
-
60
- status_table.add_row(
61
- "Conversation",
62
- "📝 Active" if history else "📝 Empty",
63
- f"{len(history)} messages" if history else "No messages"
39
+
40
+ rows.append(["Agent", "✅ Ready", f"{tools_count} tools loaded"])
41
+
42
+ rows.append(
43
+ [
44
+ "Conversation",
45
+ "📝 Active" if history else "📝 Empty",
46
+ f"{len(history)} messages" if history else "No messages",
47
+ ]
64
48
  )
65
49
  else:
66
- status_table.add_row(
67
- "Agent",
68
- "❌ Not Ready",
69
- "Not initialized"
70
- )
71
-
50
+ rows.append(["Agent", "❌ Not Ready", "Not initialized"])
51
+
72
52
  # Memory info (basic)
73
53
  try:
74
54
  import psutil
55
+
75
56
  memory = psutil.virtual_memory()
76
- status_table.add_row(
77
- "Memory",
78
- "📊 Monitored",
79
- f"{memory.percent}% used ({memory.available // (1024**3)} GB free)"
57
+ rows.append(
58
+ [
59
+ "Memory",
60
+ "📊 Monitored",
61
+ f"{memory.percent}% used ({memory.available // (1024**3)} GB free)",
62
+ ]
80
63
  )
81
64
  except ImportError:
82
- status_table.add_row(
83
- "Memory",
84
- "❌ Not Available",
85
- "psutil not installed"
86
- )
87
-
88
- self.console.print(status_table)
89
-
65
+ rows.append(["Memory", "❌ Not Available", "psutil not installed"])
66
+
67
+ # Display table
68
+ self.output.table(headers, rows, title="📊 System Status")
69
+
90
70
  # Additional info panel
91
71
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
92
-
93
- info_text = Text()
94
- info_text.append("🕒 Current Time: ", style="bold")
95
- info_text.append(f"{current_time}\n", style="white")
96
-
72
+
73
+ info_lines = [f"🕒 Current Time: {current_time}"]
74
+
97
75
  if self.agent:
98
- info_text.append("🤖 Agent Model: ", style="bold")
99
- info_text.append(f"{getattr(self.agent, 'llm', 'Unknown')}\n", style="white")
100
-
101
- info_text.append("🛠️ Available Tools: ", style="bold")
76
+ info_lines.append(
77
+ f"🤖 Agent Model: {getattr(self.agent, 'llm', 'Unknown')}"
78
+ )
79
+
102
80
  if self.agent.tools:
103
81
  tool_names = [tool.name for tool in self.agent.tools[:5]]
104
82
  if len(self.agent.tools) > 5:
105
83
  tool_names.append(f"... and {len(self.agent.tools) - 5} more")
106
- info_text.append(", ".join(tool_names), style="white")
84
+ info_lines.append(f"🛠️ Available Tools: {', '.join(tool_names)}")
107
85
  else:
108
- info_text.append("None", style="red")
109
-
110
- info_panel = Panel(
111
- info_text,
112
- title="[bold green]Additional Information[/bold green]",
113
- border_style="green"
86
+ info_lines.append("🛠️ Available Tools: None")
87
+
88
+ self.output.panel(
89
+ "\n".join(info_lines), title="Additional Information", border_style="green"
114
90
  )
115
- self.console.print(info_panel)