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,308 @@
|
|
|
1
|
+
"""Windows terminal spawners: Windows Terminal, cmd, PowerShell, and WSL."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import shlex
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess # nosec B404 - subprocess needed for terminal spawning
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from gobby.agents.spawners.base import SpawnResult, TerminalSpawnerBase, TerminalType
|
|
13
|
+
from gobby.agents.tty_config import get_tty_config
|
|
14
|
+
|
|
15
|
+
__all__ = ["WindowsTerminalSpawner", "CmdSpawner", "PowerShellSpawner", "WSLSpawner"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WindowsTerminalSpawner(TerminalSpawnerBase):
|
|
19
|
+
"""Spawner for Windows Terminal."""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def terminal_type(self) -> TerminalType:
|
|
23
|
+
return TerminalType.WINDOWS_TERMINAL
|
|
24
|
+
|
|
25
|
+
def is_available(self) -> bool:
|
|
26
|
+
if platform.system() != "Windows":
|
|
27
|
+
return False
|
|
28
|
+
config = get_tty_config().get_terminal_config("windows-terminal")
|
|
29
|
+
if not config.enabled:
|
|
30
|
+
return False
|
|
31
|
+
command = config.command or "wt"
|
|
32
|
+
return shutil.which(command) is not None
|
|
33
|
+
|
|
34
|
+
def spawn(
|
|
35
|
+
self,
|
|
36
|
+
command: list[str],
|
|
37
|
+
cwd: str | Path,
|
|
38
|
+
env: dict[str, str] | None = None,
|
|
39
|
+
title: str | None = None,
|
|
40
|
+
) -> SpawnResult:
|
|
41
|
+
try:
|
|
42
|
+
tty_config = get_tty_config().get_terminal_config("windows-terminal")
|
|
43
|
+
cli_command = tty_config.command or "wt"
|
|
44
|
+
args = [cli_command, "-d", str(cwd)]
|
|
45
|
+
# Add extra options from config
|
|
46
|
+
args.extend(tty_config.options)
|
|
47
|
+
if title:
|
|
48
|
+
args.extend(["--title", title])
|
|
49
|
+
args.extend(["--", *command])
|
|
50
|
+
|
|
51
|
+
spawn_env = os.environ.copy()
|
|
52
|
+
if env:
|
|
53
|
+
spawn_env.update(env)
|
|
54
|
+
|
|
55
|
+
process = subprocess.Popen( # nosec B603 - args built from config
|
|
56
|
+
args,
|
|
57
|
+
env=spawn_env,
|
|
58
|
+
creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return SpawnResult(
|
|
62
|
+
success=True,
|
|
63
|
+
message=f"Spawned Windows Terminal with PID {process.pid}",
|
|
64
|
+
pid=process.pid,
|
|
65
|
+
terminal_type=self.terminal_type.value,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return SpawnResult(
|
|
70
|
+
success=False,
|
|
71
|
+
message=f"Failed to spawn Windows Terminal: {e}",
|
|
72
|
+
error=str(e),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CmdSpawner(TerminalSpawnerBase):
|
|
77
|
+
"""Spawner for Windows cmd.exe."""
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def terminal_type(self) -> TerminalType:
|
|
81
|
+
return TerminalType.CMD
|
|
82
|
+
|
|
83
|
+
def is_available(self) -> bool:
|
|
84
|
+
if platform.system() != "Windows":
|
|
85
|
+
return False
|
|
86
|
+
config = get_tty_config().get_terminal_config("cmd")
|
|
87
|
+
return config.enabled
|
|
88
|
+
|
|
89
|
+
def spawn(
|
|
90
|
+
self,
|
|
91
|
+
command: list[str],
|
|
92
|
+
cwd: str | Path,
|
|
93
|
+
env: dict[str, str] | None = None,
|
|
94
|
+
title: str | None = None,
|
|
95
|
+
) -> SpawnResult:
|
|
96
|
+
try:
|
|
97
|
+
# Build the inner command as a list and convert safely with list2cmdline
|
|
98
|
+
# This properly escapes all arguments to prevent command injection
|
|
99
|
+
cd_cmd = ["cd", "/d", str(cwd)]
|
|
100
|
+
# Build full command list: cd /d path && original_command
|
|
101
|
+
# list2cmdline handles proper escaping for Windows
|
|
102
|
+
inner_cmd = subprocess.list2cmdline(cd_cmd) + " && " + subprocess.list2cmdline(command)
|
|
103
|
+
|
|
104
|
+
args = ["cmd", "/c", "start"]
|
|
105
|
+
if title:
|
|
106
|
+
# Title must be quoted if it contains spaces
|
|
107
|
+
args.append(subprocess.list2cmdline([title]))
|
|
108
|
+
# Use empty title if none provided (required for start command when path is quoted)
|
|
109
|
+
else:
|
|
110
|
+
args.append('""')
|
|
111
|
+
# Pass the inner command as a single argument to cmd /k
|
|
112
|
+
args.extend(["cmd", "/k", inner_cmd])
|
|
113
|
+
|
|
114
|
+
spawn_env = os.environ.copy()
|
|
115
|
+
if env:
|
|
116
|
+
spawn_env.update(env)
|
|
117
|
+
|
|
118
|
+
process = subprocess.Popen( # nosec B603 - args built from config
|
|
119
|
+
args,
|
|
120
|
+
env=spawn_env,
|
|
121
|
+
creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return SpawnResult(
|
|
125
|
+
success=True,
|
|
126
|
+
message=f"Spawned cmd.exe with PID {process.pid}",
|
|
127
|
+
pid=process.pid,
|
|
128
|
+
terminal_type=self.terminal_type.value,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return SpawnResult(
|
|
133
|
+
success=False,
|
|
134
|
+
message=f"Failed to spawn cmd.exe: {e}",
|
|
135
|
+
error=str(e),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PowerShellSpawner(TerminalSpawnerBase):
|
|
140
|
+
"""Spawner for Windows PowerShell."""
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def terminal_type(self) -> TerminalType:
|
|
144
|
+
return TerminalType.POWERSHELL
|
|
145
|
+
|
|
146
|
+
def is_available(self) -> bool:
|
|
147
|
+
if platform.system() != "Windows":
|
|
148
|
+
return False
|
|
149
|
+
config = get_tty_config().get_terminal_config("powershell")
|
|
150
|
+
if not config.enabled:
|
|
151
|
+
return False
|
|
152
|
+
# Check for pwsh (PowerShell Core) first, then powershell (Windows PowerShell)
|
|
153
|
+
command = config.command or "pwsh"
|
|
154
|
+
if shutil.which(command) is not None:
|
|
155
|
+
return True
|
|
156
|
+
# Fall back to Windows PowerShell
|
|
157
|
+
return shutil.which("powershell") is not None
|
|
158
|
+
|
|
159
|
+
def spawn(
|
|
160
|
+
self,
|
|
161
|
+
command: list[str],
|
|
162
|
+
cwd: str | Path,
|
|
163
|
+
env: dict[str, str] | None = None,
|
|
164
|
+
title: str | None = None,
|
|
165
|
+
) -> SpawnResult:
|
|
166
|
+
try:
|
|
167
|
+
tty_config = get_tty_config().get_terminal_config("powershell")
|
|
168
|
+
# Prefer pwsh (PowerShell Core) over powershell (Windows PowerShell)
|
|
169
|
+
cli_command = tty_config.command or "pwsh"
|
|
170
|
+
if shutil.which(cli_command) is None:
|
|
171
|
+
cli_command = "powershell"
|
|
172
|
+
|
|
173
|
+
# Build the inner command to run
|
|
174
|
+
# PowerShell requires special escaping for the -Command parameter
|
|
175
|
+
inner_cmd = subprocess.list2cmdline(command)
|
|
176
|
+
|
|
177
|
+
# Escape values for PowerShell single-quoted strings (double any single quotes)
|
|
178
|
+
safe_cwd = "'" + str(cwd).replace("'", "''") + "'"
|
|
179
|
+
safe_inner_cmd = inner_cmd.replace("'", "''")
|
|
180
|
+
|
|
181
|
+
# Build PowerShell command:
|
|
182
|
+
# Start-Process spawns a new window, -WorkingDirectory sets cwd
|
|
183
|
+
# -NoExit keeps the window open after command completes
|
|
184
|
+
ps_script = f"Set-Location -Path {safe_cwd}; {safe_inner_cmd}"
|
|
185
|
+
|
|
186
|
+
args = ["cmd", "/c", "start", "", cli_command]
|
|
187
|
+
# Add extra options from config
|
|
188
|
+
args.extend(tty_config.options)
|
|
189
|
+
if title:
|
|
190
|
+
# Escape title for PowerShell
|
|
191
|
+
safe_title = "'" + title.replace("'", "''") + "'"
|
|
192
|
+
args.extend(["-Title", safe_title])
|
|
193
|
+
args.extend(["-NoExit", "-Command", ps_script])
|
|
194
|
+
|
|
195
|
+
spawn_env = os.environ.copy()
|
|
196
|
+
if env:
|
|
197
|
+
spawn_env.update(env)
|
|
198
|
+
|
|
199
|
+
process = subprocess.Popen( # nosec B603 - args built from config
|
|
200
|
+
args,
|
|
201
|
+
env=spawn_env,
|
|
202
|
+
creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return SpawnResult(
|
|
206
|
+
success=True,
|
|
207
|
+
message=f"Spawned PowerShell with PID {process.pid}",
|
|
208
|
+
pid=process.pid,
|
|
209
|
+
terminal_type=self.terminal_type.value,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
return SpawnResult(
|
|
214
|
+
success=False,
|
|
215
|
+
message=f"Failed to spawn PowerShell: {e}",
|
|
216
|
+
error=str(e),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class WSLSpawner(TerminalSpawnerBase):
|
|
221
|
+
"""Spawner for Windows Subsystem for Linux (WSL2)."""
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def terminal_type(self) -> TerminalType:
|
|
225
|
+
return TerminalType.WSL
|
|
226
|
+
|
|
227
|
+
def is_available(self) -> bool:
|
|
228
|
+
if platform.system() != "Windows":
|
|
229
|
+
return False
|
|
230
|
+
config = get_tty_config().get_terminal_config("wsl")
|
|
231
|
+
if not config.enabled:
|
|
232
|
+
return False
|
|
233
|
+
command = config.command or "wsl"
|
|
234
|
+
return shutil.which(command) is not None
|
|
235
|
+
|
|
236
|
+
def spawn(
|
|
237
|
+
self,
|
|
238
|
+
command: list[str],
|
|
239
|
+
cwd: str | Path,
|
|
240
|
+
env: dict[str, str] | None = None,
|
|
241
|
+
title: str | None = None,
|
|
242
|
+
) -> SpawnResult:
|
|
243
|
+
try:
|
|
244
|
+
tty_config = get_tty_config().get_terminal_config("wsl")
|
|
245
|
+
cli_command = tty_config.command or "wsl"
|
|
246
|
+
|
|
247
|
+
# Convert Windows path to WSL path if needed
|
|
248
|
+
# e.g., C:\Users\foo -> /mnt/c/Users/foo
|
|
249
|
+
cwd_str = str(cwd)
|
|
250
|
+
if len(cwd_str) >= 2 and cwd_str[1] == ":":
|
|
251
|
+
# Windows absolute path - convert to WSL format
|
|
252
|
+
drive = cwd_str[0].lower()
|
|
253
|
+
wsl_path = f"/mnt/{drive}{cwd_str[2:].replace(chr(92), '/')}"
|
|
254
|
+
else:
|
|
255
|
+
wsl_path = cwd_str
|
|
256
|
+
|
|
257
|
+
# Build the command to run inside WSL
|
|
258
|
+
# Escape for bash shell inside WSL
|
|
259
|
+
inner_parts = [shlex.quote(part) for part in command]
|
|
260
|
+
inner_cmd = " ".join(inner_parts)
|
|
261
|
+
wsl_script = f"cd {shlex.quote(wsl_path)} && {inner_cmd}"
|
|
262
|
+
|
|
263
|
+
# Build environment exports for WSL
|
|
264
|
+
env_exports = ""
|
|
265
|
+
if env:
|
|
266
|
+
exports = []
|
|
267
|
+
for k, v in env.items():
|
|
268
|
+
if k.isidentifier():
|
|
269
|
+
exports.append(f"export {k}={shlex.quote(v)}")
|
|
270
|
+
if exports:
|
|
271
|
+
env_exports = " && ".join(exports) + " && "
|
|
272
|
+
|
|
273
|
+
full_script = env_exports + wsl_script
|
|
274
|
+
|
|
275
|
+
# Use cmd /c start to spawn in a new console window
|
|
276
|
+
args = ["cmd", "/c", "start"]
|
|
277
|
+
if title:
|
|
278
|
+
args.append(subprocess.list2cmdline([title]))
|
|
279
|
+
else:
|
|
280
|
+
args.append('""')
|
|
281
|
+
args.extend([cli_command])
|
|
282
|
+
# Add extra options from config (e.g., -d for distribution)
|
|
283
|
+
args.extend(tty_config.options)
|
|
284
|
+
args.extend(["--", "bash", "-c", full_script])
|
|
285
|
+
|
|
286
|
+
spawn_env = os.environ.copy()
|
|
287
|
+
# Note: env vars passed via spawn_env won't reach WSL directly
|
|
288
|
+
# They're handled via the bash -c script above
|
|
289
|
+
|
|
290
|
+
process = subprocess.Popen( # nosec B603 - args built from config
|
|
291
|
+
args,
|
|
292
|
+
env=spawn_env,
|
|
293
|
+
creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
return SpawnResult(
|
|
297
|
+
success=True,
|
|
298
|
+
message=f"Spawned WSL with PID {process.pid}",
|
|
299
|
+
pid=process.pid,
|
|
300
|
+
terminal_type=self.terminal_type.value,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
except Exception as e:
|
|
304
|
+
return SpawnResult(
|
|
305
|
+
success=False,
|
|
306
|
+
message=f"Failed to spawn WSL: {e}",
|
|
307
|
+
error=str(e),
|
|
308
|
+
)
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terminal spawner configuration.
|
|
3
|
+
|
|
4
|
+
Loads terminal preferences and customizations from ~/.gobby/tty_config.yaml,
|
|
5
|
+
allowing users to reorder preferences, customize app paths, and add options.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import platform
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TerminalConfig(BaseModel):
|
|
19
|
+
"""Configuration for a specific terminal emulator."""
|
|
20
|
+
|
|
21
|
+
app_path: str | None = Field(
|
|
22
|
+
default=None,
|
|
23
|
+
description="macOS app bundle path (e.g., /Applications/Ghostty.app)",
|
|
24
|
+
)
|
|
25
|
+
command: str | None = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="CLI command name for shutil.which() (e.g., ghostty, kitty)",
|
|
28
|
+
)
|
|
29
|
+
options: list[str] = Field(
|
|
30
|
+
default_factory=list,
|
|
31
|
+
description="Extra command-line options to pass to the terminal",
|
|
32
|
+
)
|
|
33
|
+
enabled: bool = Field(
|
|
34
|
+
default=True,
|
|
35
|
+
description="Whether this terminal is enabled for use",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PlatformPreferences(BaseModel):
|
|
40
|
+
"""Terminal preference order by platform."""
|
|
41
|
+
|
|
42
|
+
macos: list[str] = Field(
|
|
43
|
+
default_factory=lambda: [
|
|
44
|
+
"ghostty",
|
|
45
|
+
"iterm",
|
|
46
|
+
"kitty",
|
|
47
|
+
"alacritty",
|
|
48
|
+
"terminal.app",
|
|
49
|
+
"tmux", # Multiplexer (last resort)
|
|
50
|
+
],
|
|
51
|
+
description="Terminal preference order for macOS",
|
|
52
|
+
)
|
|
53
|
+
linux: list[str] = Field(
|
|
54
|
+
default_factory=lambda: [
|
|
55
|
+
"ghostty",
|
|
56
|
+
"kitty",
|
|
57
|
+
"gnome-terminal",
|
|
58
|
+
"konsole",
|
|
59
|
+
"alacritty",
|
|
60
|
+
"tmux", # Multiplexer (last resort)
|
|
61
|
+
],
|
|
62
|
+
description="Terminal preference order for Linux",
|
|
63
|
+
)
|
|
64
|
+
windows: list[str] = Field(
|
|
65
|
+
default_factory=lambda: [
|
|
66
|
+
"windows-terminal",
|
|
67
|
+
"powershell",
|
|
68
|
+
"alacritty",
|
|
69
|
+
"wsl",
|
|
70
|
+
"cmd",
|
|
71
|
+
],
|
|
72
|
+
description="Terminal preference order for Windows",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Default terminal configurations (can be overridden in config file)
|
|
77
|
+
DEFAULT_TERMINAL_CONFIGS: dict[str, dict[str, Any]] = {
|
|
78
|
+
"ghostty": {
|
|
79
|
+
"app_path": "/Applications/Ghostty.app",
|
|
80
|
+
"command": "ghostty",
|
|
81
|
+
},
|
|
82
|
+
"iterm": {
|
|
83
|
+
"app_path": "/Applications/iTerm.app",
|
|
84
|
+
},
|
|
85
|
+
"terminal.app": {
|
|
86
|
+
"app_path": "/System/Applications/Utilities/Terminal.app",
|
|
87
|
+
},
|
|
88
|
+
"kitty": {
|
|
89
|
+
"app_path": "/Applications/kitty.app",
|
|
90
|
+
"command": "kitty",
|
|
91
|
+
"options": ["-o", "confirm_os_window_close=0"],
|
|
92
|
+
},
|
|
93
|
+
"alacritty": {
|
|
94
|
+
"command": "alacritty",
|
|
95
|
+
},
|
|
96
|
+
"gnome-terminal": {
|
|
97
|
+
"command": "gnome-terminal",
|
|
98
|
+
},
|
|
99
|
+
"konsole": {
|
|
100
|
+
"command": "konsole",
|
|
101
|
+
},
|
|
102
|
+
"windows-terminal": {
|
|
103
|
+
"command": "wt",
|
|
104
|
+
},
|
|
105
|
+
"cmd": {
|
|
106
|
+
# Built-in on Windows, no command needed
|
|
107
|
+
},
|
|
108
|
+
"powershell": {
|
|
109
|
+
# pwsh (PowerShell Core) is preferred, falls back to powershell (Windows PowerShell)
|
|
110
|
+
"command": "pwsh",
|
|
111
|
+
},
|
|
112
|
+
"wsl": {
|
|
113
|
+
"command": "wsl",
|
|
114
|
+
# Options can specify distribution: ["-d", "Ubuntu"]
|
|
115
|
+
},
|
|
116
|
+
"tmux": {
|
|
117
|
+
"command": "tmux",
|
|
118
|
+
# Options can set socket name, config file, etc.
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TTYConfig(BaseModel):
|
|
124
|
+
"""Terminal spawner configuration."""
|
|
125
|
+
|
|
126
|
+
preferences: PlatformPreferences = Field(
|
|
127
|
+
default_factory=PlatformPreferences,
|
|
128
|
+
description="Terminal preference order by platform",
|
|
129
|
+
)
|
|
130
|
+
terminals: dict[str, TerminalConfig] = Field(
|
|
131
|
+
default_factory=dict,
|
|
132
|
+
description="Terminal-specific configurations (merged with defaults)",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def get_terminal_config(self, terminal_name: str) -> TerminalConfig:
|
|
136
|
+
"""
|
|
137
|
+
Get configuration for a specific terminal.
|
|
138
|
+
|
|
139
|
+
Merges user config with defaults, with user config taking precedence.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
terminal_name: Name of the terminal (e.g., 'ghostty', 'iterm')
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
TerminalConfig with merged settings
|
|
146
|
+
"""
|
|
147
|
+
# Start with defaults
|
|
148
|
+
defaults = DEFAULT_TERMINAL_CONFIGS.get(terminal_name, {})
|
|
149
|
+
config_dict = dict(defaults)
|
|
150
|
+
|
|
151
|
+
# Merge user config if present
|
|
152
|
+
if terminal_name in self.terminals:
|
|
153
|
+
user_config = self.terminals[terminal_name].model_dump(exclude_none=True)
|
|
154
|
+
# For options, extend rather than replace
|
|
155
|
+
if "options" in user_config and "options" in config_dict:
|
|
156
|
+
config_dict["options"] = config_dict["options"] + user_config["options"]
|
|
157
|
+
del user_config["options"]
|
|
158
|
+
config_dict.update(user_config)
|
|
159
|
+
|
|
160
|
+
return TerminalConfig(**config_dict)
|
|
161
|
+
|
|
162
|
+
def get_preferences(self) -> list[str]:
|
|
163
|
+
"""
|
|
164
|
+
Get terminal preference order for current platform.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
List of terminal names in preference order
|
|
168
|
+
"""
|
|
169
|
+
system = platform.system()
|
|
170
|
+
if system == "Darwin":
|
|
171
|
+
return self.preferences.macos
|
|
172
|
+
elif system == "Windows":
|
|
173
|
+
return self.preferences.windows
|
|
174
|
+
else:
|
|
175
|
+
return self.preferences.linux
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def load_tty_config(config_path: str | Path | None = None) -> TTYConfig:
|
|
179
|
+
"""
|
|
180
|
+
Load terminal configuration from YAML file.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
config_path: Path to config file (default: ~/.gobby/tty_config.yaml)
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
TTYConfig instance (with defaults if file doesn't exist)
|
|
187
|
+
"""
|
|
188
|
+
if config_path is None:
|
|
189
|
+
config_path = Path.home() / ".gobby" / "tty_config.yaml"
|
|
190
|
+
else:
|
|
191
|
+
config_path = Path(config_path).expanduser()
|
|
192
|
+
|
|
193
|
+
if not config_path.exists():
|
|
194
|
+
return TTYConfig()
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
with open(config_path) as f:
|
|
198
|
+
data = yaml.safe_load(f)
|
|
199
|
+
return TTYConfig(**(data or {}))
|
|
200
|
+
except Exception:
|
|
201
|
+
# Fall back to defaults on any error
|
|
202
|
+
return TTYConfig()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def generate_default_tty_config(config_path: str | Path | None = None) -> Path:
|
|
206
|
+
"""
|
|
207
|
+
Generate default terminal configuration file.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
config_path: Path to config file (default: ~/.gobby/tty_config.yaml)
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Path to the created config file
|
|
214
|
+
"""
|
|
215
|
+
if config_path is None:
|
|
216
|
+
config_path = Path.home() / ".gobby" / "tty_config.yaml"
|
|
217
|
+
else:
|
|
218
|
+
config_path = Path(config_path).expanduser()
|
|
219
|
+
|
|
220
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
221
|
+
|
|
222
|
+
# Generate example config with comments
|
|
223
|
+
config_content = """# Terminal spawner configuration for Gobby
|
|
224
|
+
# See: https://github.com/GobbyAI/gobby/docs/terminal-config.md
|
|
225
|
+
|
|
226
|
+
# Terminal preference order by platform (first available is used)
|
|
227
|
+
preferences:
|
|
228
|
+
macos:
|
|
229
|
+
- ghostty
|
|
230
|
+
- iterm
|
|
231
|
+
- kitty
|
|
232
|
+
- alacritty
|
|
233
|
+
- terminal.app
|
|
234
|
+
- tmux
|
|
235
|
+
linux:
|
|
236
|
+
- ghostty
|
|
237
|
+
- kitty
|
|
238
|
+
- gnome-terminal
|
|
239
|
+
- konsole
|
|
240
|
+
- alacritty
|
|
241
|
+
- tmux
|
|
242
|
+
windows:
|
|
243
|
+
- windows-terminal
|
|
244
|
+
- powershell
|
|
245
|
+
- alacritty
|
|
246
|
+
- wsl
|
|
247
|
+
- cmd
|
|
248
|
+
|
|
249
|
+
# Terminal-specific configurations (overrides defaults)
|
|
250
|
+
# Uncomment and modify as needed:
|
|
251
|
+
#
|
|
252
|
+
# terminals:
|
|
253
|
+
# ghostty:
|
|
254
|
+
# app_path: /Applications/Ghostty.app # macOS app bundle path
|
|
255
|
+
# command: ghostty # CLI command (Linux/other)
|
|
256
|
+
# enabled: true # Set to false to skip this terminal
|
|
257
|
+
#
|
|
258
|
+
# kitty:
|
|
259
|
+
# app_path: /Applications/kitty.app
|
|
260
|
+
# command: kitty
|
|
261
|
+
# options: # Extra command-line options
|
|
262
|
+
# - "-o"
|
|
263
|
+
# - "confirm_os_window_close=0"
|
|
264
|
+
#
|
|
265
|
+
# iterm:
|
|
266
|
+
# app_path: /Applications/iTerm.app
|
|
267
|
+
#
|
|
268
|
+
# powershell:
|
|
269
|
+
# command: pwsh # Use 'powershell' for Windows PowerShell
|
|
270
|
+
#
|
|
271
|
+
# wsl:
|
|
272
|
+
# command: wsl
|
|
273
|
+
# options: # Specify WSL distribution
|
|
274
|
+
# - "-d"
|
|
275
|
+
# - "Ubuntu"
|
|
276
|
+
#
|
|
277
|
+
# tmux:
|
|
278
|
+
# command: tmux
|
|
279
|
+
# options: # Use specific socket/config
|
|
280
|
+
# - "-L"
|
|
281
|
+
# - "gobby"
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
with open(config_path, "w") as f:
|
|
285
|
+
f.write(config_content)
|
|
286
|
+
|
|
287
|
+
# Set restrictive permissions
|
|
288
|
+
config_path.chmod(0o600)
|
|
289
|
+
|
|
290
|
+
return config_path
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# Global cached config instance
|
|
294
|
+
_config: TTYConfig | None = None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_tty_config() -> TTYConfig:
|
|
298
|
+
"""
|
|
299
|
+
Get the terminal configuration (cached).
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
TTYConfig instance
|
|
303
|
+
"""
|
|
304
|
+
global _config
|
|
305
|
+
if _config is None:
|
|
306
|
+
_config = load_tty_config()
|
|
307
|
+
return _config
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def reload_tty_config() -> TTYConfig:
|
|
311
|
+
"""
|
|
312
|
+
Reload terminal configuration from disk.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
New TTYConfig instance
|
|
316
|
+
"""
|
|
317
|
+
global _config
|
|
318
|
+
_config = load_tty_config()
|
|
319
|
+
return _config
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Autonomous execution infrastructure for Gobby.
|
|
2
|
+
|
|
3
|
+
This module provides infrastructure for autonomous task execution including:
|
|
4
|
+
- Stop signal management for graceful shutdown
|
|
5
|
+
- Progress tracking for detecting stagnation
|
|
6
|
+
- Stuck detection for breaking out of loops
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from gobby.autonomous.progress_tracker import (
|
|
10
|
+
ProgressEvent,
|
|
11
|
+
ProgressSummary,
|
|
12
|
+
ProgressTracker,
|
|
13
|
+
ProgressType,
|
|
14
|
+
)
|
|
15
|
+
from gobby.autonomous.stop_registry import StopRegistry, StopSignal
|
|
16
|
+
from gobby.autonomous.stuck_detector import (
|
|
17
|
+
StuckDetectionResult,
|
|
18
|
+
StuckDetector,
|
|
19
|
+
TaskSelectionEvent,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ProgressEvent",
|
|
24
|
+
"ProgressSummary",
|
|
25
|
+
"ProgressTracker",
|
|
26
|
+
"ProgressType",
|
|
27
|
+
"StopRegistry",
|
|
28
|
+
"StopSignal",
|
|
29
|
+
"StuckDetectionResult",
|
|
30
|
+
"StuckDetector",
|
|
31
|
+
"TaskSelectionEvent",
|
|
32
|
+
]
|