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.
Files changed (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {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
- # Get version from package metadata (always authoritative)
13
- try:
14
- __version__ = get_version("kollabor")
15
- except PackageNotFoundError:
16
- __version__ = "0.4.7" # Fallback for development mode
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, LLMHookSystem, MCPIntegration, KollaborPluginSDK
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 .storage import StateManager
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__(self) -> None:
36
- """Initialize the chat application."""
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 get_config_directory, ensure_config_directory, initialize_system_prompt
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.state_manager = StateManager(str(self.config_dir / "state.db"))
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
- conversations_dir = self.config_dir / "conversations"
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.state_manager, self.event_bus, self.renderer, self.config
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
- # Register LLM hooks
297
- await self.llm_hook_system.register_hooks()
298
- logger.info("LLM hook system registered")
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
- # Discover MCP servers
305
- mcp_servers = await self.mcp_integration.discover_mcp_servers()
306
- if mcp_servers:
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 renderer to plugins that might need it
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
- system_commands = SystemCommandsPlugin(
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
- # Update status areas dynamically from plugins
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(self.llm_service, self.config)
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
- # print("Exiting...")
575
-
576
- # Close state manager
577
- self.state_manager.close()
832
+
578
833
  logger.info("Application shutdown complete")