gobby 0.2.5__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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Metrics screen with tool usage statistics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from textual.app import ComposeResult
|
|
9
|
+
from textual.containers import Container, Horizontal, Vertical
|
|
10
|
+
from textual.css.query import NoMatches
|
|
11
|
+
from textual.reactive import reactive
|
|
12
|
+
from textual.widget import Widget
|
|
13
|
+
from textual.widgets import (
|
|
14
|
+
Button,
|
|
15
|
+
DataTable,
|
|
16
|
+
LoadingIndicator,
|
|
17
|
+
Static,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from gobby.tui.api_client import GobbyAPIClient
|
|
21
|
+
from gobby.tui.ws_client import GobbyWebSocketClient
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MetricsSummaryPanel(Widget):
|
|
25
|
+
"""Panel showing metrics summary."""
|
|
26
|
+
|
|
27
|
+
DEFAULT_CSS = """
|
|
28
|
+
MetricsSummaryPanel {
|
|
29
|
+
height: auto;
|
|
30
|
+
padding: 1;
|
|
31
|
+
border: round #45475a;
|
|
32
|
+
margin: 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
MetricsSummaryPanel .summary-title {
|
|
36
|
+
text-style: bold;
|
|
37
|
+
color: #a78bfa;
|
|
38
|
+
padding-bottom: 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
MetricsSummaryPanel .summary-grid {
|
|
42
|
+
layout: grid;
|
|
43
|
+
grid-size: 4 1;
|
|
44
|
+
grid-gutter: 2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
MetricsSummaryPanel .stat-box {
|
|
48
|
+
height: 4;
|
|
49
|
+
border: round #45475a;
|
|
50
|
+
padding: 0 1;
|
|
51
|
+
content-align: center middle;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
MetricsSummaryPanel .stat-value {
|
|
55
|
+
text-style: bold;
|
|
56
|
+
color: #06b6d4;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
MetricsSummaryPanel .stat-label {
|
|
60
|
+
color: #a6adc8;
|
|
61
|
+
}
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
metrics: reactive[dict[str, Any] | None] = reactive(None)
|
|
65
|
+
|
|
66
|
+
def compose(self) -> ComposeResult:
|
|
67
|
+
yield Static("📊 Summary", classes="summary-title")
|
|
68
|
+
|
|
69
|
+
with Horizontal(classes="summary-grid"):
|
|
70
|
+
# Total calls
|
|
71
|
+
with Vertical(classes="stat-box"):
|
|
72
|
+
total = self.metrics.get("total_calls", 0) if self.metrics else 0
|
|
73
|
+
yield Static(str(total), classes="stat-value")
|
|
74
|
+
yield Static("Total Calls", classes="stat-label")
|
|
75
|
+
|
|
76
|
+
# Success rate
|
|
77
|
+
with Vertical(classes="stat-box"):
|
|
78
|
+
rate = self.metrics.get("success_rate", 0) if self.metrics else 0
|
|
79
|
+
yield Static(f"{rate:.1%}", classes="stat-value")
|
|
80
|
+
yield Static("Success Rate", classes="stat-label")
|
|
81
|
+
|
|
82
|
+
# Avg response time
|
|
83
|
+
with Vertical(classes="stat-box"):
|
|
84
|
+
avg_time = self.metrics.get("avg_response_ms", 0) if self.metrics else 0
|
|
85
|
+
yield Static(f"{avg_time:.0f}ms", classes="stat-value")
|
|
86
|
+
yield Static("Avg Response", classes="stat-label")
|
|
87
|
+
|
|
88
|
+
# Active servers
|
|
89
|
+
with Vertical(classes="stat-box"):
|
|
90
|
+
servers = self.metrics.get("active_servers", 0) if self.metrics else 0
|
|
91
|
+
yield Static(str(servers), classes="stat-value")
|
|
92
|
+
yield Static("Active Servers", classes="stat-label")
|
|
93
|
+
|
|
94
|
+
def watch_metrics(self, metrics: dict[str, Any] | None) -> None:
|
|
95
|
+
"""Recompose when metrics change."""
|
|
96
|
+
asyncio.create_task(self.recompose())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class MetricsScreen(Widget):
|
|
100
|
+
"""Metrics screen showing tool usage statistics."""
|
|
101
|
+
|
|
102
|
+
DEFAULT_CSS = """
|
|
103
|
+
MetricsScreen {
|
|
104
|
+
width: 1fr;
|
|
105
|
+
height: 1fr;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
MetricsScreen .screen-header {
|
|
109
|
+
height: auto;
|
|
110
|
+
padding: 1;
|
|
111
|
+
background: #313244;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
MetricsScreen .header-row {
|
|
115
|
+
layout: horizontal;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
MetricsScreen .panel-title {
|
|
119
|
+
text-style: bold;
|
|
120
|
+
color: #a78bfa;
|
|
121
|
+
width: 1fr;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
MetricsScreen .content-area {
|
|
125
|
+
height: 1fr;
|
|
126
|
+
padding: 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
MetricsScreen #tools-table {
|
|
130
|
+
height: 1fr;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
MetricsScreen .loading-container {
|
|
134
|
+
width: 1fr;
|
|
135
|
+
height: 1fr;
|
|
136
|
+
content-align: center middle;
|
|
137
|
+
}
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
loading = reactive(True)
|
|
141
|
+
metrics: reactive[dict[str, Any] | None] = reactive(None)
|
|
142
|
+
tool_metrics: reactive[list[dict[str, Any]]] = reactive(list)
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
api_client: GobbyAPIClient,
|
|
147
|
+
ws_client: GobbyWebSocketClient,
|
|
148
|
+
**kwargs: Any,
|
|
149
|
+
) -> None:
|
|
150
|
+
super().__init__(**kwargs)
|
|
151
|
+
self.api_client = api_client
|
|
152
|
+
self.ws_client = ws_client
|
|
153
|
+
|
|
154
|
+
def compose(self) -> ComposeResult:
|
|
155
|
+
with Vertical(classes="screen-header"):
|
|
156
|
+
with Horizontal(classes="header-row"):
|
|
157
|
+
yield Static("📈 Metrics", classes="panel-title")
|
|
158
|
+
yield Button("Refresh", id="btn-refresh")
|
|
159
|
+
yield Button("Export", id="btn-export")
|
|
160
|
+
|
|
161
|
+
if self.loading:
|
|
162
|
+
with Container(classes="loading-container"):
|
|
163
|
+
yield LoadingIndicator()
|
|
164
|
+
else:
|
|
165
|
+
with Vertical(classes="content-area"):
|
|
166
|
+
yield MetricsSummaryPanel(id="summary-panel")
|
|
167
|
+
yield Static("Tool Usage", classes="panel-title")
|
|
168
|
+
yield DataTable(id="tools-table")
|
|
169
|
+
|
|
170
|
+
async def on_mount(self) -> None:
|
|
171
|
+
"""Load data when mounted."""
|
|
172
|
+
await self.refresh_data()
|
|
173
|
+
|
|
174
|
+
async def refresh_data(self) -> None:
|
|
175
|
+
"""Refresh metrics data."""
|
|
176
|
+
try:
|
|
177
|
+
async with GobbyAPIClient(self.api_client.base_url) as client:
|
|
178
|
+
result = await client.call_tool(
|
|
179
|
+
"gobby-metrics",
|
|
180
|
+
"get_metrics",
|
|
181
|
+
{},
|
|
182
|
+
)
|
|
183
|
+
self.metrics = result.get("summary", {})
|
|
184
|
+
self.tool_metrics = result.get("tools", [])
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
self.notify(f"Failed to load metrics: {e}", severity="error")
|
|
188
|
+
finally:
|
|
189
|
+
self.loading = False
|
|
190
|
+
await self.recompose()
|
|
191
|
+
await self._update_summary()
|
|
192
|
+
await self._setup_table()
|
|
193
|
+
|
|
194
|
+
async def _update_summary(self) -> None:
|
|
195
|
+
"""Update the summary panel."""
|
|
196
|
+
try:
|
|
197
|
+
panel = self.query_one("#summary-panel", MetricsSummaryPanel)
|
|
198
|
+
panel.metrics = self.metrics
|
|
199
|
+
except NoMatches:
|
|
200
|
+
pass # Widget may not be mounted yet
|
|
201
|
+
|
|
202
|
+
async def _setup_table(self) -> None:
|
|
203
|
+
"""Set up and populate the tools table."""
|
|
204
|
+
try:
|
|
205
|
+
table = self.query_one("#tools-table", DataTable)
|
|
206
|
+
table.clear(columns=True)
|
|
207
|
+
table.add_columns("Server", "Tool", "Calls", "Success", "Avg Time")
|
|
208
|
+
table.cursor_type = "row"
|
|
209
|
+
|
|
210
|
+
for tool in self.tool_metrics:
|
|
211
|
+
server = tool.get("server", "unknown")
|
|
212
|
+
name = tool.get("name", "unknown")
|
|
213
|
+
calls = str(tool.get("calls", 0))
|
|
214
|
+
success = f"{tool.get('success_rate', 0):.1%}"
|
|
215
|
+
avg_time = f"{tool.get('avg_ms', 0):.0f}ms"
|
|
216
|
+
|
|
217
|
+
table.add_row(server, name, calls, success, avg_time)
|
|
218
|
+
|
|
219
|
+
except NoMatches:
|
|
220
|
+
pass # Table widget may not be mounted yet
|
|
221
|
+
|
|
222
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
223
|
+
"""Handle button presses."""
|
|
224
|
+
button_id = event.button.id
|
|
225
|
+
|
|
226
|
+
if button_id == "btn-refresh":
|
|
227
|
+
self.loading = True
|
|
228
|
+
await self.refresh_data()
|
|
229
|
+
|
|
230
|
+
elif button_id == "btn-export":
|
|
231
|
+
self.notify("Export coming soon", severity="information")
|