kollabor 0.4.9__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 (128) hide show
  1. core/__init__.py +18 -0
  2. core/application.py +578 -0
  3. core/cli.py +193 -0
  4. core/commands/__init__.py +43 -0
  5. core/commands/executor.py +277 -0
  6. core/commands/menu_renderer.py +319 -0
  7. core/commands/parser.py +186 -0
  8. core/commands/registry.py +331 -0
  9. core/commands/system_commands.py +479 -0
  10. core/config/__init__.py +7 -0
  11. core/config/llm_task_config.py +110 -0
  12. core/config/loader.py +501 -0
  13. core/config/manager.py +112 -0
  14. core/config/plugin_config_manager.py +346 -0
  15. core/config/plugin_schema.py +424 -0
  16. core/config/service.py +399 -0
  17. core/effects/__init__.py +1 -0
  18. core/events/__init__.py +12 -0
  19. core/events/bus.py +129 -0
  20. core/events/executor.py +154 -0
  21. core/events/models.py +258 -0
  22. core/events/processor.py +176 -0
  23. core/events/registry.py +289 -0
  24. core/fullscreen/__init__.py +19 -0
  25. core/fullscreen/command_integration.py +290 -0
  26. core/fullscreen/components/__init__.py +12 -0
  27. core/fullscreen/components/animation.py +258 -0
  28. core/fullscreen/components/drawing.py +160 -0
  29. core/fullscreen/components/matrix_components.py +177 -0
  30. core/fullscreen/manager.py +302 -0
  31. core/fullscreen/plugin.py +204 -0
  32. core/fullscreen/renderer.py +282 -0
  33. core/fullscreen/session.py +324 -0
  34. core/io/__init__.py +52 -0
  35. core/io/buffer_manager.py +362 -0
  36. core/io/config_status_view.py +272 -0
  37. core/io/core_status_views.py +410 -0
  38. core/io/input_errors.py +313 -0
  39. core/io/input_handler.py +2655 -0
  40. core/io/input_mode_manager.py +402 -0
  41. core/io/key_parser.py +344 -0
  42. core/io/layout.py +587 -0
  43. core/io/message_coordinator.py +204 -0
  44. core/io/message_renderer.py +601 -0
  45. core/io/modal_interaction_handler.py +315 -0
  46. core/io/raw_input_processor.py +946 -0
  47. core/io/status_renderer.py +845 -0
  48. core/io/terminal_renderer.py +586 -0
  49. core/io/terminal_state.py +551 -0
  50. core/io/visual_effects.py +734 -0
  51. core/llm/__init__.py +26 -0
  52. core/llm/api_communication_service.py +863 -0
  53. core/llm/conversation_logger.py +473 -0
  54. core/llm/conversation_manager.py +414 -0
  55. core/llm/file_operations_executor.py +1401 -0
  56. core/llm/hook_system.py +402 -0
  57. core/llm/llm_service.py +1629 -0
  58. core/llm/mcp_integration.py +386 -0
  59. core/llm/message_display_service.py +450 -0
  60. core/llm/model_router.py +214 -0
  61. core/llm/plugin_sdk.py +396 -0
  62. core/llm/response_parser.py +848 -0
  63. core/llm/response_processor.py +364 -0
  64. core/llm/tool_executor.py +520 -0
  65. core/logging/__init__.py +19 -0
  66. core/logging/setup.py +208 -0
  67. core/models/__init__.py +5 -0
  68. core/models/base.py +23 -0
  69. core/plugins/__init__.py +13 -0
  70. core/plugins/collector.py +212 -0
  71. core/plugins/discovery.py +386 -0
  72. core/plugins/factory.py +263 -0
  73. core/plugins/registry.py +152 -0
  74. core/storage/__init__.py +5 -0
  75. core/storage/state_manager.py +84 -0
  76. core/ui/__init__.py +6 -0
  77. core/ui/config_merger.py +176 -0
  78. core/ui/config_widgets.py +369 -0
  79. core/ui/live_modal_renderer.py +276 -0
  80. core/ui/modal_actions.py +162 -0
  81. core/ui/modal_overlay_renderer.py +373 -0
  82. core/ui/modal_renderer.py +591 -0
  83. core/ui/modal_state_manager.py +443 -0
  84. core/ui/widget_integration.py +222 -0
  85. core/ui/widgets/__init__.py +27 -0
  86. core/ui/widgets/base_widget.py +136 -0
  87. core/ui/widgets/checkbox.py +85 -0
  88. core/ui/widgets/dropdown.py +140 -0
  89. core/ui/widgets/label.py +78 -0
  90. core/ui/widgets/slider.py +185 -0
  91. core/ui/widgets/text_input.py +224 -0
  92. core/utils/__init__.py +11 -0
  93. core/utils/config_utils.py +656 -0
  94. core/utils/dict_utils.py +212 -0
  95. core/utils/error_utils.py +275 -0
  96. core/utils/key_reader.py +171 -0
  97. core/utils/plugin_utils.py +267 -0
  98. core/utils/prompt_renderer.py +151 -0
  99. kollabor-0.4.9.dist-info/METADATA +298 -0
  100. kollabor-0.4.9.dist-info/RECORD +128 -0
  101. kollabor-0.4.9.dist-info/WHEEL +5 -0
  102. kollabor-0.4.9.dist-info/entry_points.txt +2 -0
  103. kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
  104. kollabor-0.4.9.dist-info/top_level.txt +4 -0
  105. kollabor_cli_main.py +20 -0
  106. plugins/__init__.py +1 -0
  107. plugins/enhanced_input/__init__.py +18 -0
  108. plugins/enhanced_input/box_renderer.py +103 -0
  109. plugins/enhanced_input/box_styles.py +142 -0
  110. plugins/enhanced_input/color_engine.py +165 -0
  111. plugins/enhanced_input/config.py +150 -0
  112. plugins/enhanced_input/cursor_manager.py +72 -0
  113. plugins/enhanced_input/geometry.py +81 -0
  114. plugins/enhanced_input/state.py +130 -0
  115. plugins/enhanced_input/text_processor.py +115 -0
  116. plugins/enhanced_input_plugin.py +385 -0
  117. plugins/fullscreen/__init__.py +9 -0
  118. plugins/fullscreen/example_plugin.py +327 -0
  119. plugins/fullscreen/matrix_plugin.py +132 -0
  120. plugins/hook_monitoring_plugin.py +1299 -0
  121. plugins/query_enhancer_plugin.py +350 -0
  122. plugins/save_conversation_plugin.py +502 -0
  123. plugins/system_commands_plugin.py +93 -0
  124. plugins/tmux_plugin.py +795 -0
  125. plugins/workflow_enforcement_plugin.py +629 -0
  126. system_prompt/default.md +1286 -0
  127. system_prompt/default_win.md +265 -0
  128. system_prompt/example_with_trender.md +47 -0
@@ -0,0 +1,362 @@
1
+ """Input buffer management for terminal input handling."""
2
+
3
+ import logging
4
+ from typing import List, Tuple
5
+
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class BufferManager:
11
+ """Manages input buffer with validation, history, and editing capabilities.
12
+
13
+ Handles text input buffer operations including character insertion, deletion,
14
+ cursor movement, input validation, and command history.
15
+ """
16
+
17
+ def __init__(self, buffer_limit: int = 1000, history_limit: int = 100):
18
+ """Initialize the buffer manager.
19
+
20
+ Args:
21
+ buffer_limit: Maximum characters allowed in buffer.
22
+ history_limit: Maximum commands to keep in history.
23
+ """
24
+ self._buffer = ""
25
+ self._cursor_pos = 0
26
+ self._buffer_limit = buffer_limit
27
+ self._history: List[str] = []
28
+ self._history_limit = history_limit
29
+ self._history_index = -1
30
+ self._temp_buffer = "" # For history navigation
31
+
32
+ logger.debug(
33
+ "BufferManager initialized with limits: "
34
+ f"buffer={buffer_limit}, history={history_limit}"
35
+ )
36
+
37
+ @property
38
+ def content(self) -> str:
39
+ """Get current buffer content."""
40
+ return self._buffer
41
+
42
+ @property
43
+ def cursor_position(self) -> int:
44
+ """Get current cursor position."""
45
+ return self._cursor_pos
46
+
47
+ @property
48
+ def is_empty(self) -> bool:
49
+ """Check if buffer is empty or only whitespace."""
50
+ return not self._buffer.strip()
51
+
52
+ @property
53
+ def length(self) -> int:
54
+ """Get current buffer length."""
55
+ return len(self._buffer)
56
+
57
+ def insert_char(self, char: str) -> bool:
58
+ """Insert a character at the current cursor position.
59
+
60
+ Args:
61
+ char: Character to insert.
62
+
63
+ Returns:
64
+ True if character was inserted, False if rejected.
65
+ """
66
+ if not self._is_valid_char(char):
67
+ return False
68
+
69
+ if len(self._buffer) >= self._buffer_limit:
70
+ logger.warning(f"Buffer limit reached: {self._buffer_limit}")
71
+ return False
72
+
73
+ # Insert character at cursor position
74
+ self._buffer = (
75
+ self._buffer[: self._cursor_pos]
76
+ + char
77
+ + self._buffer[self._cursor_pos :]
78
+ )
79
+ self._cursor_pos += 1
80
+
81
+ return True
82
+
83
+ def delete_char(self) -> bool:
84
+ """Delete character before cursor (backspace behavior).
85
+
86
+ Returns:
87
+ True if character was deleted, False if at beginning.
88
+ """
89
+ if self._cursor_pos == 0:
90
+ return False
91
+
92
+ self._buffer = (
93
+ self._buffer[: self._cursor_pos - 1] + self._buffer[self._cursor_pos :]
94
+ )
95
+ self._cursor_pos -= 1
96
+ return True
97
+
98
+ def delete_forward(self) -> bool:
99
+ """Delete character after cursor (delete key behavior).
100
+
101
+ Returns:
102
+ True if character was deleted, False if at end.
103
+ """
104
+ if self._cursor_pos >= len(self._buffer):
105
+ return False
106
+
107
+ self._buffer = (
108
+ self._buffer[: self._cursor_pos] + self._buffer[self._cursor_pos + 1 :]
109
+ )
110
+ return True
111
+
112
+ def move_cursor(self, direction: str) -> bool:
113
+ """Move cursor left or right.
114
+
115
+ Args:
116
+ direction: "left" or "right".
117
+
118
+ Returns:
119
+ True if cursor moved, False if at boundary.
120
+ """
121
+ if direction == "left" and self._cursor_pos > 0:
122
+ self._cursor_pos -= 1
123
+ return True
124
+ elif direction == "right" and self._cursor_pos < len(self._buffer):
125
+ self._cursor_pos += 1
126
+ return True
127
+ return False
128
+
129
+ def move_to_start(self) -> None:
130
+ """Move cursor to start of buffer."""
131
+ self._cursor_pos = 0
132
+
133
+ def move_to_end(self) -> None:
134
+ """Move cursor to end of buffer."""
135
+ self._cursor_pos = len(self._buffer)
136
+
137
+ def clear(self) -> None:
138
+ """Clear the buffer and reset cursor."""
139
+ self._buffer = ""
140
+ self._cursor_pos = 0
141
+ self._reset_history_navigation()
142
+
143
+ def get_content_and_clear(self) -> str:
144
+ """Get buffer content and clear it.
145
+
146
+ Returns:
147
+ The buffer content before clearing.
148
+ """
149
+ content = self._buffer
150
+ self.clear()
151
+ return content
152
+
153
+ def add_to_history(self, command: str) -> None:
154
+ """Add a command to history.
155
+
156
+ Args:
157
+ command: Command to add to history.
158
+ """
159
+ if not command.strip():
160
+ return
161
+
162
+ # Remove duplicate if it exists
163
+ if command in self._history:
164
+ self._history.remove(command)
165
+
166
+ # Add to end and maintain limit
167
+ self._history.append(command)
168
+ if len(self._history) > self._history_limit:
169
+ self._history = self._history[-self._history_limit :]
170
+
171
+ self._reset_history_navigation()
172
+ logger.debug(f"Added to history: {command[:50]}...")
173
+
174
+ def navigate_history(self, direction: str) -> bool:
175
+ """Navigate through command history.
176
+
177
+ Args:
178
+ direction: "up" for previous, "down" for next.
179
+
180
+ Returns:
181
+ True if history was navigated, False if at boundary.
182
+ """
183
+ if not self._history:
184
+ return False
185
+
186
+ # Save current buffer on first history navigation
187
+ if self._history_index == -1:
188
+ self._temp_buffer = self._buffer
189
+
190
+ if direction == "up":
191
+ if self._history_index < len(self._history) - 1:
192
+ self._history_index += 1
193
+ self._load_from_history()
194
+ return True
195
+ elif direction == "down":
196
+ if self._history_index > -1:
197
+ self._history_index -= 1
198
+ if self._history_index == -1:
199
+ # Restore temp buffer
200
+ self._buffer = self._temp_buffer
201
+ self._cursor_pos = len(self._buffer)
202
+ else:
203
+ self._load_from_history()
204
+ return True
205
+
206
+ return False
207
+
208
+ def get_display_info(self) -> Tuple[str, int]:
209
+ """Get buffer content and cursor position for display.
210
+
211
+ Returns:
212
+ Tuple of (buffer_content, cursor_position).
213
+ """
214
+ return self._buffer, self._cursor_pos
215
+
216
+ def validate_content(self) -> List[str]:
217
+ """Validate current buffer content.
218
+
219
+ Returns:
220
+ List of validation error messages (empty if valid).
221
+ """
222
+ errors = []
223
+
224
+ # Check for potentially dangerous content
225
+ dangerous_patterns = [
226
+ "rm -rf",
227
+ "sudo rm",
228
+ ":(){ :|:& };:",
229
+ "fork bomb",
230
+ ]
231
+
232
+ for pattern in dangerous_patterns:
233
+ if pattern in self._buffer.lower():
234
+ errors.append(
235
+ f"Potentially dangerous command pattern detected: {pattern}"
236
+ )
237
+
238
+ # Check for very long lines that might cause issues
239
+ if len(self._buffer) > self._buffer_limit * 0.9:
240
+ errors.append(
241
+ f"Input approaching buffer limit "
242
+ f"({len(self._buffer)}/{self._buffer_limit})"
243
+ )
244
+
245
+ return errors
246
+
247
+ def _is_valid_char(self, char: str) -> bool:
248
+ """Check if character is valid for input.
249
+
250
+ Args:
251
+ char: Character to validate.
252
+
253
+ Returns:
254
+ True if character is valid.
255
+ """
256
+ if not char or len(char) != 1:
257
+ return False
258
+
259
+ char_code = ord(char)
260
+
261
+ # Allow printable ASCII characters
262
+ if 32 <= char_code <= 126:
263
+ return True
264
+
265
+ # Allow some special characters like tab
266
+ if char_code in [9]: # Tab
267
+ return True
268
+
269
+ return False
270
+
271
+ def _load_from_history(self) -> None:
272
+ """Load buffer from history at current index."""
273
+ if 0 <= self._history_index < len(self._history):
274
+ # History is stored newest-first, but we navigate oldest-first
275
+ history_item = self._history[-(self._history_index + 1)]
276
+ self._buffer = history_item
277
+ self._cursor_pos = len(self._buffer)
278
+
279
+ def _reset_history_navigation(self) -> None:
280
+ """Reset history navigation state."""
281
+ self._history_index = -1
282
+ self._temp_buffer = ""
283
+
284
+ async def handle_paste(self, paste_content: str) -> bool:
285
+ """Handle pasted content with proper line break and size management.
286
+
287
+ Args:
288
+ paste_content: Content that was pasted.
289
+
290
+ Returns:
291
+ True if paste was successfully handled, False if rejected.
292
+ """
293
+ if not paste_content:
294
+ return False
295
+
296
+ # Check if adding paste content would exceed buffer limit
297
+ if len(self._buffer) + len(paste_content) > self._buffer_limit:
298
+ total_len = len(self._buffer) + len(paste_content)
299
+ logger.warning(
300
+ "Paste rejected: would exceed buffer limit "
301
+ f"({total_len} > {self._buffer_limit})"
302
+ )
303
+ return False
304
+
305
+ # Process paste content (handle line breaks properly)
306
+ processed_content = self._process_paste_content(paste_content)
307
+
308
+ # Insert at current cursor position
309
+ self._buffer = (
310
+ self._buffer[: self._cursor_pos]
311
+ + processed_content
312
+ + self._buffer[self._cursor_pos :]
313
+ )
314
+
315
+ # Move cursor to end of pasted content
316
+ self._cursor_pos += len(processed_content)
317
+
318
+ # Reset history navigation since buffer was modified
319
+ self._reset_history_navigation()
320
+
321
+ logger.debug(
322
+ "Paste handled successfully: " f"{len(processed_content)} chars inserted"
323
+ )
324
+ return True
325
+
326
+ def _process_paste_content(self, content: str) -> str:
327
+ """Process pasted content to handle line breaks and formatting.
328
+
329
+ Args:
330
+ content: Raw pasted content.
331
+
332
+ Returns:
333
+ Processed content suitable for buffer insertion.
334
+ """
335
+ # For now, convert line breaks to spaces to prevent auto-submission
336
+ # This preserves the content while making it safe for single-line input
337
+ processed = content.replace("\n", " ").replace("\r", " ")
338
+
339
+ # Normalize multiple spaces to single spaces
340
+ import re
341
+
342
+ processed = re.sub(r"\s+", " ", processed)
343
+
344
+ # Strip leading/trailing whitespace
345
+ processed = processed.strip()
346
+
347
+ return processed
348
+
349
+ def get_stats(self) -> dict:
350
+ """Get buffer statistics for debugging.
351
+
352
+ Returns:
353
+ Dictionary with buffer statistics.
354
+ """
355
+ return {
356
+ "buffer_length": len(self._buffer),
357
+ "buffer_limit": self._buffer_limit,
358
+ "cursor_position": self._cursor_pos,
359
+ "history_count": len(self._history),
360
+ "history_limit": self._history_limit,
361
+ "history_index": self._history_index,
362
+ }
@@ -0,0 +1,272 @@
1
+ """Configuration status view for displaying config errors and status."""
2
+
3
+ import logging
4
+ from typing import Dict, Any, Optional, List
5
+
6
+ from .status_renderer import StatusViewConfig, StatusMetric
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ConfigStatusView:
12
+ """Status view for configuration monitoring and error reporting."""
13
+
14
+ def __init__(self, config_service, event_bus):
15
+ """Initialize the configuration status view.
16
+
17
+ Args:
18
+ config_service: The configuration service to monitor.
19
+ event_bus: Event bus for receiving notifications.
20
+ """
21
+ self.config_service = config_service
22
+ self.event_bus = event_bus
23
+ self.view_id = "config_status"
24
+ self.priority = 900 # High priority for config errors
25
+
26
+ # Register for config reload notifications
27
+ if hasattr(config_service, "register_reload_callback"):
28
+ config_service.register_reload_callback(self._on_config_reload)
29
+
30
+ logger.debug("ConfigStatusView initialized")
31
+
32
+ def _on_config_reload(self) -> None:
33
+ """Callback when configuration is reloaded."""
34
+ # Trigger status refresh by emitting event
35
+ if self.event_bus and hasattr(self.event_bus, "emit_with_hooks"):
36
+ # Note: This is called from sync context during config reload
37
+ # EventBus doesn't have emit_async method
38
+ # Skip the event emission during sync config reload
39
+ logger.debug("Config reloaded - status refresh skipped (sync context)")
40
+
41
+ def get_status_data(self) -> Dict[str, Any]:
42
+ """Get configuration status data.
43
+
44
+ Returns:
45
+ Dictionary with config status information.
46
+ """
47
+ if not self.config_service:
48
+ return {"error": "No config service", "status": "ERROR"}
49
+
50
+ config_error = self.config_service.get_config_error()
51
+ has_error = self.config_service.has_config_error()
52
+
53
+ using_cache = has_error and self.config_service._cached_config is not None
54
+ status_data = {
55
+ "has_error": has_error,
56
+ "error_message": config_error,
57
+ "status": "ERROR" if has_error else "OK",
58
+ "using_cache": using_cache,
59
+ }
60
+
61
+ # Add validation info
62
+ try:
63
+ validation_result = self.config_service.validate_config()
64
+ status_data.update(
65
+ {
66
+ "valid": validation_result.get("valid", True),
67
+ "warnings": validation_result.get("warnings", []),
68
+ "errors": validation_result.get("errors", []),
69
+ }
70
+ )
71
+ except Exception as e:
72
+ logger.warning(f"Could not validate config: {e}")
73
+ status_data["validation_error"] = str(e)
74
+
75
+ return status_data
76
+
77
+ def format_status_line(self, data: Dict[str, Any]) -> Optional[str]:
78
+ """Format the configuration status line.
79
+
80
+ Args:
81
+ data: Status data dictionary.
82
+
83
+ Returns:
84
+ Formatted status line or None if no status needed.
85
+ """
86
+ if not data:
87
+ return None
88
+
89
+ # Show errors prominently
90
+ if data.get("has_error"):
91
+ error_msg = data.get("error_message", "Unknown config error")
92
+ if data.get("using_cache"):
93
+ return f" Config Error (using cache): {error_msg[:40]}..."
94
+ else:
95
+ return f"Config Error: {error_msg[:50]}..."
96
+
97
+ # Show validation warnings
98
+ warnings = data.get("warnings", [])
99
+ if warnings:
100
+ warning_count = len(warnings)
101
+ if warning_count == 1:
102
+ return f" Config Warning: {warnings[0][:45]}..."
103
+ else:
104
+ return f" Config: {warning_count} warnings"
105
+
106
+ # Show validation errors (different from load errors)
107
+ errors = data.get("errors", [])
108
+ if errors:
109
+ error_count = len(errors)
110
+ if error_count == 1:
111
+ return f"Config Validation: {errors[0][:40]}..."
112
+ else:
113
+ return f"Config: {error_count} validation errors"
114
+
115
+ # Normal status - only show if explicitly requested
116
+ if data.get("show_normal_status", False):
117
+ return "[OK] Config: OK"
118
+
119
+ # No status line needed for normal operation
120
+ return None
121
+
122
+ def should_display(self, data: Dict[str, Any]) -> bool:
123
+ """Determine if this status view should be displayed.
124
+
125
+ Args:
126
+ data: Status data dictionary.
127
+
128
+ Returns:
129
+ True if status should be shown, False otherwise.
130
+ """
131
+ if not data:
132
+ return False
133
+
134
+ # Always show errors and warnings
135
+ return (
136
+ data.get("has_error", False)
137
+ or data.get("warnings", [])
138
+ or data.get("errors", [])
139
+ or data.get("show_normal_status", False)
140
+ )
141
+
142
+ def get_color_scheme(self, data: Dict[str, Any]) -> str:
143
+ """Get color scheme based on config status.
144
+
145
+ Args:
146
+ data: Status data dictionary.
147
+
148
+ Returns:
149
+ Color scheme name.
150
+ """
151
+ if data.get("has_error"):
152
+ return "error"
153
+ elif data.get("warnings") or data.get("errors"):
154
+ return "warning"
155
+ else:
156
+ return "success"
157
+
158
+ def get_priority(self) -> int:
159
+ """Get display priority for this status view.
160
+
161
+ Returns:
162
+ Priority value (higher = more important).
163
+ """
164
+ # High priority for config issues
165
+ return self.priority
166
+
167
+ async def handle_status_event(
168
+ self, event_type: str, event_data: Dict[str, Any]
169
+ ) -> None:
170
+ """Handle status-related events.
171
+
172
+ Args:
173
+ event_type: Type of the event.
174
+ event_data: Event data dictionary.
175
+ """
176
+ if event_type in ["config_reloaded", "config_error", "config_changed"]:
177
+ # Refresh status display
178
+ await self.refresh_status()
179
+
180
+ async def refresh_status(self) -> None:
181
+ """Refresh the status display."""
182
+ if self.event_bus and hasattr(self.event_bus, "emit_with_hooks"):
183
+ from ..events.models import EventType
184
+
185
+ await self.event_bus.emit_with_hooks(
186
+ EventType.STATUS_CONTENT_UPDATE,
187
+ {"view_id": self.view_id, "source": "config_status"},
188
+ "config_status_view",
189
+ )
190
+
191
+ def get_status_view_config(self) -> StatusViewConfig:
192
+ """Get StatusViewConfig for registry registration.
193
+
194
+ Returns:
195
+ StatusViewConfig that can be registered with StatusViewRegistry.
196
+ """
197
+ from .status_renderer import BlockConfig
198
+
199
+ return StatusViewConfig(
200
+ name="Configuration Status",
201
+ plugin_source="core",
202
+ priority=self.priority,
203
+ blocks=[
204
+ BlockConfig(
205
+ width_fraction=1.0,
206
+ content_provider=self._get_config_status_content,
207
+ title="Configuration Status",
208
+ priority=100
209
+ )
210
+ ],
211
+ )
212
+
213
+ def _get_config_status_content(self) -> List[str]:
214
+ """Get configuration status content for status view.
215
+
216
+ Returns:
217
+ List of status content lines.
218
+ """
219
+ status_data = self.get_status_data()
220
+
221
+ # Show errors prominently
222
+ if status_data.get("has_error"):
223
+ error_msg = status_data.get("error_message", "Unknown config error")
224
+ if status_data.get("using_cache"):
225
+ return [f"Config: ERROR (using cache)", f"Error: {error_msg[:60]}"]
226
+ else:
227
+ return [f"Config: ERROR", f"Error: {error_msg[:60]}"]
228
+
229
+ # Show validation warnings
230
+ warnings = status_data.get("warnings", [])
231
+ if warnings:
232
+ lines = [f"Config: {len(warnings)} warning(s)"]
233
+ for warning in warnings[:3]: # Show first 3 warnings
234
+ lines.append(f"- {warning[:60]}")
235
+ return lines
236
+
237
+ # Show validation errors
238
+ errors = status_data.get("errors", [])
239
+ if errors:
240
+ lines = [f"Config: {len(errors)} validation error(s)"]
241
+ for error in errors[:3]: # Show first 3 errors
242
+ lines.append(f"- {error[:60]}")
243
+ return lines
244
+
245
+ # Normal status - show healthy config
246
+ return ["Config: OK", "No errors or warnings"]
247
+
248
+ def _get_config_status_metrics(self) -> List[StatusMetric]:
249
+ """Get configuration status as StatusMetric objects.
250
+
251
+ Returns:
252
+ List of StatusMetric objects for display.
253
+ """
254
+ status_data = self.get_status_data()
255
+
256
+ if not self.should_display(status_data):
257
+ return []
258
+
259
+ status_line = self.format_status_line(status_data)
260
+ if not status_line:
261
+ return []
262
+
263
+ color_scheme = self.get_color_scheme(status_data)
264
+
265
+ return [
266
+ StatusMetric(
267
+ name="config",
268
+ value=status_line,
269
+ color=color_scheme,
270
+ unit="",
271
+ )
272
+ ]