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,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for resolving project context.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from gobby.config.app import ProjectVerificationConfig
|
|
14
|
+
from gobby.config.features import HooksConfig
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def find_project_root(cwd: Path | None = None) -> Path | None:
|
|
20
|
+
"""
|
|
21
|
+
Find the project root directory by looking for .gobby/project.json.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
cwd: Current working directory to start search from. Defaults to Path.cwd().
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Path to project root if found, None otherwise.
|
|
28
|
+
"""
|
|
29
|
+
if cwd is None:
|
|
30
|
+
cwd = Path.cwd()
|
|
31
|
+
|
|
32
|
+
current = cwd.resolve()
|
|
33
|
+
# Traverse up
|
|
34
|
+
for parent in [current] + list(current.parents):
|
|
35
|
+
project_file = parent / ".gobby" / "project.json"
|
|
36
|
+
if project_file.exists():
|
|
37
|
+
return parent
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_project_context(cwd: Path | None = None) -> dict[str, Any] | None:
|
|
42
|
+
"""
|
|
43
|
+
Get project context from .gobby/project.json.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
cwd: Current working directory to start search from.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary containing project data (id, name, verification, etc.) and 'project_path',
|
|
50
|
+
or None if not found.
|
|
51
|
+
|
|
52
|
+
The returned dict may include:
|
|
53
|
+
- id: Project ID
|
|
54
|
+
- name: Project name
|
|
55
|
+
- created_at: Creation timestamp
|
|
56
|
+
- project_path: Path to project root
|
|
57
|
+
- verification: Optional dict with unit_tests, type_check, lint, integration, custom
|
|
58
|
+
"""
|
|
59
|
+
root = find_project_root(cwd)
|
|
60
|
+
if not root:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
project_file = root / ".gobby" / "project.json"
|
|
64
|
+
try:
|
|
65
|
+
with open(project_file) as f:
|
|
66
|
+
data = json.load(f)
|
|
67
|
+
data["project_path"] = str(root)
|
|
68
|
+
return cast(dict[str, Any], data)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.warning(f"Failed to read project context: {e}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_workflow_project_path(cwd: Path | None = None) -> Path | None:
|
|
75
|
+
"""
|
|
76
|
+
Get the project path for workflow lookup.
|
|
77
|
+
|
|
78
|
+
In a worktree, returns parent_project_path (where workflows live).
|
|
79
|
+
In a main project, returns the project_path.
|
|
80
|
+
|
|
81
|
+
This allows worktree agents to discover workflows from the parent project
|
|
82
|
+
without needing to explicitly pass the project_path parameter.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
cwd: Current working directory to start search from.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Path to use for workflow discovery, or None if no project found.
|
|
89
|
+
"""
|
|
90
|
+
ctx = get_project_context(cwd)
|
|
91
|
+
if not ctx:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# If in a worktree, use parent project for workflows
|
|
95
|
+
parent = ctx.get("parent_project_path")
|
|
96
|
+
if parent:
|
|
97
|
+
return Path(parent)
|
|
98
|
+
|
|
99
|
+
# Otherwise use current project path
|
|
100
|
+
project_path = ctx.get("project_path")
|
|
101
|
+
return Path(project_path) if project_path else None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_project_mcp_dir(project_name: str) -> Path:
|
|
105
|
+
"""
|
|
106
|
+
Get the directory for project-specific MCP configuration.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
project_name: Name of the project.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Path to the project's MCP directory in ~/.gobby/projects/.
|
|
113
|
+
"""
|
|
114
|
+
project_name_safe = project_name.replace(" ", "_").lower()
|
|
115
|
+
return Path.home() / ".gobby" / "projects" / project_name_safe
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_project_mcp_config_path(project_name: str) -> Path:
|
|
119
|
+
"""
|
|
120
|
+
Get the path to the project-specific .mcp.json file.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
project_name: Name of the project.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Path to .mcp.json.
|
|
127
|
+
"""
|
|
128
|
+
return get_project_mcp_dir(project_name) / ".mcp.json"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_verification_config(cwd: Path | None = None) -> ProjectVerificationConfig | None:
|
|
132
|
+
"""
|
|
133
|
+
Get project verification configuration from .gobby/project.json.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
cwd: Current working directory to start search from.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ProjectVerificationConfig if verification section exists, None otherwise.
|
|
140
|
+
"""
|
|
141
|
+
from gobby.config.app import ProjectVerificationConfig
|
|
142
|
+
|
|
143
|
+
context = get_project_context(cwd)
|
|
144
|
+
if not context:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
verification_data = context.get("verification")
|
|
148
|
+
if not verification_data:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
return ProjectVerificationConfig(**verification_data)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning(f"Failed to parse verification config: {e}")
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_hooks_config(cwd: Path | None = None) -> HooksConfig | None:
|
|
159
|
+
"""
|
|
160
|
+
Get git hooks configuration from .gobby/project.json.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
cwd: Current working directory to start search from.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
HooksConfig if hooks section exists, None otherwise.
|
|
167
|
+
"""
|
|
168
|
+
from gobby.config.features import HooksConfig
|
|
169
|
+
|
|
170
|
+
context = get_project_context(cwd)
|
|
171
|
+
if not context:
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
hooks_data = context.get("hooks")
|
|
175
|
+
if not hooks_data:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
return HooksConfig(**hooks_data)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.warning(f"Failed to parse hooks config: {e}")
|
|
182
|
+
return None
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared project initialization utilities.
|
|
3
|
+
|
|
4
|
+
This module provides the core logic for initializing a Gobby project,
|
|
5
|
+
used by both the CLI and the hook system for auto-initialization.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class VerificationCommands:
|
|
21
|
+
"""Auto-detected verification commands for a project."""
|
|
22
|
+
|
|
23
|
+
unit_tests: str | None = None
|
|
24
|
+
type_check: str | None = None
|
|
25
|
+
lint: str | None = None
|
|
26
|
+
integration: str | None = None
|
|
27
|
+
custom: dict[str, str] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
"""Convert to dictionary, excluding None values."""
|
|
31
|
+
result: dict[str, Any] = {}
|
|
32
|
+
if self.unit_tests:
|
|
33
|
+
result["unit_tests"] = self.unit_tests
|
|
34
|
+
if self.type_check:
|
|
35
|
+
result["type_check"] = self.type_check
|
|
36
|
+
if self.lint:
|
|
37
|
+
result["lint"] = self.lint
|
|
38
|
+
if self.integration:
|
|
39
|
+
result["integration"] = self.integration
|
|
40
|
+
if self.custom:
|
|
41
|
+
result["custom"] = self.custom
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class InitResult:
|
|
47
|
+
"""Result of project initialization."""
|
|
48
|
+
|
|
49
|
+
project_id: str
|
|
50
|
+
project_name: str
|
|
51
|
+
project_path: str
|
|
52
|
+
created_at: str
|
|
53
|
+
already_existed: bool
|
|
54
|
+
verification: VerificationCommands | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def detect_verification_commands(cwd: Path) -> VerificationCommands:
|
|
58
|
+
"""
|
|
59
|
+
Auto-detect verification commands based on project files.
|
|
60
|
+
|
|
61
|
+
Checks for pyproject.toml (Python) or package.json (Node.js) and suggests
|
|
62
|
+
appropriate commands for testing, type checking, and linting.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
cwd: Project root directory.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
VerificationCommands with detected commands.
|
|
69
|
+
"""
|
|
70
|
+
verification = VerificationCommands()
|
|
71
|
+
|
|
72
|
+
# Check for Python project (pyproject.toml)
|
|
73
|
+
pyproject_path = cwd / "pyproject.toml"
|
|
74
|
+
if pyproject_path.exists():
|
|
75
|
+
logger.debug("Detected Python project (pyproject.toml)")
|
|
76
|
+
|
|
77
|
+
# Check for tests directory
|
|
78
|
+
tests_dir = cwd / "tests"
|
|
79
|
+
if tests_dir.exists() and tests_dir.is_dir():
|
|
80
|
+
verification.unit_tests = "uv run pytest tests/ -v"
|
|
81
|
+
|
|
82
|
+
# Check for src directory (common pattern)
|
|
83
|
+
src_dir = cwd / "src"
|
|
84
|
+
if src_dir.exists() and src_dir.is_dir():
|
|
85
|
+
verification.type_check = "uv run mypy src/"
|
|
86
|
+
verification.lint = "uv run ruff check src/"
|
|
87
|
+
else:
|
|
88
|
+
# Fall back to current directory
|
|
89
|
+
verification.type_check = "uv run mypy ."
|
|
90
|
+
verification.lint = "uv run ruff check ."
|
|
91
|
+
|
|
92
|
+
return verification
|
|
93
|
+
|
|
94
|
+
# Check for Node.js project (package.json)
|
|
95
|
+
package_json_path = cwd / "package.json"
|
|
96
|
+
if package_json_path.exists():
|
|
97
|
+
logger.debug("Detected Node.js project (package.json)")
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
with open(package_json_path) as f:
|
|
101
|
+
package_data = json.load(f)
|
|
102
|
+
|
|
103
|
+
scripts = package_data.get("scripts", {})
|
|
104
|
+
|
|
105
|
+
# Check for test script
|
|
106
|
+
if "test" in scripts:
|
|
107
|
+
verification.unit_tests = "npm test"
|
|
108
|
+
|
|
109
|
+
# Check for lint script
|
|
110
|
+
if "lint" in scripts:
|
|
111
|
+
verification.lint = "npm run lint"
|
|
112
|
+
|
|
113
|
+
# Check for type-check script (common names)
|
|
114
|
+
for script_name in ["type-check", "typecheck", "types", "tsc"]:
|
|
115
|
+
if script_name in scripts:
|
|
116
|
+
verification.type_check = f"npm run {script_name}"
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
120
|
+
logger.warning(f"Failed to parse package.json: {e}")
|
|
121
|
+
|
|
122
|
+
return verification
|
|
123
|
+
|
|
124
|
+
logger.debug("No recognized project type detected")
|
|
125
|
+
return verification
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def initialize_project(
|
|
129
|
+
cwd: Path | None = None,
|
|
130
|
+
name: str | None = None,
|
|
131
|
+
github_url: str | None = None,
|
|
132
|
+
) -> InitResult:
|
|
133
|
+
"""
|
|
134
|
+
Initialize a Gobby project in the given directory.
|
|
135
|
+
|
|
136
|
+
If the project is already initialized (has .gobby/project.json),
|
|
137
|
+
returns the existing project info. Otherwise creates a new project
|
|
138
|
+
in the database and writes the local project.json file.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
cwd: Directory to initialize. Defaults to current working directory.
|
|
142
|
+
name: Project name. Defaults to directory name if not provided.
|
|
143
|
+
github_url: GitHub URL. Auto-detected from git remote if not provided.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
InitResult with project details and whether it already existed.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
Exception: If project creation fails.
|
|
150
|
+
"""
|
|
151
|
+
from gobby.storage.database import LocalDatabase
|
|
152
|
+
from gobby.storage.migrations import run_migrations
|
|
153
|
+
from gobby.storage.projects import LocalProjectManager
|
|
154
|
+
from gobby.utils.git import get_github_url as detect_github_url
|
|
155
|
+
from gobby.utils.project_context import get_project_context
|
|
156
|
+
|
|
157
|
+
if cwd is None:
|
|
158
|
+
cwd = Path.cwd()
|
|
159
|
+
|
|
160
|
+
cwd = cwd.resolve()
|
|
161
|
+
|
|
162
|
+
# Check if already initialized
|
|
163
|
+
project_context = get_project_context(cwd)
|
|
164
|
+
if project_context and project_context.get("id"):
|
|
165
|
+
logger.debug(f"Project already initialized: {project_context.get('name')}")
|
|
166
|
+
return InitResult(
|
|
167
|
+
project_id=str(project_context["id"]),
|
|
168
|
+
project_name=project_context.get("name", ""),
|
|
169
|
+
project_path=project_context.get("project_path", str(cwd)),
|
|
170
|
+
created_at=project_context.get("created_at", ""),
|
|
171
|
+
already_existed=True,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Auto-detect name from directory if not provided
|
|
175
|
+
if not name:
|
|
176
|
+
name = cwd.name
|
|
177
|
+
|
|
178
|
+
# Auto-detect GitHub URL from git remote if not provided
|
|
179
|
+
if not github_url:
|
|
180
|
+
github_url = detect_github_url(cwd)
|
|
181
|
+
|
|
182
|
+
# Initialize database and run migrations
|
|
183
|
+
db = LocalDatabase()
|
|
184
|
+
run_migrations(db)
|
|
185
|
+
project_manager = LocalProjectManager(db)
|
|
186
|
+
|
|
187
|
+
# Auto-detect verification commands
|
|
188
|
+
verification = detect_verification_commands(cwd)
|
|
189
|
+
|
|
190
|
+
# Check if project with same name exists in database
|
|
191
|
+
existing = project_manager.get_by_name(name)
|
|
192
|
+
if existing:
|
|
193
|
+
# Project exists in DB but no local project.json - write it
|
|
194
|
+
logger.debug(f"Found existing project in database: {name}")
|
|
195
|
+
_write_project_json(cwd, existing.id, existing.name, existing.created_at, verification)
|
|
196
|
+
return InitResult(
|
|
197
|
+
project_id=existing.id,
|
|
198
|
+
project_name=existing.name,
|
|
199
|
+
project_path=str(cwd),
|
|
200
|
+
created_at=existing.created_at,
|
|
201
|
+
already_existed=True,
|
|
202
|
+
verification=verification if verification.to_dict() else None,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Create new project
|
|
206
|
+
logger.debug(f"Creating new project: {name}")
|
|
207
|
+
project = project_manager.create(
|
|
208
|
+
name=name,
|
|
209
|
+
repo_path=str(cwd),
|
|
210
|
+
github_url=github_url,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Write local .gobby/project.json
|
|
214
|
+
_write_project_json(cwd, project.id, project.name, project.created_at, verification)
|
|
215
|
+
|
|
216
|
+
logger.info(f"Initialized project '{name}' in {cwd}")
|
|
217
|
+
|
|
218
|
+
return InitResult(
|
|
219
|
+
project_id=project.id,
|
|
220
|
+
project_name=project.name,
|
|
221
|
+
project_path=str(cwd),
|
|
222
|
+
created_at=project.created_at,
|
|
223
|
+
already_existed=False,
|
|
224
|
+
verification=verification if verification.to_dict() else None,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _write_project_json(
|
|
229
|
+
cwd: Path,
|
|
230
|
+
project_id: str,
|
|
231
|
+
name: str,
|
|
232
|
+
created_at: str,
|
|
233
|
+
verification: VerificationCommands | None = None,
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Write the .gobby/project.json file.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
cwd: Project root directory.
|
|
239
|
+
project_id: Project ID.
|
|
240
|
+
name: Project name.
|
|
241
|
+
created_at: Project creation timestamp.
|
|
242
|
+
verification: Optional verification commands to include.
|
|
243
|
+
"""
|
|
244
|
+
gobby_dir = cwd / ".gobby"
|
|
245
|
+
gobby_dir.mkdir(exist_ok=True)
|
|
246
|
+
|
|
247
|
+
project_file = gobby_dir / "project.json"
|
|
248
|
+
project_data: dict[str, Any] = {
|
|
249
|
+
"id": project_id,
|
|
250
|
+
"name": name,
|
|
251
|
+
"created_at": created_at,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# Add verification config if provided and has commands
|
|
255
|
+
if verification:
|
|
256
|
+
verification_dict = verification.to_dict()
|
|
257
|
+
if verification_dict:
|
|
258
|
+
project_data["verification"] = verification_dict
|
|
259
|
+
|
|
260
|
+
with open(project_file, "w") as f:
|
|
261
|
+
json.dump(project_data, f, indent=2)
|
|
262
|
+
|
|
263
|
+
logger.debug(f"Wrote project.json to {project_file}")
|
gobby/utils/status.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Status message formatting for Gobby daemon.
|
|
3
|
+
|
|
4
|
+
Provides consistent status display across CLI and MCP server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def fetch_rich_status(http_port: int, timeout: float = 2.0) -> dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Fetch rich status data from the daemon API.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
http_port: HTTP port of the daemon
|
|
21
|
+
timeout: Request timeout in seconds
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Dict of status kwargs to pass to format_status_message
|
|
25
|
+
"""
|
|
26
|
+
status_kwargs: dict[str, Any] = {}
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
response = httpx.get(f"http://localhost:{http_port}/admin/status", timeout=timeout)
|
|
30
|
+
if response.status_code != 200:
|
|
31
|
+
return status_kwargs
|
|
32
|
+
|
|
33
|
+
data = response.json()
|
|
34
|
+
|
|
35
|
+
# Process metrics
|
|
36
|
+
process_data = data.get("process")
|
|
37
|
+
if process_data:
|
|
38
|
+
status_kwargs["memory_mb"] = process_data.get("memory_rss_mb")
|
|
39
|
+
status_kwargs["cpu_percent"] = process_data.get("cpu_percent")
|
|
40
|
+
|
|
41
|
+
# MCP servers
|
|
42
|
+
mcp_servers = data.get("mcp_servers", {})
|
|
43
|
+
if mcp_servers:
|
|
44
|
+
total = len(mcp_servers)
|
|
45
|
+
connected = sum(1 for s in mcp_servers.values() if s.get("connected"))
|
|
46
|
+
status_kwargs["mcp_total"] = total
|
|
47
|
+
status_kwargs["mcp_connected"] = connected
|
|
48
|
+
status_kwargs["mcp_tools_cached"] = data.get("mcp_tools_cached", 0)
|
|
49
|
+
|
|
50
|
+
# Find unhealthy servers
|
|
51
|
+
unhealthy = []
|
|
52
|
+
for name, info in mcp_servers.items():
|
|
53
|
+
health = info.get("health")
|
|
54
|
+
if health and health not in ("healthy", None):
|
|
55
|
+
unhealthy.append((name, health))
|
|
56
|
+
elif info.get("consecutive_failures", 0) > 0:
|
|
57
|
+
unhealthy.append((name, f"{info['consecutive_failures']} failures"))
|
|
58
|
+
if unhealthy:
|
|
59
|
+
status_kwargs["mcp_unhealthy"] = unhealthy
|
|
60
|
+
|
|
61
|
+
# Sessions
|
|
62
|
+
sessions = data.get("sessions", {})
|
|
63
|
+
if sessions:
|
|
64
|
+
status_kwargs["sessions_active"] = sessions.get("active", 0)
|
|
65
|
+
status_kwargs["sessions_paused"] = sessions.get("paused", 0)
|
|
66
|
+
status_kwargs["sessions_handoff_ready"] = sessions.get("handoff_ready", 0)
|
|
67
|
+
|
|
68
|
+
# Tasks
|
|
69
|
+
tasks = data.get("tasks", {})
|
|
70
|
+
if tasks:
|
|
71
|
+
status_kwargs["tasks_open"] = tasks.get("open", 0)
|
|
72
|
+
status_kwargs["tasks_in_progress"] = tasks.get("in_progress", 0)
|
|
73
|
+
status_kwargs["tasks_ready"] = tasks.get("ready", 0)
|
|
74
|
+
status_kwargs["tasks_blocked"] = tasks.get("blocked", 0)
|
|
75
|
+
|
|
76
|
+
# Memory
|
|
77
|
+
memory = data.get("memory", {})
|
|
78
|
+
if memory and memory.get("count", 0) > 0:
|
|
79
|
+
status_kwargs["memories_count"] = memory.get("count", 0)
|
|
80
|
+
status_kwargs["memories_avg_importance"] = memory.get("avg_importance", 0.0)
|
|
81
|
+
|
|
82
|
+
except (httpx.ConnectError, httpx.TimeoutException):
|
|
83
|
+
# Daemon not responding - return empty
|
|
84
|
+
pass
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.debug(f"Failed to fetch daemon status: {e}")
|
|
87
|
+
|
|
88
|
+
return status_kwargs
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def format_status_message(
|
|
92
|
+
*,
|
|
93
|
+
running: bool,
|
|
94
|
+
pid: int | None = None,
|
|
95
|
+
pid_file: str | None = None,
|
|
96
|
+
log_files: str | None = None,
|
|
97
|
+
uptime: str | None = None,
|
|
98
|
+
http_port: int | None = None,
|
|
99
|
+
websocket_port: int | None = None,
|
|
100
|
+
# Process metrics
|
|
101
|
+
memory_mb: float | None = None,
|
|
102
|
+
cpu_percent: float | None = None,
|
|
103
|
+
# MCP proxy info
|
|
104
|
+
mcp_connected: int | None = None,
|
|
105
|
+
mcp_total: int | None = None,
|
|
106
|
+
mcp_tools_cached: int | None = None,
|
|
107
|
+
mcp_unhealthy: list[tuple[str, str]] | None = None,
|
|
108
|
+
# Sessions info
|
|
109
|
+
sessions_active: int | None = None,
|
|
110
|
+
sessions_paused: int | None = None,
|
|
111
|
+
sessions_handoff_ready: int | None = None,
|
|
112
|
+
# Tasks info
|
|
113
|
+
tasks_open: int | None = None,
|
|
114
|
+
tasks_in_progress: int | None = None,
|
|
115
|
+
tasks_ready: int | None = None,
|
|
116
|
+
tasks_blocked: int | None = None,
|
|
117
|
+
# Memory
|
|
118
|
+
memories_count: int | None = None,
|
|
119
|
+
memories_avg_importance: float | None = None,
|
|
120
|
+
**kwargs: Any,
|
|
121
|
+
) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Format Gobby daemon status message with consistent styling.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
running: Whether the daemon is running
|
|
127
|
+
pid: Process ID
|
|
128
|
+
pid_file: Path to PID file
|
|
129
|
+
log_files: Path to log files directory
|
|
130
|
+
uptime: Formatted uptime string (e.g., "1h 23m 45s")
|
|
131
|
+
http_port: HTTP server port
|
|
132
|
+
websocket_port: WebSocket server port
|
|
133
|
+
memory_mb: Memory usage in MB
|
|
134
|
+
cpu_percent: CPU usage percentage
|
|
135
|
+
mcp_connected: Number of connected MCP servers
|
|
136
|
+
mcp_total: Total number of configured MCP servers
|
|
137
|
+
mcp_tools_cached: Number of cached tools
|
|
138
|
+
mcp_unhealthy: List of (server_name, status) for unhealthy servers
|
|
139
|
+
sessions_active: Number of active sessions
|
|
140
|
+
sessions_paused: Number of paused sessions
|
|
141
|
+
sessions_handoff_ready: Number of sessions ready for handoff
|
|
142
|
+
tasks_open: Number of open tasks
|
|
143
|
+
tasks_in_progress: Number of in-progress tasks
|
|
144
|
+
tasks_ready: Number of ready tasks
|
|
145
|
+
tasks_blocked: Number of blocked tasks
|
|
146
|
+
memories_count: Total number of memories
|
|
147
|
+
memories_avg_importance: Average memory importance
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Formatted status message string
|
|
152
|
+
"""
|
|
153
|
+
lines = []
|
|
154
|
+
|
|
155
|
+
# Header
|
|
156
|
+
lines.append("=" * 70)
|
|
157
|
+
lines.append("GOBBY DAEMON STATUS")
|
|
158
|
+
lines.append("=" * 70)
|
|
159
|
+
lines.append("")
|
|
160
|
+
|
|
161
|
+
# Status section
|
|
162
|
+
if running:
|
|
163
|
+
status_line = "Status: Running"
|
|
164
|
+
if pid:
|
|
165
|
+
status_line += f" (PID: {pid})"
|
|
166
|
+
lines.append(status_line)
|
|
167
|
+
|
|
168
|
+
# Uptime and process metrics on same conceptual level
|
|
169
|
+
metrics_parts = []
|
|
170
|
+
if uptime:
|
|
171
|
+
metrics_parts.append(f"Uptime: {uptime}")
|
|
172
|
+
if memory_mb is not None:
|
|
173
|
+
metrics_parts.append(f"Memory: {memory_mb:.1f} MB")
|
|
174
|
+
if cpu_percent is not None:
|
|
175
|
+
metrics_parts.append(f"CPU: {cpu_percent:.1f}%")
|
|
176
|
+
|
|
177
|
+
if metrics_parts:
|
|
178
|
+
lines.append(f" {' | '.join(metrics_parts)}")
|
|
179
|
+
else:
|
|
180
|
+
lines.append("Status: Stopped")
|
|
181
|
+
|
|
182
|
+
lines.append("")
|
|
183
|
+
|
|
184
|
+
# Server Configuration section
|
|
185
|
+
if http_port or websocket_port:
|
|
186
|
+
lines.append("Server Configuration:")
|
|
187
|
+
if http_port:
|
|
188
|
+
lines.append(f" HTTP: localhost:{http_port}")
|
|
189
|
+
if websocket_port:
|
|
190
|
+
lines.append(f" WebSocket: localhost:{websocket_port}")
|
|
191
|
+
lines.append("")
|
|
192
|
+
|
|
193
|
+
# MCP Proxy section (only show if we have data)
|
|
194
|
+
if mcp_total is not None:
|
|
195
|
+
lines.append("MCP Proxy:")
|
|
196
|
+
connected = mcp_connected if mcp_connected is not None else 0
|
|
197
|
+
lines.append(f" Servers: {connected} connected / {mcp_total} total")
|
|
198
|
+
if mcp_tools_cached is not None:
|
|
199
|
+
lines.append(f" Tools cached: {mcp_tools_cached}")
|
|
200
|
+
if mcp_unhealthy:
|
|
201
|
+
unhealthy_str = ", ".join(f"{name} ({status})" for name, status in mcp_unhealthy)
|
|
202
|
+
lines.append(f" Unhealthy: {unhealthy_str}")
|
|
203
|
+
lines.append("")
|
|
204
|
+
|
|
205
|
+
# Sessions section (only show if we have data)
|
|
206
|
+
if sessions_active is not None or sessions_paused is not None:
|
|
207
|
+
lines.append("Sessions:")
|
|
208
|
+
parts = []
|
|
209
|
+
if sessions_active is not None:
|
|
210
|
+
parts.append(f"Active: {sessions_active}")
|
|
211
|
+
if sessions_paused is not None:
|
|
212
|
+
parts.append(f"Paused: {sessions_paused}")
|
|
213
|
+
if sessions_handoff_ready is not None:
|
|
214
|
+
parts.append(f"Handoff Ready: {sessions_handoff_ready}")
|
|
215
|
+
if parts:
|
|
216
|
+
lines.append(f" {' | '.join(parts)}")
|
|
217
|
+
lines.append("")
|
|
218
|
+
|
|
219
|
+
# Tasks section (only show if we have data)
|
|
220
|
+
if tasks_open is not None or tasks_in_progress is not None:
|
|
221
|
+
lines.append("Tasks:")
|
|
222
|
+
parts = []
|
|
223
|
+
if tasks_open is not None:
|
|
224
|
+
parts.append(f"Open: {tasks_open}")
|
|
225
|
+
if tasks_in_progress is not None:
|
|
226
|
+
parts.append(f"In Progress: {tasks_in_progress}")
|
|
227
|
+
if tasks_ready is not None:
|
|
228
|
+
parts.append(f"Ready: {tasks_ready}")
|
|
229
|
+
if tasks_blocked is not None:
|
|
230
|
+
parts.append(f"Blocked: {tasks_blocked}")
|
|
231
|
+
if parts:
|
|
232
|
+
lines.append(f" {' | '.join(parts)}")
|
|
233
|
+
lines.append("")
|
|
234
|
+
|
|
235
|
+
# Memory section (only show if we have data)
|
|
236
|
+
if memories_count is not None:
|
|
237
|
+
lines.append("Memory:")
|
|
238
|
+
mem_str = f"Memories: {memories_count}"
|
|
239
|
+
if memories_avg_importance is not None:
|
|
240
|
+
mem_str += f" (avg importance: {memories_avg_importance:.2f})"
|
|
241
|
+
lines.append(f" {mem_str}")
|
|
242
|
+
lines.append("")
|
|
243
|
+
|
|
244
|
+
# Paths section (only when running)
|
|
245
|
+
if running and (pid_file or log_files):
|
|
246
|
+
lines.append("Paths:")
|
|
247
|
+
if pid_file:
|
|
248
|
+
lines.append(f" PID file: {pid_file}")
|
|
249
|
+
if log_files:
|
|
250
|
+
lines.append(f" Logs: {log_files}")
|
|
251
|
+
lines.append("")
|
|
252
|
+
|
|
253
|
+
# Footer
|
|
254
|
+
lines.append("=" * 70)
|
|
255
|
+
|
|
256
|
+
return "\n".join(lines)
|