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,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example Notify Plugin - Demonstrates Custom Workflow Actions with Schema Validation
|
|
3
|
+
|
|
4
|
+
This plugin demonstrates the full pattern for creating custom workflow actions:
|
|
5
|
+
1. Schema definition using JSON Schema
|
|
6
|
+
2. Executor function implementation
|
|
7
|
+
3. Registration via register_workflow_action()
|
|
8
|
+
4. Usage in workflow YAML files
|
|
9
|
+
|
|
10
|
+
This is a reference implementation showing best practices for plugin development.
|
|
11
|
+
|
|
12
|
+
Installation:
|
|
13
|
+
1. Copy this file to ~/.gobby/plugins/example_notify.py
|
|
14
|
+
2. Enable in ~/.gobby/config.yaml:
|
|
15
|
+
hook_extensions:
|
|
16
|
+
plugins:
|
|
17
|
+
enabled: true
|
|
18
|
+
plugins:
|
|
19
|
+
example-notify:
|
|
20
|
+
enabled: true
|
|
21
|
+
config:
|
|
22
|
+
default_channel: "#general"
|
|
23
|
+
log_file: "~/.gobby/logs/metrics.log"
|
|
24
|
+
3. Restart gobby daemon: gobby stop && gobby start
|
|
25
|
+
|
|
26
|
+
Usage in Workflows:
|
|
27
|
+
# HTTP notification example
|
|
28
|
+
- action: plugin:example-notify:http_notify
|
|
29
|
+
url: "https://hooks.slack.com/services/xxx"
|
|
30
|
+
method: "POST"
|
|
31
|
+
payload:
|
|
32
|
+
text: "Build completed: {result}"
|
|
33
|
+
|
|
34
|
+
# Metric logging example
|
|
35
|
+
- action: plugin:example-notify:log_metric
|
|
36
|
+
metric_name: "build_duration"
|
|
37
|
+
value: 42.5
|
|
38
|
+
tags:
|
|
39
|
+
project: "my-app"
|
|
40
|
+
environment: "production"
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import json
|
|
46
|
+
from datetime import UTC, datetime
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
from typing import TYPE_CHECKING, Any
|
|
49
|
+
|
|
50
|
+
from gobby.hooks.plugins import HookPlugin
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from gobby.workflows.actions import ActionContext
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ExampleNotifyPlugin(HookPlugin):
|
|
57
|
+
"""
|
|
58
|
+
Example plugin demonstrating custom workflow actions with schema validation.
|
|
59
|
+
|
|
60
|
+
This plugin provides two actions:
|
|
61
|
+
- http_notify: Send HTTP notifications (mock implementation for example)
|
|
62
|
+
- log_metric: Log metrics to a file
|
|
63
|
+
|
|
64
|
+
Both actions use register_workflow_action() for schema validation.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
name = "example-notify"
|
|
68
|
+
version = "1.0.0"
|
|
69
|
+
description = "Example plugin demonstrating workflow actions with schema validation"
|
|
70
|
+
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
super().__init__()
|
|
73
|
+
# Configuration defaults
|
|
74
|
+
self.default_channel: str = "#general"
|
|
75
|
+
self.log_file: Path = Path("~/.gobby/logs/metrics.log").expanduser()
|
|
76
|
+
self._metrics_logged: int = 0
|
|
77
|
+
self._notifications_sent: int = 0
|
|
78
|
+
|
|
79
|
+
def on_load(self, config: dict[str, Any]) -> None:
|
|
80
|
+
"""Initialize plugin with configuration and register actions."""
|
|
81
|
+
# Load configuration
|
|
82
|
+
self.default_channel = config.get("default_channel", self.default_channel)
|
|
83
|
+
log_file = config.get("log_file", str(self.log_file))
|
|
84
|
+
self.log_file = Path(log_file).expanduser()
|
|
85
|
+
|
|
86
|
+
self.logger.info(
|
|
87
|
+
f"Example Notify plugin loaded: channel={self.default_channel}, "
|
|
88
|
+
f"log_file={self.log_file}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# =====================================================================
|
|
92
|
+
# PATTERN: Register actions with schema validation
|
|
93
|
+
# =====================================================================
|
|
94
|
+
#
|
|
95
|
+
# Use register_workflow_action() when you want input validation.
|
|
96
|
+
# The schema follows JSON Schema format with 'properties' and 'required'.
|
|
97
|
+
#
|
|
98
|
+
# Actions are available in workflows as: plugin:<plugin-name>:<action-type>
|
|
99
|
+
# Example: plugin:example-notify:http_notify
|
|
100
|
+
|
|
101
|
+
# Register http_notify action with full schema
|
|
102
|
+
self.register_workflow_action(
|
|
103
|
+
action_type="http_notify",
|
|
104
|
+
schema=HTTP_NOTIFY_SCHEMA,
|
|
105
|
+
executor_fn=self._execute_http_notify,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Register log_metric action with full schema
|
|
109
|
+
self.register_workflow_action(
|
|
110
|
+
action_type="log_metric",
|
|
111
|
+
schema=LOG_METRIC_SCHEMA,
|
|
112
|
+
executor_fn=self._execute_log_metric,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# =====================================================================
|
|
116
|
+
# ALTERNATIVE: Simple registration without schema
|
|
117
|
+
# =====================================================================
|
|
118
|
+
#
|
|
119
|
+
# Use register_action() for actions that don't need input validation:
|
|
120
|
+
#
|
|
121
|
+
# self.register_action("simple_action", self._execute_simple)
|
|
122
|
+
#
|
|
123
|
+
# This is equivalent to register_workflow_action with an empty schema.
|
|
124
|
+
|
|
125
|
+
def on_unload(self) -> None:
|
|
126
|
+
"""Cleanup and log statistics on plugin unload."""
|
|
127
|
+
self.logger.info(
|
|
128
|
+
f"Example Notify stats: notifications_sent={self._notifications_sent}, "
|
|
129
|
+
f"metrics_logged={self._metrics_logged}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# =========================================================================
|
|
133
|
+
# Action Executors
|
|
134
|
+
# =========================================================================
|
|
135
|
+
#
|
|
136
|
+
# Executor functions must be async and follow this signature:
|
|
137
|
+
#
|
|
138
|
+
# async def executor(
|
|
139
|
+
# context: ActionContext,
|
|
140
|
+
# **kwargs: Any
|
|
141
|
+
# ) -> dict[str, Any] | None
|
|
142
|
+
#
|
|
143
|
+
# - context: ActionContext with session info, variables, workflow state
|
|
144
|
+
# - kwargs: Input parameters from the workflow YAML (validated against schema)
|
|
145
|
+
# - Returns: Dict with results (stored in workflow variables if capture_output set)
|
|
146
|
+
|
|
147
|
+
async def _execute_http_notify(
|
|
148
|
+
self,
|
|
149
|
+
context: ActionContext,
|
|
150
|
+
url: str,
|
|
151
|
+
method: str = "POST",
|
|
152
|
+
payload: dict[str, Any] | None = None,
|
|
153
|
+
headers: dict[str, str] | None = None,
|
|
154
|
+
channel: str | None = None,
|
|
155
|
+
**kwargs: Any,
|
|
156
|
+
) -> dict[str, Any]:
|
|
157
|
+
"""
|
|
158
|
+
Execute HTTP notification action.
|
|
159
|
+
|
|
160
|
+
In a real implementation, this would make an actual HTTP request.
|
|
161
|
+
For this example, we simulate the notification and log it.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
context: Workflow action context
|
|
165
|
+
url: Target URL for the notification
|
|
166
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
167
|
+
payload: Request body (JSON-serializable)
|
|
168
|
+
headers: Additional HTTP headers
|
|
169
|
+
channel: Optional channel override
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dict with notification result
|
|
173
|
+
"""
|
|
174
|
+
effective_channel = channel or self.default_channel
|
|
175
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
176
|
+
|
|
177
|
+
# In a real implementation, you would use aiohttp here:
|
|
178
|
+
#
|
|
179
|
+
# async with aiohttp.ClientSession() as session:
|
|
180
|
+
# async with session.request(method, url, json=payload) as resp:
|
|
181
|
+
# return {"status_code": resp.status, "body": await resp.text()}
|
|
182
|
+
#
|
|
183
|
+
# For this example, we simulate success:
|
|
184
|
+
|
|
185
|
+
self.logger.info(
|
|
186
|
+
f"[SIMULATED] HTTP {method} to {url} | channel={effective_channel} | payload={payload}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self._notifications_sent += 1
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
"success": True,
|
|
193
|
+
"simulated": True,
|
|
194
|
+
"method": method,
|
|
195
|
+
"url": url,
|
|
196
|
+
"channel": effective_channel,
|
|
197
|
+
"timestamp": timestamp,
|
|
198
|
+
"notification_count": self._notifications_sent,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async def _execute_log_metric(
|
|
202
|
+
self,
|
|
203
|
+
context: ActionContext,
|
|
204
|
+
metric_name: str,
|
|
205
|
+
value: int | float,
|
|
206
|
+
tags: dict[str, str] | None = None,
|
|
207
|
+
**kwargs: Any,
|
|
208
|
+
) -> dict[str, Any]:
|
|
209
|
+
"""
|
|
210
|
+
Execute metric logging action.
|
|
211
|
+
|
|
212
|
+
Writes metrics to a log file in JSON Lines format.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
context: Workflow action context
|
|
216
|
+
metric_name: Name of the metric (e.g., "build_duration")
|
|
217
|
+
value: Numeric value of the metric
|
|
218
|
+
tags: Optional key-value tags for the metric
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Dict with logging result
|
|
222
|
+
"""
|
|
223
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
224
|
+
|
|
225
|
+
metric_entry = {
|
|
226
|
+
"timestamp": timestamp,
|
|
227
|
+
"metric": metric_name,
|
|
228
|
+
"value": value,
|
|
229
|
+
"tags": tags or {},
|
|
230
|
+
"session_id": context.session_id if context else None,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Ensure log directory exists
|
|
234
|
+
self.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
235
|
+
|
|
236
|
+
# Append to log file in JSON Lines format
|
|
237
|
+
try:
|
|
238
|
+
with open(self.log_file, "a") as f:
|
|
239
|
+
f.write(json.dumps(metric_entry) + "\n")
|
|
240
|
+
|
|
241
|
+
self._metrics_logged += 1
|
|
242
|
+
|
|
243
|
+
self.logger.debug(f"Logged metric: {metric_name}={value}")
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
"success": True,
|
|
247
|
+
"metric_name": metric_name,
|
|
248
|
+
"value": value,
|
|
249
|
+
"timestamp": timestamp,
|
|
250
|
+
"log_file": str(self.log_file),
|
|
251
|
+
"metrics_logged": self._metrics_logged,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
except OSError as e:
|
|
255
|
+
self.logger.error(f"Failed to write metric: {e}")
|
|
256
|
+
return {
|
|
257
|
+
"success": False,
|
|
258
|
+
"error": str(e),
|
|
259
|
+
"metric_name": metric_name,
|
|
260
|
+
"value": value,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# =============================================================================
|
|
265
|
+
# JSON Schema Definitions
|
|
266
|
+
# =============================================================================
|
|
267
|
+
#
|
|
268
|
+
# Schemas define the expected input parameters for workflow actions.
|
|
269
|
+
# They enable validation before execution and serve as documentation.
|
|
270
|
+
#
|
|
271
|
+
# Schema format follows JSON Schema draft-07 with these commonly used fields:
|
|
272
|
+
# - properties: Object mapping parameter names to their schemas
|
|
273
|
+
# - required: Array of required parameter names
|
|
274
|
+
# - type: "string", "number", "integer", "boolean", "array", "object", "null"
|
|
275
|
+
# - description: Human-readable description of the parameter
|
|
276
|
+
# - default: Default value if not provided
|
|
277
|
+
# - enum: Array of allowed values
|
|
278
|
+
|
|
279
|
+
HTTP_NOTIFY_SCHEMA: dict[str, Any] = {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"description": "Send an HTTP notification to a webhook URL",
|
|
282
|
+
"properties": {
|
|
283
|
+
"url": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"description": "Target URL for the HTTP request (e.g., Slack webhook URL)",
|
|
286
|
+
},
|
|
287
|
+
"method": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"description": "HTTP method to use",
|
|
290
|
+
"enum": ["GET", "POST", "PUT", "DELETE"],
|
|
291
|
+
"default": "POST",
|
|
292
|
+
},
|
|
293
|
+
"payload": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"description": "Request body as JSON object",
|
|
296
|
+
},
|
|
297
|
+
"headers": {
|
|
298
|
+
"type": "object",
|
|
299
|
+
"description": "Additional HTTP headers as key-value pairs",
|
|
300
|
+
},
|
|
301
|
+
"channel": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Override the default notification channel",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
"required": ["url"],
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
LOG_METRIC_SCHEMA: dict[str, Any] = {
|
|
310
|
+
"type": "object",
|
|
311
|
+
"description": "Log a metric value with optional tags",
|
|
312
|
+
"properties": {
|
|
313
|
+
"metric_name": {
|
|
314
|
+
"type": "string",
|
|
315
|
+
"description": "Name of the metric (e.g., 'build_duration', 'test_count')",
|
|
316
|
+
},
|
|
317
|
+
"value": {
|
|
318
|
+
"type": "number",
|
|
319
|
+
"description": "Numeric value of the metric",
|
|
320
|
+
},
|
|
321
|
+
"tags": {
|
|
322
|
+
"type": "object",
|
|
323
|
+
"description": "Key-value tags for metric categorization",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
"required": ["metric_name", "value"],
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# For dynamic discovery, the class must be importable
|
|
331
|
+
__all__ = ["ExampleNotifyPlugin"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""External service integrations for Gobby.
|
|
2
|
+
|
|
3
|
+
This module provides integration classes that delegate to official MCP servers
|
|
4
|
+
for external services like GitHub and Linear.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from gobby.integrations.github import GitHubIntegration
|
|
8
|
+
from gobby.integrations.linear import LinearIntegration
|
|
9
|
+
|
|
10
|
+
__all__ = ["GitHubIntegration", "LinearIntegration"]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""GitHub integration via official GitHub MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides a GitHubIntegration class that delegates to the official
|
|
4
|
+
GitHub MCP server (@modelcontextprotocol/server-github) for all GitHub operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from gobby.mcp_proxy.manager import MCPClientManager
|
|
14
|
+
|
|
15
|
+
__all__ = ["GitHubIntegration"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GitHubIntegration:
|
|
19
|
+
"""Integration with GitHub via the official GitHub MCP server.
|
|
20
|
+
|
|
21
|
+
This class provides a high-level interface for checking GitHub MCP availability
|
|
22
|
+
and generating graceful error messages when the server is unavailable.
|
|
23
|
+
|
|
24
|
+
The integration delegates all actual GitHub operations to the official
|
|
25
|
+
GitHub MCP server, avoiding the need for custom API client code.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
server_name: Name of the GitHub MCP server in configuration.
|
|
29
|
+
mcp_manager: The MCPClientManager instance for server access.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
mcp_manager: MCPClientManager,
|
|
35
|
+
server_name: str = "github",
|
|
36
|
+
cache_ttl_seconds: float = 30.0,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Initialize GitHubIntegration.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
mcp_manager: MCPClientManager instance for accessing MCP servers.
|
|
42
|
+
server_name: Name of the GitHub MCP server. Defaults to "github".
|
|
43
|
+
cache_ttl_seconds: How long to cache availability checks. Defaults to 30s.
|
|
44
|
+
"""
|
|
45
|
+
self.mcp_manager = mcp_manager
|
|
46
|
+
self.server_name = server_name
|
|
47
|
+
self._cache_ttl_seconds = cache_ttl_seconds
|
|
48
|
+
self._cached_available: bool | None = None
|
|
49
|
+
self._cache_timestamp: float | None = None
|
|
50
|
+
|
|
51
|
+
def is_available(self) -> bool:
|
|
52
|
+
"""Check if GitHub MCP server is available.
|
|
53
|
+
|
|
54
|
+
Returns True if:
|
|
55
|
+
- The server is configured (has_server returns True)
|
|
56
|
+
- The server is connected (health state is "connected")
|
|
57
|
+
|
|
58
|
+
Results are cached for cache_ttl_seconds to avoid excessive checks.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if GitHub MCP server is available, False otherwise.
|
|
62
|
+
"""
|
|
63
|
+
# Check cache
|
|
64
|
+
if self._cached_available is not None and self._cache_timestamp is not None:
|
|
65
|
+
age = time.time() - self._cache_timestamp
|
|
66
|
+
if age < self._cache_ttl_seconds:
|
|
67
|
+
return self._cached_available
|
|
68
|
+
|
|
69
|
+
# Check availability
|
|
70
|
+
available = self._check_availability()
|
|
71
|
+
|
|
72
|
+
# Update cache
|
|
73
|
+
self._cached_available = available
|
|
74
|
+
self._cache_timestamp = time.time()
|
|
75
|
+
|
|
76
|
+
return available
|
|
77
|
+
|
|
78
|
+
def _check_availability(self) -> bool:
|
|
79
|
+
"""Perform actual availability check without caching."""
|
|
80
|
+
# Check if server is configured
|
|
81
|
+
if not self.mcp_manager.has_server(self.server_name):
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Check if server is connected
|
|
85
|
+
health = self.mcp_manager.health
|
|
86
|
+
if self.server_name not in health:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
server_health = health[self.server_name]
|
|
90
|
+
# Handle both object with .state attribute and dict with 'state' key
|
|
91
|
+
state = getattr(server_health, "state", None)
|
|
92
|
+
if state is None and isinstance(server_health, dict):
|
|
93
|
+
state = server_health.get("state")
|
|
94
|
+
|
|
95
|
+
return state == "connected"
|
|
96
|
+
|
|
97
|
+
def clear_cache(self) -> None:
|
|
98
|
+
"""Clear the availability cache, forcing next is_available() to check fresh."""
|
|
99
|
+
self._cached_available = None
|
|
100
|
+
self._cache_timestamp = None
|
|
101
|
+
|
|
102
|
+
def get_unavailable_reason(self) -> str | None:
|
|
103
|
+
"""Get a human-readable reason why GitHub MCP is unavailable.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A string explaining why GitHub is unavailable, or None if available.
|
|
107
|
+
"""
|
|
108
|
+
if self.is_available():
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Check if server is configured
|
|
112
|
+
if not self.mcp_manager.has_server(self.server_name):
|
|
113
|
+
return (
|
|
114
|
+
f"GitHub MCP server '{self.server_name}' is not configured. "
|
|
115
|
+
"Add it to your gobby configuration or use `gobby mcp add github`."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Server is configured but not connected
|
|
119
|
+
health = self.mcp_manager.health
|
|
120
|
+
if self.server_name not in health:
|
|
121
|
+
return (
|
|
122
|
+
f"GitHub MCP server '{self.server_name}' has no health status. "
|
|
123
|
+
"The server may not have been started yet."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
server_health = health[self.server_name]
|
|
127
|
+
state = getattr(server_health, "state", None)
|
|
128
|
+
if state is None and isinstance(server_health, dict):
|
|
129
|
+
state = server_health.get("state")
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
f"GitHub MCP server '{self.server_name}' is not connected "
|
|
133
|
+
f"(current state: {state}). "
|
|
134
|
+
"Check your GitHub token and server configuration."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def require_available(self) -> None:
|
|
138
|
+
"""Require that GitHub MCP is available, raising if not.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: If GitHub MCP server is unavailable.
|
|
142
|
+
"""
|
|
143
|
+
if not self.is_available():
|
|
144
|
+
reason = self.get_unavailable_reason()
|
|
145
|
+
raise RuntimeError(f"GitHub integration unavailable: {reason}")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Linear integration via official Linear MCP server.
|
|
2
|
+
|
|
3
|
+
This module provides a LinearIntegration class that delegates to the official
|
|
4
|
+
Linear MCP server for all Linear operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import time
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from gobby.mcp_proxy.manager import MCPClientManager
|
|
14
|
+
|
|
15
|
+
__all__ = ["LinearIntegration"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LinearIntegration:
|
|
19
|
+
"""Integration with Linear via the official Linear MCP server.
|
|
20
|
+
|
|
21
|
+
This class provides a high-level interface for checking Linear MCP availability
|
|
22
|
+
and generating graceful error messages when the server is unavailable.
|
|
23
|
+
|
|
24
|
+
The integration delegates all actual Linear operations to the official
|
|
25
|
+
Linear MCP server, avoiding the need for custom API client code.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
server_name: Name of the Linear MCP server in configuration.
|
|
29
|
+
mcp_manager: The MCPClientManager instance for server access.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
mcp_manager: MCPClientManager,
|
|
35
|
+
server_name: str = "linear",
|
|
36
|
+
cache_ttl_seconds: float = 30.0,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Initialize LinearIntegration.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
mcp_manager: MCPClientManager instance for accessing MCP servers.
|
|
42
|
+
server_name: Name of the Linear MCP server. Defaults to "linear".
|
|
43
|
+
cache_ttl_seconds: How long to cache availability checks. Defaults to 30s.
|
|
44
|
+
"""
|
|
45
|
+
self.mcp_manager = mcp_manager
|
|
46
|
+
self.server_name = server_name
|
|
47
|
+
self._cache_ttl_seconds = cache_ttl_seconds
|
|
48
|
+
self._cached_available: bool | None = None
|
|
49
|
+
self._cache_timestamp: float | None = None
|
|
50
|
+
|
|
51
|
+
def is_available(self) -> bool:
|
|
52
|
+
"""Check if Linear MCP server is available.
|
|
53
|
+
|
|
54
|
+
Returns True if:
|
|
55
|
+
- The server is configured (has_server returns True)
|
|
56
|
+
- The server is connected (health state is "connected")
|
|
57
|
+
|
|
58
|
+
Results are cached for cache_ttl_seconds to avoid excessive checks.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if Linear MCP server is available, False otherwise.
|
|
62
|
+
"""
|
|
63
|
+
# Check cache
|
|
64
|
+
if self._cached_available is not None and self._cache_timestamp is not None:
|
|
65
|
+
age = time.time() - self._cache_timestamp
|
|
66
|
+
if age < self._cache_ttl_seconds:
|
|
67
|
+
return self._cached_available
|
|
68
|
+
|
|
69
|
+
# Check availability
|
|
70
|
+
available = self._check_availability()
|
|
71
|
+
|
|
72
|
+
# Update cache
|
|
73
|
+
self._cached_available = available
|
|
74
|
+
self._cache_timestamp = time.time()
|
|
75
|
+
|
|
76
|
+
return available
|
|
77
|
+
|
|
78
|
+
def _check_availability(self) -> bool:
|
|
79
|
+
"""Perform actual availability check without caching."""
|
|
80
|
+
# Check if server is configured
|
|
81
|
+
if not self.mcp_manager.has_server(self.server_name):
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Check if server is connected
|
|
85
|
+
health = self.mcp_manager.health
|
|
86
|
+
if self.server_name not in health:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
server_health = health[self.server_name]
|
|
90
|
+
# Handle both object with .state attribute and dict with 'state' key
|
|
91
|
+
state = getattr(server_health, "state", None)
|
|
92
|
+
if state is None and isinstance(server_health, dict):
|
|
93
|
+
state = server_health.get("state")
|
|
94
|
+
|
|
95
|
+
return state == "connected"
|
|
96
|
+
|
|
97
|
+
def clear_cache(self) -> None:
|
|
98
|
+
"""Clear the availability cache, forcing next is_available() to check fresh."""
|
|
99
|
+
self._cached_available = None
|
|
100
|
+
self._cache_timestamp = None
|
|
101
|
+
|
|
102
|
+
def get_unavailable_reason(self) -> str | None:
|
|
103
|
+
"""Get a human-readable reason why Linear MCP is unavailable.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A string explaining why Linear is unavailable, or None if available.
|
|
107
|
+
"""
|
|
108
|
+
if self.is_available():
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Check if server is configured
|
|
112
|
+
if not self.mcp_manager.has_server(self.server_name):
|
|
113
|
+
return (
|
|
114
|
+
f"Linear MCP server '{self.server_name}' is not configured. "
|
|
115
|
+
"Add it to your gobby configuration or use `gobby mcp add linear`."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Server is configured but not connected
|
|
119
|
+
health = self.mcp_manager.health
|
|
120
|
+
if self.server_name not in health:
|
|
121
|
+
return (
|
|
122
|
+
f"Linear MCP server '{self.server_name}' has no health status. "
|
|
123
|
+
"The server may not have been started yet."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
server_health = health[self.server_name]
|
|
127
|
+
state = getattr(server_health, "state", None)
|
|
128
|
+
if state is None and isinstance(server_health, dict):
|
|
129
|
+
state = server_health.get("state")
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
f"Linear MCP server '{self.server_name}' is not connected "
|
|
133
|
+
f"(current state: {state}). "
|
|
134
|
+
"Check your Linear API key and server configuration."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def require_available(self) -> None:
|
|
138
|
+
"""Require that Linear MCP is available, raising if not.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: If Linear MCP server is unavailable.
|
|
142
|
+
"""
|
|
143
|
+
if not self.is_available():
|
|
144
|
+
reason = self.get_unavailable_reason()
|
|
145
|
+
raise RuntimeError(f"Linear integration unavailable: {reason}")
|
gobby/llm/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Provider Abstraction for Gobby Client.
|
|
3
|
+
|
|
4
|
+
This module provides interfaces and implementations for different LLM providers
|
|
5
|
+
(Claude, Codex, Gemini, LiteLLM) to make the client CLI-agnostic.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
service = create_llm_service(config)
|
|
9
|
+
provider, model, prompt = service.get_provider_for_feature(config.session_summary)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from gobby.llm.base import AuthMode, LLMProvider
|
|
13
|
+
from gobby.llm.claude import MCPToolResult, ToolCall
|
|
14
|
+
from gobby.llm.claude_executor import ClaudeExecutor
|
|
15
|
+
from gobby.llm.executor import (
|
|
16
|
+
AgentExecutor,
|
|
17
|
+
AgentResult,
|
|
18
|
+
ToolCallRecord,
|
|
19
|
+
ToolHandler,
|
|
20
|
+
ToolResult,
|
|
21
|
+
ToolSchema,
|
|
22
|
+
)
|
|
23
|
+
from gobby.llm.factory import create_llm_service
|
|
24
|
+
from gobby.llm.service import LLMService
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"AgentExecutor",
|
|
28
|
+
"AgentResult",
|
|
29
|
+
"AuthMode",
|
|
30
|
+
"ClaudeExecutor",
|
|
31
|
+
"LLMProvider",
|
|
32
|
+
"LLMService",
|
|
33
|
+
"MCPToolResult",
|
|
34
|
+
"ToolCall",
|
|
35
|
+
"ToolCallRecord",
|
|
36
|
+
"ToolHandler",
|
|
37
|
+
"ToolResult",
|
|
38
|
+
"ToolSchema",
|
|
39
|
+
"create_llm_service",
|
|
40
|
+
]
|