crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (156) hide show
  1. crackerjack/CLAUDE.md +1005 -0
  2. crackerjack/RULES.md +380 -0
  3. crackerjack/__init__.py +42 -13
  4. crackerjack/__main__.py +227 -299
  5. crackerjack/agents/__init__.py +41 -0
  6. crackerjack/agents/architect_agent.py +281 -0
  7. crackerjack/agents/base.py +170 -0
  8. crackerjack/agents/coordinator.py +512 -0
  9. crackerjack/agents/documentation_agent.py +498 -0
  10. crackerjack/agents/dry_agent.py +388 -0
  11. crackerjack/agents/formatting_agent.py +245 -0
  12. crackerjack/agents/import_optimization_agent.py +281 -0
  13. crackerjack/agents/performance_agent.py +669 -0
  14. crackerjack/agents/proactive_agent.py +104 -0
  15. crackerjack/agents/refactoring_agent.py +788 -0
  16. crackerjack/agents/security_agent.py +529 -0
  17. crackerjack/agents/test_creation_agent.py +657 -0
  18. crackerjack/agents/test_specialist_agent.py +486 -0
  19. crackerjack/agents/tracker.py +212 -0
  20. crackerjack/api.py +560 -0
  21. crackerjack/cli/__init__.py +24 -0
  22. crackerjack/cli/facade.py +104 -0
  23. crackerjack/cli/handlers.py +267 -0
  24. crackerjack/cli/interactive.py +471 -0
  25. crackerjack/cli/options.py +409 -0
  26. crackerjack/cli/utils.py +18 -0
  27. crackerjack/code_cleaner.py +618 -928
  28. crackerjack/config/__init__.py +19 -0
  29. crackerjack/config/hooks.py +218 -0
  30. crackerjack/core/__init__.py +0 -0
  31. crackerjack/core/async_workflow_orchestrator.py +406 -0
  32. crackerjack/core/autofix_coordinator.py +200 -0
  33. crackerjack/core/container.py +104 -0
  34. crackerjack/core/enhanced_container.py +542 -0
  35. crackerjack/core/performance.py +243 -0
  36. crackerjack/core/phase_coordinator.py +585 -0
  37. crackerjack/core/proactive_workflow.py +316 -0
  38. crackerjack/core/session_coordinator.py +289 -0
  39. crackerjack/core/workflow_orchestrator.py +826 -0
  40. crackerjack/dynamic_config.py +94 -103
  41. crackerjack/errors.py +263 -41
  42. crackerjack/executors/__init__.py +11 -0
  43. crackerjack/executors/async_hook_executor.py +431 -0
  44. crackerjack/executors/cached_hook_executor.py +242 -0
  45. crackerjack/executors/hook_executor.py +345 -0
  46. crackerjack/executors/individual_hook_executor.py +669 -0
  47. crackerjack/intelligence/__init__.py +44 -0
  48. crackerjack/intelligence/adaptive_learning.py +751 -0
  49. crackerjack/intelligence/agent_orchestrator.py +551 -0
  50. crackerjack/intelligence/agent_registry.py +414 -0
  51. crackerjack/intelligence/agent_selector.py +502 -0
  52. crackerjack/intelligence/integration.py +290 -0
  53. crackerjack/interactive.py +576 -315
  54. crackerjack/managers/__init__.py +11 -0
  55. crackerjack/managers/async_hook_manager.py +135 -0
  56. crackerjack/managers/hook_manager.py +137 -0
  57. crackerjack/managers/publish_manager.py +433 -0
  58. crackerjack/managers/test_command_builder.py +151 -0
  59. crackerjack/managers/test_executor.py +443 -0
  60. crackerjack/managers/test_manager.py +258 -0
  61. crackerjack/managers/test_manager_backup.py +1124 -0
  62. crackerjack/managers/test_progress.py +114 -0
  63. crackerjack/mcp/__init__.py +0 -0
  64. crackerjack/mcp/cache.py +336 -0
  65. crackerjack/mcp/client_runner.py +104 -0
  66. crackerjack/mcp/context.py +621 -0
  67. crackerjack/mcp/dashboard.py +636 -0
  68. crackerjack/mcp/enhanced_progress_monitor.py +479 -0
  69. crackerjack/mcp/file_monitor.py +336 -0
  70. crackerjack/mcp/progress_components.py +569 -0
  71. crackerjack/mcp/progress_monitor.py +949 -0
  72. crackerjack/mcp/rate_limiter.py +332 -0
  73. crackerjack/mcp/server.py +22 -0
  74. crackerjack/mcp/server_core.py +244 -0
  75. crackerjack/mcp/service_watchdog.py +501 -0
  76. crackerjack/mcp/state.py +395 -0
  77. crackerjack/mcp/task_manager.py +257 -0
  78. crackerjack/mcp/tools/__init__.py +17 -0
  79. crackerjack/mcp/tools/core_tools.py +249 -0
  80. crackerjack/mcp/tools/error_analyzer.py +308 -0
  81. crackerjack/mcp/tools/execution_tools.py +372 -0
  82. crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
  83. crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
  84. crackerjack/mcp/tools/intelligence_tools.py +314 -0
  85. crackerjack/mcp/tools/monitoring_tools.py +502 -0
  86. crackerjack/mcp/tools/proactive_tools.py +384 -0
  87. crackerjack/mcp/tools/progress_tools.py +217 -0
  88. crackerjack/mcp/tools/utility_tools.py +341 -0
  89. crackerjack/mcp/tools/workflow_executor.py +565 -0
  90. crackerjack/mcp/websocket/__init__.py +14 -0
  91. crackerjack/mcp/websocket/app.py +39 -0
  92. crackerjack/mcp/websocket/endpoints.py +559 -0
  93. crackerjack/mcp/websocket/jobs.py +253 -0
  94. crackerjack/mcp/websocket/server.py +116 -0
  95. crackerjack/mcp/websocket/websocket_handler.py +78 -0
  96. crackerjack/mcp/websocket_server.py +10 -0
  97. crackerjack/models/__init__.py +31 -0
  98. crackerjack/models/config.py +93 -0
  99. crackerjack/models/config_adapter.py +230 -0
  100. crackerjack/models/protocols.py +118 -0
  101. crackerjack/models/task.py +154 -0
  102. crackerjack/monitoring/ai_agent_watchdog.py +450 -0
  103. crackerjack/monitoring/regression_prevention.py +638 -0
  104. crackerjack/orchestration/__init__.py +0 -0
  105. crackerjack/orchestration/advanced_orchestrator.py +970 -0
  106. crackerjack/orchestration/coverage_improvement.py +223 -0
  107. crackerjack/orchestration/execution_strategies.py +341 -0
  108. crackerjack/orchestration/test_progress_streamer.py +636 -0
  109. crackerjack/plugins/__init__.py +15 -0
  110. crackerjack/plugins/base.py +200 -0
  111. crackerjack/plugins/hooks.py +246 -0
  112. crackerjack/plugins/loader.py +335 -0
  113. crackerjack/plugins/managers.py +259 -0
  114. crackerjack/py313.py +8 -3
  115. crackerjack/services/__init__.py +22 -0
  116. crackerjack/services/cache.py +314 -0
  117. crackerjack/services/config.py +358 -0
  118. crackerjack/services/config_integrity.py +99 -0
  119. crackerjack/services/contextual_ai_assistant.py +516 -0
  120. crackerjack/services/coverage_ratchet.py +356 -0
  121. crackerjack/services/debug.py +736 -0
  122. crackerjack/services/dependency_monitor.py +617 -0
  123. crackerjack/services/enhanced_filesystem.py +439 -0
  124. crackerjack/services/file_hasher.py +151 -0
  125. crackerjack/services/filesystem.py +421 -0
  126. crackerjack/services/git.py +176 -0
  127. crackerjack/services/health_metrics.py +611 -0
  128. crackerjack/services/initialization.py +873 -0
  129. crackerjack/services/log_manager.py +286 -0
  130. crackerjack/services/logging.py +174 -0
  131. crackerjack/services/metrics.py +578 -0
  132. crackerjack/services/pattern_cache.py +362 -0
  133. crackerjack/services/pattern_detector.py +515 -0
  134. crackerjack/services/performance_benchmarks.py +653 -0
  135. crackerjack/services/security.py +163 -0
  136. crackerjack/services/server_manager.py +234 -0
  137. crackerjack/services/smart_scheduling.py +144 -0
  138. crackerjack/services/tool_version_service.py +61 -0
  139. crackerjack/services/unified_config.py +437 -0
  140. crackerjack/services/version_checker.py +248 -0
  141. crackerjack/slash_commands/__init__.py +14 -0
  142. crackerjack/slash_commands/init.md +122 -0
  143. crackerjack/slash_commands/run.md +163 -0
  144. crackerjack/slash_commands/status.md +127 -0
  145. crackerjack-0.31.7.dist-info/METADATA +742 -0
  146. crackerjack-0.31.7.dist-info/RECORD +149 -0
  147. crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
  148. crackerjack/.gitignore +0 -34
  149. crackerjack/.libcst.codemod.yaml +0 -18
  150. crackerjack/.pdm.toml +0 -1
  151. crackerjack/crackerjack.py +0 -3805
  152. crackerjack/pyproject.toml +0 -286
  153. crackerjack-0.30.3.dist-info/METADATA +0 -1290
  154. crackerjack-0.30.3.dist-info/RECORD +0 -16
  155. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
  156. {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,636 @@
1
+ import time
2
+ from collections import deque
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import aiohttp
8
+ from rich.text import Text
9
+ from textual import work
10
+ from textual.app import App, ComposeResult
11
+ from textual.containers import (
12
+ Container,
13
+ Horizontal,
14
+ )
15
+ from textual.reactive import reactive
16
+ from textual.widgets import (
17
+ Button,
18
+ DataTable,
19
+ Footer,
20
+ Header,
21
+ Log,
22
+ Static,
23
+ TabbedContent,
24
+ TabPane,
25
+ )
26
+
27
+ from crackerjack.services import server_manager
28
+
29
+ from .progress_components import JobDataCollector, TerminalRestorer
30
+
31
+ if TYPE_CHECKING:
32
+ from textual.timer import Timer
33
+
34
+
35
+ class MetricCard(Static):
36
+ DEFAULT_CSS = """
37
+ MetricCard {
38
+ width: 1fr;
39
+ height: 4;
40
+ border: solid $primary;
41
+ padding: 0 1;
42
+ margin: 0 1;
43
+ }
44
+
45
+ MetricCard.critical {
46
+ border: solid $error;
47
+ color: $error;
48
+ }
49
+
50
+ MetricCard.warning {
51
+ border: solid $warning;
52
+ color: $warning;
53
+ }
54
+
55
+ MetricCard.success {
56
+ border: solid $success;
57
+ color: $success;
58
+ }
59
+
60
+ MetricCard.info {
61
+ border: solid $info;
62
+ color: $info;
63
+ }
64
+ """
65
+
66
+ value = reactive(" -- ")
67
+ label = reactive("Metric")
68
+ trend = reactive("")
69
+ status = reactive("")
70
+
71
+ def __init__(
72
+ self,
73
+ label: str,
74
+ value: str = " -- ",
75
+ trend: str = "",
76
+ status: str = "",
77
+ **kwargs,
78
+ ) -> None:
79
+ super().__init__(**kwargs)
80
+ self.label = label
81
+ self.value = value
82
+ self.trend = trend
83
+ self.status = status
84
+
85
+ def render(self) -> Text:
86
+ text = Text()
87
+ text.append(f"{self.label}\n", style="dim")
88
+ text.append(str(self.value), style="bold")
89
+ if self.trend:
90
+ text.append(f" {self.trend}", style="green" if "↑" in self.trend else "red")
91
+ return text
92
+
93
+ def update_metric(self, value: str, trend: str = "", status: str = "") -> None:
94
+ self.value = value
95
+ self.trend = trend
96
+ self.status = status
97
+
98
+ self.remove_class("critical", "warning", "success", "info")
99
+ if status:
100
+ self.add_class(status)
101
+
102
+
103
+ class SystemOverviewWidget(Static):
104
+ DEFAULT_CSS = """
105
+ SystemOverviewWidget {
106
+ height: 8;
107
+ border: solid $primary;
108
+ margin: 1;
109
+ }
110
+ """
111
+
112
+ def compose(self) -> ComposeResult:
113
+ with Container(id="system_overview"):
114
+ with Horizontal(classes="metrics_row"):
115
+ yield MetricCard("CPU", "0 % ", id="cpu_metric")
116
+ yield MetricCard("Memory", "0MB", id="memory_metric")
117
+ yield MetricCard("Active Jobs", "0", id="jobs_metric")
118
+ yield MetricCard("Queue Depth", "0", id="queue_metric")
119
+
120
+ with Horizontal(classes="status_row"):
121
+ yield MetricCard("MCP Server", "Stopped", id="mcp_status")
122
+ yield MetricCard("WebSocket", "Stopped", id="websocket_status")
123
+ yield MetricCard("AI Agent", "Idle", id="agent_status")
124
+ yield MetricCard("Last Run", "Never", id="last_run")
125
+
126
+
127
+ class JobsTableWidget(Static):
128
+ DEFAULT_CSS = """
129
+ JobsTableWidget {
130
+ height: 1fr;
131
+ border: solid $primary;
132
+ margin: 1;
133
+ }
134
+ """
135
+
136
+ def compose(self) -> ComposeResult:
137
+ with Container():
138
+ yield DataTable(id="jobs_table")
139
+
140
+ def on_mount(self) -> None:
141
+ table = self.query_one("#jobs_table", DataTable)
142
+ table.add_columns(
143
+ "Job ID",
144
+ "Status",
145
+ "Stage",
146
+ "Progress",
147
+ "Started",
148
+ "Duration",
149
+ "Issues",
150
+ )
151
+ table.zebra_stripes = True
152
+ table.show_header = True
153
+
154
+
155
+ class LogViewWidget(Static):
156
+ DEFAULT_CSS = """
157
+ LogViewWidget {
158
+ height: 1fr;
159
+ border: solid $primary;
160
+ margin: 1;
161
+ }
162
+ """
163
+
164
+ def compose(self) -> ComposeResult:
165
+ with Container():
166
+ with Horizontal(id="log_controls"):
167
+ yield Button("Clear", id="clear_logs")
168
+ yield Button("Pause", id="pause_logs")
169
+ yield Button("Export", id="export_logs")
170
+ yield Log(id="log_display")
171
+
172
+
173
+ class AIAgentWidget(Static):
174
+ DEFAULT_CSS = """
175
+ AIAgentWidget {
176
+ height: 12;
177
+ border: solid $primary;
178
+ margin: 1;
179
+ }
180
+ """
181
+
182
+ def compose(self) -> ComposeResult:
183
+ with Container():
184
+ with Horizontal(classes="agent_metrics"):
185
+ yield MetricCard("Active Agents", "0", id="active_agents")
186
+ yield MetricCard("Issues Fixed", "0", "↑", id="issues_fixed")
187
+ yield MetricCard("Avg Confidence", "0 % ", id="avg_confidence")
188
+ yield MetricCard("Cache Hits", "0", id="cache_hits")
189
+
190
+ yield DataTable(id="agents_table")
191
+
192
+ def on_mount(self) -> None:
193
+ table = self.query_one("#agents_table", DataTable)
194
+ table.add_columns("Agent", "Type", "Status", "Confidence", "Fixed", "Runtime")
195
+ table.zebra_stripes = True
196
+ table.show_header = True
197
+
198
+
199
+ class PerformanceWidget(Static):
200
+ DEFAULT_CSS = """
201
+ PerformanceWidget {
202
+ height: 10;
203
+ border: solid $primary;
204
+ margin: 1;
205
+ }
206
+ """
207
+
208
+ def __init__(self, **kwargs) -> None:
209
+ super().__init__(**kwargs)
210
+ self.cpu_history: deque[float] = deque(maxlen=50)
211
+ self.memory_history: deque[float] = deque(maxlen=50)
212
+ self.job_history: deque[int] = deque(maxlen=50)
213
+
214
+ def compose(self) -> ComposeResult:
215
+ with Container():
216
+ with Horizontal():
217
+ yield MetricCard("Avg Job Time", "0s", id="avg_job_time")
218
+ yield MetricCard("Success Rate", "100 % ", id="success_rate")
219
+ yield MetricCard("Error Rate", "0 % ", id="error_rate")
220
+ yield MetricCard("Throughput", "0 j / h", id="throughput")
221
+
222
+ yield Static(id="performance_chart")
223
+
224
+ def update_performance_data(self, cpu: float, memory: float, jobs: int) -> None:
225
+ self.cpu_history.append(cpu)
226
+ self.memory_history.append(memory)
227
+ self.job_history.append(jobs)
228
+
229
+ self._render_performance_chart()
230
+
231
+ def _render_performance_chart(self) -> None:
232
+ if not self.cpu_history:
233
+ return
234
+
235
+ max_cpu = max(self.cpu_history) if self.cpu_history else 1
236
+ max_mem = max(self.memory_history) if self.memory_history else 1
237
+
238
+ cpu_line = "".join(self._get_bar_char(val, max_cpu) for val in self.cpu_history)
239
+ mem_line = "".join(
240
+ self._get_bar_char(val, max_mem) for val in self.memory_history
241
+ )
242
+
243
+ chart_text = f"CPU: {cpu_line}\nMEM: {mem_line}"
244
+
245
+ chart = self.query_one("#performance_chart", Static)
246
+ chart.update(chart_text)
247
+
248
+ def _get_bar_char(self, value: float, max_value: float) -> str:
249
+ if max_value == 0:
250
+ return "▁"
251
+ ratio = value / max_value
252
+ if ratio >= 0.875:
253
+ return "█"
254
+ if ratio >= 0.75:
255
+ return "▇"
256
+ if ratio >= 0.625:
257
+ return "▆"
258
+ if ratio >= 0.5:
259
+ return "▅"
260
+ if ratio >= 0.375:
261
+ return "▄"
262
+ if ratio >= 0.25:
263
+ return "▃"
264
+ if ratio >= 0.125:
265
+ return "▂"
266
+ return "▁"
267
+
268
+
269
+ class CrackerjackDashboard(App):
270
+ TITLE = "Crackerjack Dashboard"
271
+ SUB_TITLE = "Comprehensive Project Monitoring"
272
+
273
+ CSS_PATH = None
274
+
275
+ BINDINGS = [
276
+ ("q", "quit", "Quit"),
277
+ ("r", "refresh", "Refresh"),
278
+ ("c", "clear_logs", "Clear Logs"),
279
+ ("p", "pause_logs", "Pause / Resume Logs"),
280
+ ("s", "toggle_servers", "Start / Stop Servers"),
281
+ ("d", "toggle_debug", "Debug Mode"),
282
+ ]
283
+
284
+ DEFAULT_CSS = """
285
+ Screen {
286
+ layout: vertical;
287
+ }
288
+
289
+ Header {
290
+ dock: top;
291
+ height: 3;
292
+ }
293
+
294
+ Footer {
295
+ dock: bottom;
296
+ height: 1;
297
+ }
298
+
299
+ .metrics_row {
300
+ height: 4;
301
+ margin: 0 1;
302
+ }
303
+
304
+ .status_row {
305
+ height: 4;
306
+ margin: 0 1;
307
+ }
308
+ """
309
+
310
+ def __init__(self, **kwargs) -> None:
311
+ super().__init__(**kwargs)
312
+
313
+ self.job_collector = JobDataCollector()
314
+ self.terminal_restorer = TerminalRestorer()
315
+
316
+ self.is_paused = False
317
+ self.debug_mode = False
318
+ self.last_refresh = time.time()
319
+
320
+ self.jobs_data: dict[str, Any] = {}
321
+ self.logs_buffer: list[str] = []
322
+ self.performance_data = {
323
+ "cpu": 0.0,
324
+ "memory": 0.0,
325
+ "jobs": 0,
326
+ "success_rate": 100.0,
327
+ }
328
+
329
+ self.update_timer: Timer | None = None
330
+
331
+ def compose(self) -> ComposeResult:
332
+ yield Header()
333
+
334
+ with TabbedContent(id="main_tabs"):
335
+ with TabPane("Overview", id="overview_tab"):
336
+ yield SystemOverviewWidget(id="system_overview")
337
+ yield JobsTableWidget(id="jobs_widget")
338
+
339
+ with TabPane("AI Agents", id="agents_tab"):
340
+ yield AIAgentWidget(id="ai_agent_widget")
341
+
342
+ with TabPane("Performance", id="performance_tab"):
343
+ yield PerformanceWidget(id="performance_widget")
344
+
345
+ with TabPane("Logs", id="logs_tab"):
346
+ yield LogViewWidget(id="log_widget")
347
+
348
+ yield Footer()
349
+
350
+ def on_mount(self) -> None:
351
+ self.log("Crackerjack Dashboard starting...")
352
+
353
+ self.update_timer = self.set_interval(2.0, self.update_dashboard)
354
+
355
+ self.call_later(self.initial_setup)
356
+
357
+ async def initial_setup(self) -> None:
358
+ try:
359
+ await self._check_server_status()
360
+
361
+ await self._load_jobs_data()
362
+
363
+ self.call_later(self.update_dashboard)
364
+
365
+ self.log("Dashboard initialized successfully")
366
+
367
+ except Exception as e:
368
+ self.log(f"Error during initial setup: {e}")
369
+
370
+ @work(exclusive=True)
371
+ async def update_dashboard(self) -> None:
372
+ if self.is_paused:
373
+ return
374
+
375
+ try:
376
+ current_time = time.time()
377
+
378
+ await self._update_system_metrics()
379
+
380
+ await self._update_jobs_data()
381
+
382
+ await self._update_agent_status()
383
+
384
+ await self._update_performance_metrics()
385
+
386
+ await self._update_logs()
387
+
388
+ self.last_refresh = current_time
389
+
390
+ except Exception as e:
391
+ self.log(f"Error updating dashboard: {e}")
392
+
393
+ async def _check_server_status(self) -> None:
394
+ try:
395
+ mcp_processes = server_manager.find_mcp_server_processes()
396
+ mcp_running = len(mcp_processes) > 0
397
+ mcp_metric = self.query_one("#mcp_status", MetricCard)
398
+ mcp_metric.update_metric(
399
+ "Running" if mcp_running else "Stopped",
400
+ status="success" if mcp_running else "critical",
401
+ )
402
+
403
+ ws_running = await self._check_websocket_server()
404
+ ws_metric = self.query_one("#websocket_status", MetricCard)
405
+ ws_metric.update_metric(
406
+ "Running" if ws_running else "Stopped",
407
+ status="success" if ws_running else "critical",
408
+ )
409
+
410
+ except Exception as e:
411
+ self.log(f"Error checking server status: {e}")
412
+
413
+ async def _check_websocket_server(self) -> bool:
414
+ try:
415
+ timeout = aiohttp.ClientTimeout(total=5.0)
416
+ async with aiohttp.ClientSession(timeout=timeout) as session:
417
+ async with session.get("http://localhost:8675/") as response:
418
+ return response.status == 200
419
+ except Exception:
420
+ return False
421
+
422
+ async def _update_system_metrics(self) -> None:
423
+ try:
424
+ import psutil
425
+
426
+ cpu_percent = psutil.cpu_percent(interval=None)
427
+ memory = psutil.virtual_memory()
428
+ memory_mb = memory.used // (1024 * 1024)
429
+
430
+ cpu_metric = self.query_one("#cpu_metric", MetricCard)
431
+ memory_metric = self.query_one("#memory_metric", MetricCard)
432
+
433
+ cpu_metric.update_metric(f"{cpu_percent: .1f} % ")
434
+ memory_metric.update_metric(f"{memory_mb}MB")
435
+
436
+ self.performance_data["cpu"] = cpu_percent
437
+ self.performance_data["memory"] = memory_mb
438
+
439
+ except ImportError:
440
+ pass
441
+ except Exception as e:
442
+ self.log(f"Error updating system metrics: {e}")
443
+
444
+ async def _load_jobs_data(self) -> None:
445
+ try:
446
+ if await self._check_websocket_server():
447
+ jobs_data = await self._fetch_jobs_from_websocket()
448
+ if jobs_data:
449
+ self.jobs_data.update(jobs_data)
450
+
451
+ file_jobs = await self._collect_jobs_from_filesystem()
452
+ if file_jobs:
453
+ self.jobs_data.update(file_jobs)
454
+
455
+ except Exception as e:
456
+ self.log(f"Error loading jobs data: {e}")
457
+
458
+ async def _fetch_jobs_from_websocket(self) -> dict[str, Any]:
459
+ try:
460
+ timeout = aiohttp.ClientTimeout(total=10.0)
461
+ async with aiohttp.ClientSession(timeout=timeout) as session:
462
+ async with session.get("http://localhost:8675/api/jobs") as response:
463
+ if response.status == 200:
464
+ return await response.json()
465
+ return {}
466
+ except Exception as e:
467
+ self.log(f"Error fetching WebSocket jobs: {e}")
468
+ return {}
469
+
470
+ async def _collect_jobs_from_filesystem(self) -> dict[str, Any]:
471
+ try:
472
+ jobs = {}
473
+
474
+ import tempfile
475
+
476
+ temp_dir = Path(tempfile.gettempdir())
477
+ debug_files = temp_dir.glob("crackerjack-debug-*.log")
478
+ for debug_file in debug_files:
479
+ try:
480
+ if debug_file.stat().st_mtime > (time.time() - 3600):
481
+ debug_file.read_text()
482
+ job_id = debug_file.stem.replace("crackerjack-debug-", "")
483
+ jobs[job_id] = {
484
+ "id": job_id,
485
+ "status": "completed",
486
+ "log_file": str(debug_file),
487
+ "timestamp": debug_file.stat().st_mtime,
488
+ }
489
+ except Exception:
490
+ continue
491
+
492
+ return jobs
493
+
494
+ except Exception as e:
495
+ self.log(f"Error collecting filesystem jobs: {e}")
496
+ return {}
497
+
498
+ async def _update_jobs_data(self) -> None:
499
+ try:
500
+ table = self.query_one("#jobs_table", DataTable)
501
+ table.clear()
502
+
503
+ active_count = len(
504
+ [j for j in self.jobs_data.values() if j.get("status") != "completed"],
505
+ )
506
+ jobs_metric = self.query_one("#jobs_metric", MetricCard)
507
+ jobs_metric.update_metric(str(active_count))
508
+
509
+ for job_id, job_data in sorted(
510
+ self.jobs_data.items(),
511
+ key=lambda x: x[1].get("timestamp", 0),
512
+ reverse=True,
513
+ ):
514
+ status = job_data.get("status", "unknown")
515
+ stage = job_data.get("current_stage", "N/A")
516
+ progress = job_data.get("progress", 0)
517
+ started = datetime.fromtimestamp(job_data.get("timestamp", 0)).strftime(
518
+ "%H:%M:%S",
519
+ )
520
+ duration = self._format_duration(job_data.get("duration", 0))
521
+ issues = job_data.get("issues_found", 0)
522
+
523
+ table.add_row(
524
+ job_id[:8],
525
+ status.title(),
526
+ stage,
527
+ f"{progress} % ",
528
+ started,
529
+ duration,
530
+ str(issues),
531
+ )
532
+
533
+ except Exception as e:
534
+ self.log(f"Error updating jobs data: {e}")
535
+
536
+ async def _update_agent_status(self) -> None:
537
+ try:
538
+ agents_table = self.query_one("#agents_table", DataTable)
539
+ agents_table.clear()
540
+
541
+ active_agents_metric = self.query_one("#active_agents", MetricCard)
542
+ issues_fixed_metric = self.query_one("#issues_fixed", MetricCard)
543
+ avg_confidence_metric = self.query_one("#avg_confidence", MetricCard)
544
+ cache_hits_metric = self.query_one("#cache_hits", MetricCard)
545
+
546
+ active_agents_metric.update_metric("0")
547
+ issues_fixed_metric.update_metric("0")
548
+ avg_confidence_metric.update_metric("0 % ")
549
+ cache_hits_metric.update_metric("0")
550
+
551
+ except Exception as e:
552
+ self.log(f"Error updating agent status: {e}")
553
+
554
+ async def _update_performance_metrics(self) -> None:
555
+ try:
556
+ performance_widget = self.query_one(
557
+ "#performance_widget",
558
+ PerformanceWidget,
559
+ )
560
+
561
+ cpu = self.performance_data.get("cpu", 0)
562
+ memory = self.performance_data.get("memory", 0)
563
+ jobs = len(self.jobs_data)
564
+
565
+ performance_widget.update_performance_data(cpu, memory, jobs)
566
+
567
+ avg_job_time = self.query_one("#avg_job_time", MetricCard)
568
+ success_rate = self.query_one("#success_rate", MetricCard)
569
+
570
+ avg_job_time.update_metric("0s")
571
+ success_rate.update_metric("100 % ")
572
+
573
+ except Exception as e:
574
+ self.log(f"Error updating performance metrics: {e}")
575
+
576
+ async def _update_logs(self) -> None:
577
+ try:
578
+ log_display = self.query_one("#log_display", Log)
579
+
580
+ current_time = datetime.now().strftime("%H:%M:%S")
581
+ if len(self.logs_buffer) % 10 == 0:
582
+ log_display.write_line(f"[{current_time}] Dashboard refresh completed")
583
+
584
+ except Exception as e:
585
+ self.log(f"Error updating logs: {e}")
586
+
587
+ def _format_duration(self, seconds: float) -> str:
588
+ if seconds < 60:
589
+ return f"{seconds: .1f}s"
590
+ if seconds < 3600:
591
+ minutes = seconds / 60
592
+ return f"{minutes: .1f}m"
593
+ hours = seconds / 3600
594
+ return f"{hours: .1f}h"
595
+
596
+ def action_refresh(self) -> None:
597
+ self.call_later(self.update_dashboard)
598
+ self.log("Manual refresh triggered")
599
+
600
+ def action_clear_logs(self) -> None:
601
+ try:
602
+ log_display = self.query_one("#log_display", Log)
603
+ log_display.clear()
604
+ self.logs_buffer.clear()
605
+ self.log("Logs cleared")
606
+ except Exception as e:
607
+ self.log(f"Error clearing logs: {e}")
608
+
609
+ def action_pause_logs(self) -> None:
610
+ self.is_paused = not self.is_paused
611
+ status = "paused" if self.is_paused else "resumed"
612
+ self.log(f"Dashboard updates {status}")
613
+
614
+ def action_toggle_servers(self) -> None:
615
+ self.log("Server toggle not implemented yet")
616
+
617
+ def action_toggle_debug(self) -> None:
618
+ self.debug_mode = not self.debug_mode
619
+ self.log(f"Debug mode {'enabled' if self.debug_mode else 'disabled'}")
620
+
621
+ async def on_exit(self) -> None:
622
+ if self.update_timer:
623
+ self.update_timer.stop()
624
+
625
+ self.terminal_restorer.restore_terminal()
626
+
627
+ self.log("Dashboard shutting down...")
628
+
629
+
630
+ def run_dashboard() -> None:
631
+ app = CrackerjackDashboard()
632
+ app.run()
633
+
634
+
635
+ if __name__ == "__main__":
636
+ run_dashboard()