Refactor cockpit to use claude-code-tools-local package
Architecture improvements: - Import TmuxCLIController from installed claude-code-tools-local - DockerTmuxAdapter extends TmuxCLIController for Docker execution - Override _run_tmux_command() to prepend 'docker exec' to commands - Backward compatibility alias: DockerTmuxController = DockerTmuxAdapter This enables: - Pulling upstream improvements from claude-code-tools - Maintaining Docker-specific functionality - Clean inheritance-based architecture Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,10 @@ Key features:
|
|||||||
- tmux for human attachment when needed
|
- tmux for human attachment when needed
|
||||||
- Multi-turn conversation support
|
- Multi-turn conversation support
|
||||||
|
|
||||||
Uses claude-code-tools TmuxCLIController pattern for robust session management.
|
Architecture:
|
||||||
|
Imports TmuxCLIController from claude-code-tools-local package and extends
|
||||||
|
it with DockerTmuxAdapter for Docker-containerized tmux sessions.
|
||||||
|
This enables importing upstream improvements while adding Docker support.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
luzia cockpit start <project> Start cockpit container
|
luzia cockpit start <project> Start cockpit container
|
||||||
@@ -31,40 +34,57 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Tuple, List
|
from typing import Dict, Optional, Tuple, List
|
||||||
|
|
||||||
|
# Import TmuxCLIController from installed claude-code-tools-local package
|
||||||
|
from claude_code_tools.tmux_cli_controller import TmuxCLIController
|
||||||
|
|
||||||
class DockerTmuxController:
|
|
||||||
|
class DockerTmuxAdapter(TmuxCLIController):
|
||||||
"""
|
"""
|
||||||
Tmux controller that executes commands inside a Docker container.
|
Docker adapter for TmuxCLIController.
|
||||||
|
|
||||||
Based on claude-code-tools TmuxCLIController pattern, adapted for
|
Extends TmuxCLIController to execute tmux commands inside a Docker container
|
||||||
Docker containerized tmux sessions.
|
rather than on the host system. This enables:
|
||||||
|
- Inheriting all TmuxCLIController methods automatically
|
||||||
|
- Pulling upstream improvements from claude-code-tools
|
||||||
|
- Adding Docker-specific functionality
|
||||||
|
|
||||||
|
The key override is _run_tmux_command() which prepends 'docker exec' to all
|
||||||
|
tmux commands.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, container_name: str, tmux_session: str = "agent", tmux_window: str = "main"):
|
def __init__(self, container_name: str, tmux_session: str = "agent", tmux_window: str = "main"):
|
||||||
"""
|
"""
|
||||||
Initialize controller for a Docker container's tmux session.
|
Initialize adapter for a Docker container's tmux session.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
container_name: Docker container name
|
container_name: Docker container name
|
||||||
tmux_session: tmux session name inside container (default: agent)
|
tmux_session: tmux session name inside container (default: agent)
|
||||||
tmux_window: tmux window name (default: main)
|
tmux_window: tmux window name (default: main)
|
||||||
"""
|
"""
|
||||||
|
super().__init__(session_name=tmux_session, window_name=tmux_window)
|
||||||
self.container_name = container_name
|
self.container_name = container_name
|
||||||
self.tmux_session = tmux_session
|
self.tmux_session = tmux_session
|
||||||
self.tmux_window = tmux_window
|
self.tmux_window = tmux_window
|
||||||
self.target = f"{tmux_session}:{tmux_window}"
|
self.target = f"{tmux_session}:{tmux_window}"
|
||||||
|
|
||||||
def _run_tmux(self, args: List[str]) -> Tuple[str, int]:
|
def _run_tmux_command(self, command: List[str]) -> Tuple[str, int]:
|
||||||
"""
|
"""
|
||||||
Run a tmux command inside the Docker container.
|
Override: Run tmux command inside the Docker container.
|
||||||
|
|
||||||
|
This is the key override that makes all inherited TmuxCLIController
|
||||||
|
methods work inside Docker. Instead of running 'tmux <cmd>', we run
|
||||||
|
'docker exec <container> tmux <cmd>'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
args: tmux command arguments (without 'tmux' prefix)
|
command: List of command components (without 'tmux' prefix)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (stdout, return_code)
|
Tuple of (output, exit_code)
|
||||||
"""
|
"""
|
||||||
cmd = ["docker", "exec", self.container_name, "tmux"] + args
|
if not self.is_container_running():
|
||||||
|
return "", 1
|
||||||
|
|
||||||
|
cmd = ["docker", "exec", self.container_name, "tmux"] + command
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
return result.stdout.strip(), result.returncode
|
return result.stdout.strip(), result.returncode
|
||||||
|
|
||||||
@@ -92,14 +112,14 @@ class DockerTmuxController:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Send text first
|
# Send text first
|
||||||
_, code = self._run_tmux(["send-keys", "-t", self.target, text])
|
_, code = self._run_tmux_command(["send-keys", "-t", self.target, text])
|
||||||
if code != 0:
|
if code != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if enter:
|
if enter:
|
||||||
if delay_enter > 0:
|
if delay_enter > 0:
|
||||||
time.sleep(delay_enter)
|
time.sleep(delay_enter)
|
||||||
self._run_tmux(["send-keys", "-t", self.target, "Enter"])
|
self._run_tmux_command(["send-keys", "-t", self.target, "Enter"])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -116,7 +136,7 @@ class DockerTmuxController:
|
|||||||
if not self.is_container_running():
|
if not self.is_container_running():
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
output, code = self._run_tmux([
|
output, code = self._run_tmux_command([
|
||||||
"capture-pane", "-t", self.target, "-p", "-S", f"-{lines}"
|
"capture-pane", "-t", self.target, "-p", "-S", f"-{lines}"
|
||||||
])
|
])
|
||||||
return output if code == 0 else ""
|
return output if code == 0 else ""
|
||||||
@@ -265,16 +285,20 @@ class DockerTmuxController:
|
|||||||
"""Send Ctrl+C to interrupt running command."""
|
"""Send Ctrl+C to interrupt running command."""
|
||||||
if not self.is_container_running():
|
if not self.is_container_running():
|
||||||
return False
|
return False
|
||||||
_, code = self._run_tmux(["send-keys", "-t", self.target, "C-c"])
|
_, code = self._run_tmux_command(["send-keys", "-t", self.target, "C-c"])
|
||||||
return code == 0
|
return code == 0
|
||||||
|
|
||||||
def clear_pane(self) -> bool:
|
def clear_pane(self) -> bool:
|
||||||
"""Clear the pane screen."""
|
"""Clear the pane screen."""
|
||||||
if not self.is_container_running():
|
if not self.is_container_running():
|
||||||
return False
|
return False
|
||||||
_, code = self._run_tmux(["send-keys", "-t", self.target, "C-l"])
|
_, code = self._run_tmux_command(["send-keys", "-t", self.target, "C-l"])
|
||||||
return code == 0
|
return code == 0
|
||||||
|
|
||||||
|
|
||||||
|
# Backward compatibility alias
|
||||||
|
DockerTmuxController = DockerTmuxAdapter
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
COCKPIT_IMAGE = "luzia-cockpit:latest"
|
COCKPIT_IMAGE = "luzia-cockpit:latest"
|
||||||
COCKPIT_PREFIX = "luzia-cockpit-"
|
COCKPIT_PREFIX = "luzia-cockpit-"
|
||||||
|
|||||||
Reference in New Issue
Block a user