gobby 0.2.8__py3-none-any.whl → 0.2.11__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 +1 -1
- gobby/adapters/__init__.py +6 -0
- gobby/adapters/base.py +11 -2
- gobby/adapters/claude_code.py +5 -28
- gobby/adapters/codex_impl/adapter.py +38 -43
- gobby/adapters/copilot.py +324 -0
- gobby/adapters/cursor.py +373 -0
- gobby/adapters/gemini.py +2 -26
- gobby/adapters/windsurf.py +359 -0
- gobby/agents/definitions.py +162 -2
- gobby/agents/isolation.py +33 -1
- gobby/agents/pty_reader.py +192 -0
- gobby/agents/registry.py +10 -1
- gobby/agents/runner.py +24 -8
- gobby/agents/sandbox.py +8 -3
- gobby/agents/session.py +4 -0
- gobby/agents/spawn.py +9 -2
- gobby/agents/spawn_executor.py +49 -61
- gobby/agents/spawners/command_builder.py +4 -4
- gobby/app_context.py +64 -0
- gobby/cli/__init__.py +4 -0
- gobby/cli/install.py +259 -4
- gobby/cli/installers/__init__.py +12 -0
- gobby/cli/installers/copilot.py +242 -0
- gobby/cli/installers/cursor.py +244 -0
- gobby/cli/installers/shared.py +3 -0
- gobby/cli/installers/windsurf.py +242 -0
- gobby/cli/pipelines.py +639 -0
- gobby/cli/sessions.py +3 -1
- gobby/cli/skills.py +209 -0
- gobby/cli/tasks/crud.py +6 -5
- gobby/cli/tasks/search.py +1 -1
- gobby/cli/ui.py +116 -0
- gobby/cli/utils.py +5 -17
- gobby/cli/workflows.py +38 -17
- gobby/config/app.py +5 -0
- gobby/config/features.py +0 -20
- gobby/config/skills.py +23 -2
- gobby/config/tasks.py +4 -0
- gobby/hooks/broadcaster.py +9 -0
- gobby/hooks/event_handlers/__init__.py +155 -0
- gobby/hooks/event_handlers/_agent.py +175 -0
- gobby/hooks/event_handlers/_base.py +92 -0
- gobby/hooks/event_handlers/_misc.py +66 -0
- gobby/hooks/event_handlers/_session.py +487 -0
- gobby/hooks/event_handlers/_tool.py +196 -0
- gobby/hooks/events.py +48 -0
- gobby/hooks/hook_manager.py +27 -3
- gobby/install/copilot/hooks/hook_dispatcher.py +203 -0
- gobby/install/cursor/hooks/hook_dispatcher.py +203 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +8 -0
- gobby/install/windsurf/hooks/hook_dispatcher.py +205 -0
- gobby/llm/__init__.py +14 -1
- gobby/llm/claude.py +594 -43
- gobby/llm/service.py +149 -0
- gobby/mcp_proxy/importer.py +4 -41
- gobby/mcp_proxy/instructions.py +9 -27
- gobby/mcp_proxy/manager.py +13 -3
- gobby/mcp_proxy/models.py +1 -0
- gobby/mcp_proxy/registries.py +66 -5
- gobby/mcp_proxy/server.py +6 -2
- gobby/mcp_proxy/services/recommendation.py +2 -28
- gobby/mcp_proxy/services/tool_filter.py +7 -0
- gobby/mcp_proxy/services/tool_proxy.py +19 -1
- gobby/mcp_proxy/stdio.py +37 -21
- gobby/mcp_proxy/tools/agents.py +7 -0
- gobby/mcp_proxy/tools/artifacts.py +3 -3
- gobby/mcp_proxy/tools/hub.py +30 -1
- gobby/mcp_proxy/tools/orchestration/cleanup.py +5 -5
- gobby/mcp_proxy/tools/orchestration/monitor.py +1 -1
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +8 -3
- gobby/mcp_proxy/tools/orchestration/review.py +17 -4
- gobby/mcp_proxy/tools/orchestration/wait.py +7 -7
- gobby/mcp_proxy/tools/pipelines/__init__.py +254 -0
- gobby/mcp_proxy/tools/pipelines/_discovery.py +67 -0
- gobby/mcp_proxy/tools/pipelines/_execution.py +281 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +4 -4
- gobby/mcp_proxy/tools/sessions/_handoff.py +1 -1
- gobby/mcp_proxy/tools/skills/__init__.py +184 -30
- gobby/mcp_proxy/tools/spawn_agent.py +229 -14
- gobby/mcp_proxy/tools/task_readiness.py +27 -4
- gobby/mcp_proxy/tools/tasks/_context.py +8 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +27 -1
- gobby/mcp_proxy/tools/tasks/_helpers.py +1 -1
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +125 -8
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +2 -1
- gobby/mcp_proxy/tools/tasks/_search.py +1 -1
- gobby/mcp_proxy/tools/workflows/__init__.py +273 -0
- gobby/mcp_proxy/tools/workflows/_artifacts.py +225 -0
- gobby/mcp_proxy/tools/workflows/_import.py +112 -0
- gobby/mcp_proxy/tools/workflows/_lifecycle.py +332 -0
- gobby/mcp_proxy/tools/workflows/_query.py +226 -0
- gobby/mcp_proxy/tools/workflows/_resolution.py +78 -0
- gobby/mcp_proxy/tools/workflows/_terminal.py +175 -0
- gobby/mcp_proxy/tools/worktrees.py +54 -15
- gobby/memory/components/__init__.py +0 -0
- gobby/memory/components/ingestion.py +98 -0
- gobby/memory/components/search.py +108 -0
- gobby/memory/context.py +5 -5
- gobby/memory/manager.py +16 -25
- gobby/paths.py +51 -0
- gobby/prompts/loader.py +1 -35
- gobby/runner.py +131 -16
- gobby/servers/http.py +193 -150
- gobby/servers/routes/__init__.py +2 -0
- gobby/servers/routes/admin.py +56 -0
- gobby/servers/routes/mcp/endpoints/execution.py +33 -32
- gobby/servers/routes/mcp/endpoints/registry.py +8 -8
- gobby/servers/routes/mcp/hooks.py +10 -1
- gobby/servers/routes/pipelines.py +227 -0
- gobby/servers/websocket.py +314 -1
- gobby/sessions/analyzer.py +89 -3
- gobby/sessions/manager.py +5 -5
- gobby/sessions/transcripts/__init__.py +3 -0
- gobby/sessions/transcripts/claude.py +5 -0
- gobby/sessions/transcripts/codex.py +5 -0
- gobby/sessions/transcripts/gemini.py +5 -0
- gobby/skills/hubs/__init__.py +25 -0
- gobby/skills/hubs/base.py +234 -0
- gobby/skills/hubs/claude_plugins.py +328 -0
- gobby/skills/hubs/clawdhub.py +289 -0
- gobby/skills/hubs/github_collection.py +465 -0
- gobby/skills/hubs/manager.py +263 -0
- gobby/skills/hubs/skillhub.py +342 -0
- gobby/skills/parser.py +23 -0
- gobby/skills/sync.py +5 -4
- gobby/storage/artifacts.py +19 -0
- gobby/storage/memories.py +4 -4
- gobby/storage/migrations.py +118 -3
- gobby/storage/pipelines.py +367 -0
- gobby/storage/sessions.py +23 -4
- gobby/storage/skills.py +48 -8
- gobby/storage/tasks/_aggregates.py +2 -2
- gobby/storage/tasks/_lifecycle.py +4 -4
- gobby/storage/tasks/_models.py +7 -1
- gobby/storage/tasks/_queries.py +3 -3
- gobby/sync/memories.py +4 -3
- gobby/tasks/commits.py +48 -17
- gobby/tasks/external_validator.py +4 -17
- gobby/tasks/validation.py +13 -87
- gobby/tools/summarizer.py +18 -51
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +80 -0
- gobby/workflows/context_actions.py +265 -27
- gobby/workflows/definitions.py +119 -1
- gobby/workflows/detection_helpers.py +23 -11
- gobby/workflows/enforcement/__init__.py +11 -1
- gobby/workflows/enforcement/blocking.py +96 -0
- gobby/workflows/enforcement/handlers.py +35 -1
- gobby/workflows/enforcement/task_policy.py +18 -0
- gobby/workflows/engine.py +26 -4
- gobby/workflows/evaluator.py +8 -5
- gobby/workflows/lifecycle_evaluator.py +59 -27
- gobby/workflows/loader.py +567 -30
- gobby/workflows/lobster_compat.py +147 -0
- gobby/workflows/pipeline_executor.py +801 -0
- gobby/workflows/pipeline_state.py +172 -0
- gobby/workflows/pipeline_webhooks.py +206 -0
- gobby/workflows/premature_stop.py +5 -0
- gobby/worktrees/git.py +135 -20
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/METADATA +56 -22
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/RECORD +166 -122
- gobby/hooks/event_handlers.py +0 -1008
- gobby/mcp_proxy/tools/workflows.py +0 -1023
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/WHEEL +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.8.dist-info → gobby-0.2.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""ClawdHub provider implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the ClawdHubProvider class which wraps the official
|
|
4
|
+
`clawdhub` CLI tool to provide skill search, listing, and download functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from gobby.skills.hubs.base import DownloadResult, HubProvider, HubSkillDetails, HubSkillInfo
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClawdHubProvider(HubProvider):
|
|
20
|
+
"""Provider for ClawdHub skill registry using the CLI tool.
|
|
21
|
+
|
|
22
|
+
This provider wraps the official `clawdhub` CLI tool (installed via
|
|
23
|
+
`npm i -g clawdhub`) to provide access to the ClawdHub skill registry.
|
|
24
|
+
|
|
25
|
+
The CLI provides commands for:
|
|
26
|
+
- search: Search for skills by query
|
|
27
|
+
- list: List available skills
|
|
28
|
+
- install: Download/install a skill
|
|
29
|
+
- info: Get detailed skill information
|
|
30
|
+
|
|
31
|
+
Example usage:
|
|
32
|
+
```python
|
|
33
|
+
provider = ClawdHubProvider(
|
|
34
|
+
hub_name="clawdhub",
|
|
35
|
+
base_url="https://clawdhub.com",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Check if CLI is available
|
|
39
|
+
info = await provider.discover()
|
|
40
|
+
if info["cli_available"]:
|
|
41
|
+
results = await provider.search("commit message")
|
|
42
|
+
for skill in results:
|
|
43
|
+
print(f"{skill.slug}: {skill.description}")
|
|
44
|
+
```
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
hub_name: str,
|
|
50
|
+
base_url: str,
|
|
51
|
+
auth_token: str | None = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Initialize the ClawdHub provider.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
hub_name: The configured name for this hub instance
|
|
57
|
+
base_url: Base URL for ClawdHub (used for reference)
|
|
58
|
+
auth_token: Optional authentication token for private skills
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(hub_name=hub_name, base_url=base_url, auth_token=auth_token)
|
|
61
|
+
self._cli_available: bool | None = None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def provider_type(self) -> str:
|
|
65
|
+
"""Return the provider type identifier."""
|
|
66
|
+
return "clawdhub"
|
|
67
|
+
|
|
68
|
+
async def _check_cli_available(self) -> bool:
|
|
69
|
+
"""Check if the clawdhub CLI is available.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if CLI is installed and accessible, False otherwise
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
process = await asyncio.create_subprocess_exec(
|
|
76
|
+
"clawdhub",
|
|
77
|
+
"--version",
|
|
78
|
+
stdout=asyncio.subprocess.PIPE,
|
|
79
|
+
stderr=asyncio.subprocess.PIPE,
|
|
80
|
+
)
|
|
81
|
+
stdout, _ = await process.communicate()
|
|
82
|
+
|
|
83
|
+
if process.returncode == 0:
|
|
84
|
+
version = stdout.decode().strip()
|
|
85
|
+
logger.debug(f"ClawdHub CLI version: {version}")
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
except FileNotFoundError:
|
|
89
|
+
logger.warning("ClawdHub CLI not found. Install with: npm i -g clawdhub")
|
|
90
|
+
return False
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Error checking ClawdHub CLI: {e}")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
async def _run_cli_command(
|
|
96
|
+
self,
|
|
97
|
+
command: str,
|
|
98
|
+
args: list[str] | None = None,
|
|
99
|
+
) -> dict[str, Any]:
|
|
100
|
+
"""Run a clawdhub CLI command and return parsed JSON output.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
command: The CLI command (search, list, install, info, etc.)
|
|
104
|
+
args: Additional arguments for the command
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Parsed JSON output from the CLI
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
RuntimeError: If CLI is not available or command fails
|
|
111
|
+
"""
|
|
112
|
+
cmd_args = ["clawdhub", command, "--json"]
|
|
113
|
+
if args:
|
|
114
|
+
cmd_args.extend(args)
|
|
115
|
+
|
|
116
|
+
# Add auth token if available
|
|
117
|
+
if self.auth_token:
|
|
118
|
+
cmd_args.extend(["--token", self.auth_token])
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
process = await asyncio.create_subprocess_exec(
|
|
122
|
+
*cmd_args,
|
|
123
|
+
stdout=asyncio.subprocess.PIPE,
|
|
124
|
+
stderr=asyncio.subprocess.PIPE,
|
|
125
|
+
)
|
|
126
|
+
stdout, stderr = await process.communicate()
|
|
127
|
+
|
|
128
|
+
if process.returncode != 0:
|
|
129
|
+
error_msg = stderr.decode().strip() if stderr else "Unknown error"
|
|
130
|
+
logger.error(f"ClawdHub CLI error: {error_msg}")
|
|
131
|
+
raise RuntimeError(f"ClawdHub CLI command failed: {error_msg}")
|
|
132
|
+
|
|
133
|
+
output = stdout.decode().strip()
|
|
134
|
+
if not output:
|
|
135
|
+
return {}
|
|
136
|
+
|
|
137
|
+
parsed: dict[str, Any] = json.loads(output)
|
|
138
|
+
return parsed
|
|
139
|
+
except FileNotFoundError as e:
|
|
140
|
+
raise RuntimeError("ClawdHub CLI not found. Install with: npm i -g clawdhub") from e
|
|
141
|
+
except json.JSONDecodeError as e:
|
|
142
|
+
logger.error(f"Failed to parse CLI output: {e}")
|
|
143
|
+
raise RuntimeError(f"Invalid JSON from ClawdHub CLI: {e}") from e
|
|
144
|
+
|
|
145
|
+
async def discover(self) -> dict[str, Any]:
|
|
146
|
+
"""Discover hub capabilities and check CLI availability.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dictionary with hub info and CLI availability status
|
|
150
|
+
"""
|
|
151
|
+
cli_available = await self._check_cli_available()
|
|
152
|
+
self._cli_available = cli_available
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"hub_name": self.hub_name,
|
|
156
|
+
"provider_type": self.provider_type,
|
|
157
|
+
"cli_available": cli_available,
|
|
158
|
+
"base_url": self.base_url,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async def search(
|
|
162
|
+
self,
|
|
163
|
+
query: str,
|
|
164
|
+
limit: int = 20,
|
|
165
|
+
) -> list[HubSkillInfo]:
|
|
166
|
+
"""Search for skills matching a query.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
query: Search query string
|
|
170
|
+
limit: Maximum number of results
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of matching skills with basic info
|
|
174
|
+
"""
|
|
175
|
+
result = await self._run_cli_command("search", [query, "--limit", str(limit)])
|
|
176
|
+
|
|
177
|
+
skills = result.get("skills", [])
|
|
178
|
+
return [
|
|
179
|
+
HubSkillInfo(
|
|
180
|
+
slug=skill.get("slug", skill.get("name", "")),
|
|
181
|
+
display_name=skill.get("name", skill.get("slug", "")),
|
|
182
|
+
description=skill.get("description", ""),
|
|
183
|
+
hub_name=self.hub_name,
|
|
184
|
+
version=skill.get("version"),
|
|
185
|
+
score=skill.get("score"),
|
|
186
|
+
)
|
|
187
|
+
for skill in skills
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
async def list_skills(
|
|
191
|
+
self,
|
|
192
|
+
limit: int = 50,
|
|
193
|
+
offset: int = 0,
|
|
194
|
+
) -> list[HubSkillInfo]:
|
|
195
|
+
"""List available skills from the hub.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
limit: Maximum number of results
|
|
199
|
+
offset: Number of results to skip
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
List of skills with basic info
|
|
203
|
+
"""
|
|
204
|
+
args = ["--limit", str(limit)]
|
|
205
|
+
if offset > 0:
|
|
206
|
+
args.extend(["--offset", str(offset)])
|
|
207
|
+
|
|
208
|
+
result = await self._run_cli_command("list", args)
|
|
209
|
+
|
|
210
|
+
skills = result.get("skills", [])
|
|
211
|
+
return [
|
|
212
|
+
HubSkillInfo(
|
|
213
|
+
slug=skill.get("slug", skill.get("name", "")),
|
|
214
|
+
display_name=skill.get("name", skill.get("slug", "")),
|
|
215
|
+
description=skill.get("description", ""),
|
|
216
|
+
hub_name=self.hub_name,
|
|
217
|
+
version=skill.get("version"),
|
|
218
|
+
)
|
|
219
|
+
for skill in skills
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
async def get_skill_details(
|
|
223
|
+
self,
|
|
224
|
+
slug: str,
|
|
225
|
+
) -> HubSkillDetails | None:
|
|
226
|
+
"""Get detailed information about a specific skill.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
slug: The skill's unique identifier
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Detailed skill info, or None if not found
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
result = await self._run_cli_command("info", [slug])
|
|
236
|
+
|
|
237
|
+
if not result:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
return HubSkillDetails(
|
|
241
|
+
slug=result.get("slug", slug),
|
|
242
|
+
display_name=result.get("name", slug),
|
|
243
|
+
description=result.get("description", ""),
|
|
244
|
+
hub_name=self.hub_name,
|
|
245
|
+
version=result.get("version"),
|
|
246
|
+
latest_version=result.get("latest_version", result.get("version")),
|
|
247
|
+
versions=result.get("versions", []),
|
|
248
|
+
)
|
|
249
|
+
except RuntimeError:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
async def download_skill(
|
|
253
|
+
self,
|
|
254
|
+
slug: str,
|
|
255
|
+
version: str | None = None,
|
|
256
|
+
target_dir: str | None = None,
|
|
257
|
+
) -> DownloadResult:
|
|
258
|
+
"""Download and extract a skill from the hub.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
slug: The skill's unique identifier
|
|
262
|
+
version: Specific version to download (None for latest)
|
|
263
|
+
target_dir: Directory to extract to
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
DownloadResult with success status, path, version, or error
|
|
267
|
+
"""
|
|
268
|
+
args = [slug]
|
|
269
|
+
|
|
270
|
+
if version:
|
|
271
|
+
args.extend(["--version", version])
|
|
272
|
+
|
|
273
|
+
if target_dir:
|
|
274
|
+
args.extend(["--output", target_dir])
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
result = await self._run_cli_command("install", args)
|
|
278
|
+
return DownloadResult(
|
|
279
|
+
success=result.get("success", True),
|
|
280
|
+
slug=slug,
|
|
281
|
+
path=result.get("path", target_dir),
|
|
282
|
+
version=result.get("version", version),
|
|
283
|
+
)
|
|
284
|
+
except RuntimeError as e:
|
|
285
|
+
return DownloadResult(
|
|
286
|
+
success=False,
|
|
287
|
+
slug=slug,
|
|
288
|
+
error=str(e),
|
|
289
|
+
)
|