Files
clide/legacy/clide/services/process_service.py
T
jpmschweitzerandClaude Opus 4.7 a355751437 move python clide to legacy/
Clide is being rebuilt as a Flutter desktop app. The Python Textual
implementation moves wholesale into legacy/ rather than being deleted:
its pane model, panel set, git skills, and panel communication design
are real thought that should remain readable next to the new code
while the rebuild finds its shape. Git's rename tracking preserves
history, so `git log -- legacy/` still works.

The Flutter rebuild lives at the repo root alongside a Go sidecar
(the architecture claudian was heading toward, which folds into
clide as a core component rather than a separate plugin project).
Bootstrap of the new shape lands in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:30:51 +02:00

125 lines
3.3 KiB
Python

"""Generic subprocess management service."""
import asyncio
import subprocess
from dataclasses import dataclass
from pathlib import Path
@dataclass
class CommandResult:
"""Result of a command execution."""
returncode: int
stdout: str
stderr: str
@property
def success(self) -> bool:
"""Check if command succeeded."""
return self.returncode == 0
class ProcessService:
"""Service for running subprocess commands."""
def __init__(self, cwd: Path | None = None) -> None:
self.cwd = cwd or Path.cwd()
async def run(
self,
*args: str,
cwd: Path | None = None,
timeout: float | None = 30.0,
env: dict[str, str] | None = None,
) -> CommandResult:
"""Run a command asynchronously.
Args:
*args: Command and arguments
cwd: Working directory (defaults to service cwd)
timeout: Timeout in seconds
env: Environment variables to add
Returns:
CommandResult with stdout, stderr, and returncode
"""
working_dir = cwd or self.cwd
try:
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=working_dir,
env=env,
)
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout,
)
return CommandResult(
returncode=process.returncode or 0,
stdout=stdout.decode("utf-8", errors="replace"),
stderr=stderr.decode("utf-8", errors="replace"),
)
except TimeoutError:
process.kill()
return CommandResult(
returncode=-1,
stdout="",
stderr="Command timed out",
)
except Exception as e:
return CommandResult(
returncode=-1,
stdout="",
stderr=str(e),
)
def run_sync(
self,
*args: str,
cwd: Path | None = None,
timeout: float | None = 30.0,
) -> CommandResult:
"""Run a command synchronously (for use in threads).
Args:
*args: Command and arguments
cwd: Working directory
timeout: Timeout in seconds
Returns:
CommandResult with stdout, stderr, and returncode
"""
working_dir = cwd or self.cwd
try:
result = subprocess.run(
args,
capture_output=True,
cwd=working_dir,
timeout=timeout,
text=True,
)
return CommandResult(
returncode=result.returncode,
stdout=result.stdout,
stderr=result.stderr,
)
except subprocess.TimeoutExpired:
return CommandResult(
returncode=-1,
stdout="",
stderr="Command timed out",
)
except Exception as e:
return CommandResult(
returncode=-1,
stdout="",
stderr=str(e),
)