kollabor 0.4.9__py3-none-any.whl → 0.4.15__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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
core/__init__.py
CHANGED
|
@@ -5,7 +5,6 @@ from .config import ConfigManager
|
|
|
5
5
|
from .events import EventBus, Event, EventType, Hook, HookStatus, HookPriority
|
|
6
6
|
from .io import InputHandler, TerminalRenderer
|
|
7
7
|
from .plugins import PluginRegistry
|
|
8
|
-
from .storage import StateManager
|
|
9
8
|
from .models import ConversationMessage
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
@@ -13,6 +12,5 @@ __all__ = [
|
|
|
13
12
|
'EventBus', 'Event', 'EventType', 'Hook', 'HookStatus', 'HookPriority',
|
|
14
13
|
'InputHandler', 'TerminalRenderer',
|
|
15
14
|
'PluginRegistry',
|
|
16
|
-
'StateManager',
|
|
17
15
|
'ConversationMessage'
|
|
18
16
|
]
|
core/application.py
CHANGED
|
@@ -9,37 +9,107 @@ from importlib.metadata import version as get_version, PackageNotFoundError
|
|
|
9
9
|
|
|
10
10
|
from .config import ConfigService
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
def _get_version_from_pyproject() -> str:
|
|
13
|
+
"""Read version from pyproject.toml for development mode."""
|
|
14
|
+
try:
|
|
15
|
+
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
|
|
16
|
+
if pyproject_path.exists():
|
|
17
|
+
content = pyproject_path.read_text()
|
|
18
|
+
for line in content.splitlines():
|
|
19
|
+
if line.startswith("version ="):
|
|
20
|
+
# Extract version from: version = "0.4.10"
|
|
21
|
+
return line.split("=")[1].strip().strip('"').strip("'")
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
return None # Return None if not found
|
|
25
|
+
|
|
26
|
+
def _is_running_from_source() -> bool:
|
|
27
|
+
"""Check if we're running from source (development mode) vs installed package."""
|
|
28
|
+
try:
|
|
29
|
+
# If pyproject.toml exists in parent directory, we're running from source
|
|
30
|
+
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
|
|
31
|
+
return pyproject_path.exists()
|
|
32
|
+
except Exception:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
# Get version: prefer pyproject.toml when running from source, otherwise use installed version
|
|
36
|
+
if _is_running_from_source():
|
|
37
|
+
# Development mode: use pyproject.toml
|
|
38
|
+
__version__ = _get_version_from_pyproject() or "0.0.0"
|
|
39
|
+
else:
|
|
40
|
+
# Production mode: use installed package version
|
|
41
|
+
try:
|
|
42
|
+
__version__ = get_version("kollabor")
|
|
43
|
+
except PackageNotFoundError:
|
|
44
|
+
__version__ = "0.0.0"
|
|
17
45
|
from .events import EventBus
|
|
18
46
|
from .io import InputHandler, TerminalRenderer
|
|
19
47
|
from .io.visual_effects import VisualEffects
|
|
20
|
-
from .llm import LLMService, KollaborConversationLogger,
|
|
48
|
+
from .llm import LLMService, KollaborConversationLogger, MCPIntegration, KollaborPluginSDK
|
|
49
|
+
from .llm.profile_manager import ProfileManager
|
|
50
|
+
from .llm.agent_manager import AgentManager
|
|
21
51
|
from .logging import setup_from_config
|
|
22
52
|
from .plugins import PluginRegistry
|
|
23
|
-
from .
|
|
53
|
+
from .updates import VersionCheckService
|
|
24
54
|
|
|
25
55
|
logger = logging.getLogger(__name__)
|
|
26
56
|
|
|
27
57
|
|
|
28
58
|
class TerminalLLMChat:
|
|
29
59
|
"""Main Kollabor CLI application.
|
|
30
|
-
|
|
60
|
+
|
|
31
61
|
Orchestrates all components including rendering, input handling,
|
|
32
62
|
event processing, and plugin management.
|
|
33
63
|
"""
|
|
34
|
-
|
|
35
|
-
def __init__(
|
|
36
|
-
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
args=None,
|
|
68
|
+
system_prompt_file: str | None = None,
|
|
69
|
+
agent_name: str | None = None,
|
|
70
|
+
profile_name: str | None = None,
|
|
71
|
+
save_profile: bool = False,
|
|
72
|
+
skill_names: list[str] | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Initialize the chat application.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
args: Parsed CLI arguments namespace (includes plugin args).
|
|
78
|
+
system_prompt_file: Optional path to a custom system prompt file
|
|
79
|
+
(overrides all other system prompt sources)
|
|
80
|
+
agent_name: Optional agent name to use (e.g., "lint-editor")
|
|
81
|
+
profile_name: Optional LLM profile name to use (e.g., "claude")
|
|
82
|
+
save_profile: If True, save auto-created profile to global config
|
|
83
|
+
skill_names: Optional list of skill names to load for the agent
|
|
84
|
+
"""
|
|
85
|
+
# Store CLI args for plugins to access
|
|
86
|
+
self.args = args
|
|
87
|
+
|
|
37
88
|
# Get configuration directory using standard resolution
|
|
38
|
-
from .utils.config_utils import
|
|
89
|
+
from .utils.config_utils import (
|
|
90
|
+
get_config_directory,
|
|
91
|
+
ensure_config_directory,
|
|
92
|
+
initialize_system_prompt,
|
|
93
|
+
initialize_config,
|
|
94
|
+
set_cli_system_prompt_file,
|
|
95
|
+
get_project_data_dir,
|
|
96
|
+
get_conversations_dir,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Set CLI system prompt override if provided
|
|
100
|
+
if system_prompt_file:
|
|
101
|
+
set_cli_system_prompt_file(system_prompt_file)
|
|
102
|
+
|
|
103
|
+
# Check if this is first install BEFORE creating directories
|
|
104
|
+
global_config_path = Path.home() / ".kollabor-cli" / "config.json"
|
|
105
|
+
self._is_first_install = not global_config_path.exists()
|
|
39
106
|
|
|
40
107
|
self.config_dir = ensure_config_directory()
|
|
41
108
|
logger.info(f"Using config directory: {self.config_dir}")
|
|
42
109
|
|
|
110
|
+
# Initialize config.json (creates global with profiles, copies to local)
|
|
111
|
+
initialize_config()
|
|
112
|
+
|
|
43
113
|
# Initialize system prompt (copies default.md to config directories)
|
|
44
114
|
initialize_system_prompt()
|
|
45
115
|
|
|
@@ -65,12 +135,55 @@ class TerminalLLMChat:
|
|
|
65
135
|
# Update config file with plugin configurations
|
|
66
136
|
self.config.update_from_plugins()
|
|
67
137
|
|
|
138
|
+
# Initialize profile manager (for LLM endpoint profiles)
|
|
139
|
+
self.profile_manager = ProfileManager(self.config)
|
|
140
|
+
if profile_name:
|
|
141
|
+
# CLI --profile is a one-time override, don't persist active selection
|
|
142
|
+
if not self.profile_manager.set_active_profile(profile_name, persist=False):
|
|
143
|
+
logger.warning(f"Profile '{profile_name}' not found, using default")
|
|
144
|
+
elif save_profile:
|
|
145
|
+
# Save auto-created profile to global config if --save was used
|
|
146
|
+
profile = self.profile_manager.get_profile(profile_name)
|
|
147
|
+
if profile and profile.description == "Auto-created from environment variables":
|
|
148
|
+
self.profile_manager.save_profile_values_to_config(profile)
|
|
149
|
+
logger.info(f"Saved profile '{profile_name}' to global config")
|
|
150
|
+
|
|
151
|
+
# Initialize agent manager (for agent/skill system)
|
|
152
|
+
self.agent_manager = AgentManager(self.config)
|
|
153
|
+
# Load default agent using priority system (CLI > project > global > fallback)
|
|
154
|
+
if not self.agent_manager.load_default_agent(agent_name):
|
|
155
|
+
logger.warning("Failed to load any agent, system may not function correctly")
|
|
156
|
+
|
|
157
|
+
# If agent has a preferred profile, use it (unless profile was explicitly set)
|
|
158
|
+
# Don't persist agent's profile - it's automatic based on agent selection
|
|
159
|
+
if not profile_name:
|
|
160
|
+
agent_profile = self.agent_manager.get_preferred_profile()
|
|
161
|
+
if agent_profile:
|
|
162
|
+
if self.profile_manager.set_active_profile(agent_profile, persist=False):
|
|
163
|
+
logger.info(f"Using agent's preferred profile: {agent_profile}")
|
|
164
|
+
|
|
165
|
+
# Load skills if specified (requires an active agent)
|
|
166
|
+
if skill_names:
|
|
167
|
+
if self.agent_manager.get_active_agent():
|
|
168
|
+
for skill_name in skill_names:
|
|
169
|
+
if self.agent_manager.load_skill(skill_name):
|
|
170
|
+
logger.info(f"Loaded skill: {skill_name}")
|
|
171
|
+
else:
|
|
172
|
+
logger.warning(f"Skill '{skill_name}' not found")
|
|
173
|
+
else:
|
|
174
|
+
logger.warning("Cannot load skills without an active agent")
|
|
175
|
+
|
|
68
176
|
# Reconfigure logging now that config system is available
|
|
69
177
|
setup_from_config(self.config.config_manager.config)
|
|
70
|
-
|
|
178
|
+
|
|
179
|
+
# Initialize version check service
|
|
180
|
+
self.version_check_service = VersionCheckService(
|
|
181
|
+
config=self.config,
|
|
182
|
+
current_version=__version__
|
|
183
|
+
)
|
|
184
|
+
|
|
71
185
|
# Initialize core components
|
|
72
|
-
self.
|
|
73
|
-
self.event_bus = EventBus()
|
|
186
|
+
self.event_bus = EventBus(config=self.config.config_manager.config)
|
|
74
187
|
|
|
75
188
|
# Initialize status view registry for flexible status display
|
|
76
189
|
from .io.status_renderer import StatusViewRegistry
|
|
@@ -104,19 +217,33 @@ class TerminalLLMChat:
|
|
|
104
217
|
logger.info("Slash command system initialization completed")
|
|
105
218
|
|
|
106
219
|
# Initialize LLM core service components
|
|
107
|
-
|
|
220
|
+
self.project_data_dir = get_project_data_dir()
|
|
221
|
+
conversations_dir = get_conversations_dir()
|
|
108
222
|
conversations_dir.mkdir(parents=True, exist_ok=True)
|
|
109
223
|
self.conversation_logger = KollaborConversationLogger(conversations_dir)
|
|
110
|
-
self.llm_hook_system = LLMHookSystem(self.event_bus)
|
|
111
224
|
self.mcp_integration = MCPIntegration()
|
|
112
225
|
self.plugin_sdk = KollaborPluginSDK()
|
|
113
226
|
self.llm_service = LLMService(
|
|
114
227
|
config=self.config,
|
|
115
|
-
state_manager=self.state_manager,
|
|
116
228
|
event_bus=self.event_bus,
|
|
117
|
-
renderer=self.renderer
|
|
229
|
+
renderer=self.renderer,
|
|
230
|
+
profile_manager=self.profile_manager,
|
|
231
|
+
agent_manager=self.agent_manager,
|
|
118
232
|
)
|
|
119
|
-
|
|
233
|
+
|
|
234
|
+
# Register hot reload callbacks for config changes
|
|
235
|
+
self.config.register_reload_callback(self._on_config_reload)
|
|
236
|
+
|
|
237
|
+
# Inject active skills as user messages (after LLM service is ready)
|
|
238
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
239
|
+
if active_agent and active_agent.active_skills:
|
|
240
|
+
for skill_name in active_agent.active_skills:
|
|
241
|
+
skill = active_agent.get_skill(skill_name)
|
|
242
|
+
if skill:
|
|
243
|
+
skill_message = f"## Skill: {skill_name}\n\n{skill.content}"
|
|
244
|
+
self.llm_service._add_conversation_message("user", skill_message)
|
|
245
|
+
logger.debug(f"Injected skill as user message: {skill_name}")
|
|
246
|
+
|
|
120
247
|
# Configure renderer with thinking effect and shimmer parameters
|
|
121
248
|
thinking_effect = self.config.get("terminal.thinking_effect", "shimmer")
|
|
122
249
|
shimmer_speed = self.config.get("terminal.shimmer_speed", 3)
|
|
@@ -129,7 +256,7 @@ class TerminalLLMChat:
|
|
|
129
256
|
|
|
130
257
|
# Dynamically instantiate all discovered plugins
|
|
131
258
|
self.plugin_instances = self.plugin_registry.instantiate_plugins(
|
|
132
|
-
self.
|
|
259
|
+
self.event_bus, self.renderer, self.config
|
|
133
260
|
)
|
|
134
261
|
|
|
135
262
|
# Task tracking for race condition prevention
|
|
@@ -143,7 +270,7 @@ class TerminalLLMChat:
|
|
|
143
270
|
async def start(self) -> None:
|
|
144
271
|
"""Start the chat application with guaranteed cleanup."""
|
|
145
272
|
# Display startup messages using config
|
|
146
|
-
self._display_startup_messages()
|
|
273
|
+
await self._display_startup_messages()
|
|
147
274
|
|
|
148
275
|
logger.info("Application starting")
|
|
149
276
|
|
|
@@ -164,7 +291,7 @@ class TerminalLLMChat:
|
|
|
164
291
|
self._startup_complete = True
|
|
165
292
|
logger.info("Application startup complete")
|
|
166
293
|
|
|
167
|
-
# Start main loops with task tracking
|
|
294
|
+
# Start main loops with task tracking (needed for raw mode and input routing)
|
|
168
295
|
self.running = True
|
|
169
296
|
render_task = self.create_background_task(
|
|
170
297
|
self._render_loop(), "render_loop"
|
|
@@ -173,6 +300,12 @@ class TerminalLLMChat:
|
|
|
173
300
|
self.input_handler.start(), "input_handler"
|
|
174
301
|
)
|
|
175
302
|
|
|
303
|
+
# Wait a moment for input handler to initialize (enter raw mode, register hooks)
|
|
304
|
+
await asyncio.sleep(0.1)
|
|
305
|
+
|
|
306
|
+
# Check for first-run and launch setup wizard if needed
|
|
307
|
+
await self._check_first_run_wizard()
|
|
308
|
+
|
|
176
309
|
# Wait for completion
|
|
177
310
|
await asyncio.gather(render_task, input_task)
|
|
178
311
|
|
|
@@ -253,12 +386,15 @@ class TerminalLLMChat:
|
|
|
253
386
|
await self.cleanup()
|
|
254
387
|
# DON'T reset pipe_mode here - let main.py's finally block check it to avoid double cleanup
|
|
255
388
|
|
|
256
|
-
def _display_startup_messages(self) -> None:
|
|
389
|
+
async def _display_startup_messages(self) -> None:
|
|
257
390
|
"""Display startup messages with plugin information."""
|
|
258
391
|
# Display Kollabor banner with version from package metadata
|
|
259
392
|
kollabor_banner = self.renderer.create_kollabor_banner(f"v{__version__}")
|
|
260
393
|
print(kollabor_banner)
|
|
261
394
|
|
|
395
|
+
# Check for updates
|
|
396
|
+
await self._check_for_updates()
|
|
397
|
+
|
|
262
398
|
# LLM Core status
|
|
263
399
|
#print(f"\033[2;35mLLM Core: \033[2;32mActive\033[0m")
|
|
264
400
|
|
|
@@ -286,26 +422,53 @@ class TerminalLLMChat:
|
|
|
286
422
|
print(gradient_msg + bold_enter + gradient_end)
|
|
287
423
|
print()
|
|
288
424
|
|
|
289
|
-
|
|
425
|
+
async def _check_for_updates(self) -> None:
|
|
426
|
+
"""Check for updates and display notification if newer version available."""
|
|
427
|
+
try:
|
|
428
|
+
# Initialize version check service
|
|
429
|
+
await self.version_check_service.initialize()
|
|
430
|
+
|
|
431
|
+
# Check for updates (uses cache if valid)
|
|
432
|
+
release_info = await self.version_check_service.check_for_updates()
|
|
433
|
+
|
|
434
|
+
# Display notification if newer version available
|
|
435
|
+
if release_info:
|
|
436
|
+
update_msg = (
|
|
437
|
+
f"\033[1;33mUpdate available:\033[0m "
|
|
438
|
+
f"v{release_info.version} is now available "
|
|
439
|
+
f"(current: v{__version__})"
|
|
440
|
+
)
|
|
441
|
+
download_msg = f"\033[2;36mDownload:\033[0m {release_info.url}"
|
|
442
|
+
|
|
443
|
+
print(update_msg)
|
|
444
|
+
print(download_msg)
|
|
445
|
+
print() # Spacing before plugin list
|
|
446
|
+
|
|
447
|
+
logger.info(f"Update available: {release_info.version}")
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
# Graceful degradation - log but don't crash startup
|
|
451
|
+
logger.warning(f"Failed to check for updates: {e}")
|
|
452
|
+
|
|
453
|
+
|
|
290
454
|
async def _initialize_llm_core(self) -> None:
|
|
291
455
|
"""Initialize LLM core service components."""
|
|
292
456
|
# Initialize LLM service
|
|
293
457
|
await self.llm_service.initialize()
|
|
294
458
|
logger.info("LLM core service initialized")
|
|
295
|
-
|
|
296
|
-
#
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
459
|
+
|
|
460
|
+
# Update system_commands with llm_service reference (it was None during init)
|
|
461
|
+
if hasattr(self, 'system_commands') and self.system_commands:
|
|
462
|
+
self.system_commands.llm_service = self.llm_service
|
|
463
|
+
logger.debug("Updated system_commands with llm_service reference")
|
|
464
|
+
|
|
300
465
|
# Initialize conversation logger
|
|
301
466
|
await self.conversation_logger.initialize()
|
|
302
467
|
logger.info("Conversation logger initialized")
|
|
303
|
-
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
logger.info(f"Discovered {len(mcp_servers)} MCP servers")
|
|
308
|
-
|
|
468
|
+
|
|
469
|
+
# Note: MCP server discovery is handled in background by llm_service.initialize()
|
|
470
|
+
# to avoid blocking startup (see llm_service._background_mcp_discovery)
|
|
471
|
+
|
|
309
472
|
# Register LLM service hooks for user input processing
|
|
310
473
|
await self.llm_service.register_hooks()
|
|
311
474
|
|
|
@@ -324,14 +487,18 @@ class TerminalLLMChat:
|
|
|
324
487
|
initialized_instances.add(instance_id)
|
|
325
488
|
|
|
326
489
|
if hasattr(plugin_instance, 'initialize'):
|
|
327
|
-
# Pass command registry, input handler, llm_service, and
|
|
490
|
+
# Pass command registry, input handler, llm_service, renderer, and args to plugins
|
|
328
491
|
init_kwargs = {
|
|
492
|
+
'args': self.args, # Parsed CLI arguments including plugin args
|
|
329
493
|
'event_bus': self.event_bus,
|
|
330
494
|
'config': self.config,
|
|
331
495
|
'command_registry': getattr(self.input_handler, 'command_registry', None),
|
|
332
496
|
'input_handler': self.input_handler,
|
|
333
497
|
'renderer': self.renderer,
|
|
334
|
-
'llm_service': self.llm_service
|
|
498
|
+
'llm_service': self.llm_service,
|
|
499
|
+
# Use llm_service's conversation_logger (the one actively logging)
|
|
500
|
+
'conversation_logger': getattr(self.llm_service, 'conversation_logger', self.conversation_logger),
|
|
501
|
+
'conversation_manager': getattr(self.llm_service, 'conversation_manager', None)
|
|
335
502
|
}
|
|
336
503
|
|
|
337
504
|
# Check if initialize method accepts keyword arguments
|
|
@@ -347,6 +514,21 @@ class TerminalLLMChat:
|
|
|
347
514
|
await plugin_instance.register_hooks()
|
|
348
515
|
logger.debug(f"Registered hooks for plugin: {plugin_name}")
|
|
349
516
|
|
|
517
|
+
# Register system commands hooks (for modal command handling)
|
|
518
|
+
if hasattr(self, 'system_commands') and self.system_commands:
|
|
519
|
+
await self.system_commands.register_hooks()
|
|
520
|
+
logger.debug("Registered hooks for system commands")
|
|
521
|
+
|
|
522
|
+
# Set plugin instances on LLM service for system prompt additions
|
|
523
|
+
if hasattr(self, 'llm_service') and self.llm_service:
|
|
524
|
+
self.llm_service.set_plugin_instances(self.plugin_instances)
|
|
525
|
+
|
|
526
|
+
# Check if any plugin wants to add to system prompt and rebuild if needed
|
|
527
|
+
additions = self.llm_service._get_plugin_system_prompt_additions()
|
|
528
|
+
if additions:
|
|
529
|
+
self.llm_service.rebuild_system_prompt()
|
|
530
|
+
logger.info(f"System prompt rebuilt with {len(additions)} plugin additions")
|
|
531
|
+
|
|
350
532
|
def _initialize_slash_commands(self) -> None:
|
|
351
533
|
"""Initialize the slash command system with core commands."""
|
|
352
534
|
logger.info("Starting slash command system initialization...")
|
|
@@ -355,15 +537,19 @@ class TerminalLLMChat:
|
|
|
355
537
|
logger.info("SystemCommandsPlugin imported successfully")
|
|
356
538
|
|
|
357
539
|
# Create and register system commands
|
|
358
|
-
|
|
540
|
+
# Note: llm_service is passed if available, but may be None at this point
|
|
541
|
+
self.system_commands = SystemCommandsPlugin(
|
|
359
542
|
command_registry=self.input_handler.command_registry,
|
|
360
543
|
event_bus=self.event_bus,
|
|
361
|
-
config_manager=self.config
|
|
544
|
+
config_manager=self.config,
|
|
545
|
+
llm_service=getattr(self, 'llm_service', None),
|
|
546
|
+
profile_manager=getattr(self, 'profile_manager', None),
|
|
547
|
+
agent_manager=getattr(self, 'agent_manager', None),
|
|
362
548
|
)
|
|
363
549
|
logger.info("SystemCommandsPlugin instance created")
|
|
364
550
|
|
|
365
551
|
# Register all system commands
|
|
366
|
-
system_commands.register_commands()
|
|
552
|
+
self.system_commands.register_commands()
|
|
367
553
|
logger.info("System commands registration completed")
|
|
368
554
|
|
|
369
555
|
stats = self.input_handler.command_registry.get_registry_stats()
|
|
@@ -375,15 +561,80 @@ class TerminalLLMChat:
|
|
|
375
561
|
import traceback
|
|
376
562
|
logger.error(f"[INFO] Traceback: {traceback.format_exc()}")
|
|
377
563
|
|
|
564
|
+
async def _check_first_run_wizard(self) -> None:
|
|
565
|
+
"""Check if this is first run and launch setup wizard if needed."""
|
|
566
|
+
try:
|
|
567
|
+
# Only show wizard on first install (when global config didn't exist before)
|
|
568
|
+
if not self._is_first_install:
|
|
569
|
+
logger.info("Not a first install, skipping wizard")
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
# Double-check the config flag in case wizard was already run
|
|
573
|
+
setup_completed = self.config.get("application.setup_completed", False)
|
|
574
|
+
if setup_completed:
|
|
575
|
+
logger.info("Setup already completed, skipping wizard")
|
|
576
|
+
return
|
|
577
|
+
|
|
578
|
+
# Check if we have the fullscreen integrator
|
|
579
|
+
if not hasattr(self, 'fullscreen_integrator') or not self.fullscreen_integrator:
|
|
580
|
+
logger.warning("Fullscreen integrator not available, skipping wizard")
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
# Check if setup plugin is registered
|
|
584
|
+
if "setup" not in self.fullscreen_integrator.registered_plugins:
|
|
585
|
+
logger.info("Setup wizard plugin not found, skipping")
|
|
586
|
+
return
|
|
587
|
+
|
|
588
|
+
logger.info("First run detected - launching setup wizard")
|
|
589
|
+
|
|
590
|
+
# Get the setup plugin instance and pass managers
|
|
591
|
+
plugin_class = self.fullscreen_integrator.registered_plugins["setup"]
|
|
592
|
+
plugin_instance = plugin_class()
|
|
593
|
+
plugin_instance.set_managers(self.config, self.profile_manager)
|
|
594
|
+
|
|
595
|
+
# Ensure fullscreen manager is initialized (it's lazily created in command handlers)
|
|
596
|
+
if not self.fullscreen_integrator._fullscreen_manager:
|
|
597
|
+
from core.fullscreen import FullScreenManager
|
|
598
|
+
self.fullscreen_integrator._fullscreen_manager = FullScreenManager(
|
|
599
|
+
self.fullscreen_integrator.event_bus,
|
|
600
|
+
self.fullscreen_integrator.terminal_renderer
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Register and launch
|
|
604
|
+
self.fullscreen_integrator._fullscreen_manager.register_plugin(plugin_instance)
|
|
605
|
+
await self.fullscreen_integrator._fullscreen_manager.launch_plugin("setup")
|
|
606
|
+
|
|
607
|
+
# Check wizard completion status and mark setup as completed
|
|
608
|
+
if plugin_instance.completed:
|
|
609
|
+
logger.info("Setup wizard completed successfully")
|
|
610
|
+
elif plugin_instance.skipped:
|
|
611
|
+
logger.info("Setup wizard skipped by user")
|
|
612
|
+
else:
|
|
613
|
+
logger.info("Setup wizard exited")
|
|
614
|
+
|
|
615
|
+
# Mark setup as completed to avoid showing wizard on next startup
|
|
616
|
+
self.config.set("application.setup_completed", True)
|
|
617
|
+
|
|
618
|
+
except Exception as e:
|
|
619
|
+
logger.error(f"Error launching setup wizard: {e}")
|
|
620
|
+
import traceback
|
|
621
|
+
logger.error(f"Setup wizard traceback: {traceback.format_exc()}")
|
|
622
|
+
# Don't fail startup if wizard fails
|
|
623
|
+
# Mark as completed so we don't retry
|
|
624
|
+
self.config.set("application.setup_completed", True)
|
|
625
|
+
|
|
378
626
|
def _initialize_fullscreen_commands(self) -> None:
|
|
379
627
|
"""Initialize dynamic fullscreen plugin commands."""
|
|
380
628
|
try:
|
|
381
629
|
from core.fullscreen.command_integration import FullScreenCommandIntegrator
|
|
382
630
|
|
|
383
|
-
# Create the integrator
|
|
631
|
+
# Create the integrator with managers for plugins that need them
|
|
384
632
|
self.fullscreen_integrator = FullScreenCommandIntegrator(
|
|
385
633
|
command_registry=self.input_handler.command_registry,
|
|
386
|
-
event_bus=self.event_bus
|
|
634
|
+
event_bus=self.event_bus,
|
|
635
|
+
config=self.config,
|
|
636
|
+
profile_manager=self.profile_manager,
|
|
637
|
+
terminal_renderer=self.renderer
|
|
387
638
|
)
|
|
388
639
|
|
|
389
640
|
# Discover and register all fullscreen plugins
|
|
@@ -406,50 +657,13 @@ class TerminalLLMChat:
|
|
|
406
657
|
logger.info("Render loop starting...")
|
|
407
658
|
while self.running:
|
|
408
659
|
try:
|
|
409
|
-
#
|
|
410
|
-
status_areas = {"A": [], "B": [], "C": []}
|
|
411
|
-
|
|
412
|
-
# Core system status goes to area A
|
|
413
|
-
registry_stats = self.event_bus.get_registry_stats()
|
|
414
|
-
hook_count = registry_stats.get("total_hooks", 0)
|
|
415
|
-
status_areas["A"].append(f"Hooks: {hook_count}")
|
|
416
|
-
|
|
417
|
-
# LLM Core status
|
|
418
|
-
llm_status = self.llm_service.get_status_line()
|
|
419
|
-
if llm_status:
|
|
420
|
-
for area in ["A", "B", "C"]:
|
|
421
|
-
if area in llm_status:
|
|
422
|
-
status_areas[area].extend(llm_status[area])
|
|
423
|
-
|
|
424
|
-
# Collect status from all plugins (organized by area)
|
|
425
|
-
plugin_status_areas = self.plugin_registry.collect_status_lines(self.plugin_instances)
|
|
426
|
-
|
|
427
|
-
# Merge plugin status into our areas
|
|
428
|
-
for area in ["A", "B", "C"]:
|
|
429
|
-
status_areas[area].extend(plugin_status_areas[area])
|
|
430
|
-
|
|
431
|
-
# Handle spinner for processing status across all areas
|
|
432
|
-
for area in ["A", "B", "C"]:
|
|
433
|
-
for i, line in enumerate(status_areas[area]):
|
|
434
|
-
if line.startswith("Processing: Yes"):
|
|
435
|
-
spinner = self.renderer.thinking_animation.get_next_frame()
|
|
436
|
-
status_areas[area][i] = f"Processing: {spinner} Yes"
|
|
437
|
-
elif line.startswith("Processing: ") and "tokens" in line:
|
|
438
|
-
# Extract token count and add spinner
|
|
439
|
-
spinner = self.renderer.thinking_animation.get_next_frame()
|
|
440
|
-
token_part = line.replace("Processing: ", "")
|
|
441
|
-
status_areas[area][i] = f"Processing: {spinner} {token_part}"
|
|
442
|
-
|
|
443
|
-
# Update renderer with status areas
|
|
444
|
-
self.renderer.status_areas = status_areas
|
|
445
|
-
|
|
446
|
-
# Render active area
|
|
660
|
+
# Render active area (status views use content providers)
|
|
447
661
|
await self.renderer.render_active_area()
|
|
448
662
|
|
|
449
663
|
# Use configured FPS for render timing
|
|
450
664
|
render_fps = self.config.get("terminal.render_fps", 20)
|
|
451
665
|
await asyncio.sleep(1.0 / render_fps)
|
|
452
|
-
|
|
666
|
+
|
|
453
667
|
except Exception as e:
|
|
454
668
|
logger.error(f"Render loop error: {e}")
|
|
455
669
|
error_delay = self.config.get("terminal.render_error_delay", 0.1)
|
|
@@ -459,7 +673,12 @@ class TerminalLLMChat:
|
|
|
459
673
|
"""Register default core status views."""
|
|
460
674
|
try:
|
|
461
675
|
from .io.core_status_views import CoreStatusViews
|
|
462
|
-
core_views = CoreStatusViews(
|
|
676
|
+
core_views = CoreStatusViews(
|
|
677
|
+
llm_service=self.llm_service,
|
|
678
|
+
config=self.config,
|
|
679
|
+
profile_manager=getattr(self, 'profile_manager', None),
|
|
680
|
+
agent_manager=getattr(self, 'agent_manager', None),
|
|
681
|
+
)
|
|
463
682
|
core_views.register_all_views(self.status_registry)
|
|
464
683
|
except Exception as e:
|
|
465
684
|
logger.error(f"Failed to register core status views: {e}")
|
|
@@ -529,6 +748,36 @@ class TerminalLLMChat:
|
|
|
529
748
|
|
|
530
749
|
logger.info("Application cleanup complete")
|
|
531
750
|
|
|
751
|
+
def _on_config_reload(self) -> None:
|
|
752
|
+
"""Handle configuration reload (hot reload support).
|
|
753
|
+
|
|
754
|
+
Called when configuration changes via /config modal or file watcher.
|
|
755
|
+
Updates all services with new configuration values.
|
|
756
|
+
"""
|
|
757
|
+
logger.info("Hot reloading application configuration...")
|
|
758
|
+
|
|
759
|
+
# Reload LLM service settings
|
|
760
|
+
if hasattr(self, 'llm_service'):
|
|
761
|
+
self.llm_service.reload_config()
|
|
762
|
+
|
|
763
|
+
# Reload logging level
|
|
764
|
+
from .logging import set_level
|
|
765
|
+
new_level = self.config.get("logging.level", "INFO")
|
|
766
|
+
set_level(new_level)
|
|
767
|
+
|
|
768
|
+
# Reload terminal renderer settings
|
|
769
|
+
if hasattr(self, 'renderer'):
|
|
770
|
+
thinking_effect = self.config.get("terminal.thinking_effect", "shimmer")
|
|
771
|
+
shimmer_speed = self.config.get("terminal.shimmer_speed", 3)
|
|
772
|
+
shimmer_wave_width = self.config.get("terminal.shimmer_wave_width", 4)
|
|
773
|
+
thinking_limit = self.config.get("terminal.thinking_message_limit", 2)
|
|
774
|
+
|
|
775
|
+
self.renderer.set_thinking_effect(thinking_effect)
|
|
776
|
+
self.renderer.configure_shimmer(shimmer_speed, shimmer_wave_width)
|
|
777
|
+
self.renderer.configure_thinking_limit(thinking_limit)
|
|
778
|
+
|
|
779
|
+
logger.info("Hot reload complete")
|
|
780
|
+
|
|
532
781
|
def get_system_status(self):
|
|
533
782
|
"""Get current system status for monitoring and debugging.
|
|
534
783
|
|
|
@@ -556,7 +805,12 @@ class TerminalLLMChat:
|
|
|
556
805
|
await self.conversation_logger.shutdown()
|
|
557
806
|
await self.mcp_integration.shutdown()
|
|
558
807
|
logger.info("LLM core service shutdown complete")
|
|
559
|
-
|
|
808
|
+
|
|
809
|
+
# Shutdown version check service
|
|
810
|
+
if hasattr(self, 'version_check_service'):
|
|
811
|
+
await self.version_check_service.shutdown()
|
|
812
|
+
logger.debug("Version check service shutdown complete")
|
|
813
|
+
|
|
560
814
|
# Shutdown all plugins dynamically
|
|
561
815
|
for plugin_name, plugin_instance in self.plugin_instances.items():
|
|
562
816
|
if hasattr(plugin_instance, 'shutdown'):
|
|
@@ -566,13 +820,14 @@ class TerminalLLMChat:
|
|
|
566
820
|
except Exception as e:
|
|
567
821
|
logger.warning(f"Error shutting down plugin {plugin_name}: {e}")
|
|
568
822
|
|
|
823
|
+
# Clear active area (input box) before restoring terminal
|
|
824
|
+
if not self.pipe_mode:
|
|
825
|
+
self.renderer.clear_active_area(force=True)
|
|
826
|
+
|
|
569
827
|
# Restore terminal
|
|
570
828
|
self.renderer.exit_raw_mode()
|
|
571
829
|
# Only show cursor if not in pipe mode
|
|
572
830
|
if not self.pipe_mode:
|
|
573
831
|
print("\033[?25h") # Show cursor
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
# Close state manager
|
|
577
|
-
self.state_manager.close()
|
|
832
|
+
|
|
578
833
|
logger.info("Application shutdown complete")
|