diff --git a/drivers/goose/Dockerfile b/drivers/goose/Dockerfile index 44b231b..2c250d6 100644 --- a/drivers/goose/Dockerfile +++ b/drivers/goose/Dockerfile @@ -20,16 +20,21 @@ RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/ # Create app directory WORKDIR /app +# Install python dependencies +# This is done before copying scripts for better cache management +RUN pip install --no-cache-dir goose-ai langfuse + # Copy initialization scripts -COPY mai-init.sh /mai-init.sh +COPY mc-init.sh /mc-init.sh COPY entrypoint.sh /entrypoint.sh -COPY mai-driver.yaml /mai-driver.yaml +COPY mc-driver.yaml /mc-driver.yaml +COPY init-status.sh /init-status.sh # Make scripts executable -RUN chmod +x /mai-init.sh /entrypoint.sh +RUN chmod +x /mc-init.sh /entrypoint.sh /init-status.sh -# Install python dependencies -RUN pip install --no-cache-dir goose-ai langfuse +# Set up initialization status check on login +RUN echo '[ -x /init-status.sh ] && /init-status.sh' >> /etc/bash.bashrc # Set up environment ENV PYTHONUNBUFFERED=1 diff --git a/drivers/goose/entrypoint.sh b/drivers/goose/entrypoint.sh index eeee879..f340ef7 100755 --- a/drivers/goose/entrypoint.sh +++ b/drivers/goose/entrypoint.sh @@ -2,7 +2,7 @@ # Entrypoint script for Goose driver # Run the standard initialization script -/mai-init.sh +/mc-init.sh # Start SSH server in the background /usr/sbin/sshd diff --git a/drivers/goose/init-status.sh b/drivers/goose/init-status.sh new file mode 100644 index 0000000..8372817 --- /dev/null +++ b/drivers/goose/init-status.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Script to check and display initialization status + +# Function to display initialization logs +show_init_logs() { + if [ -f "/init.log" ]; then + echo "Displaying initialization logs:" + echo "----------------------------------------" + cat /init.log + echo "----------------------------------------" + else + echo "No initialization logs found." + fi +} + +# Function to follow logs until initialization completes +follow_init_logs() { + if [ ! -f "/init.log" ]; then + echo "No initialization logs found." + return + fi + + echo "Initialization is still in progress. Showing logs:" + echo "----------------------------------------" + tail -f /init.log & + tail_pid=$! + + # Check every second if initialization has completed + while true; do + if [ -f "/init.status" ] && grep -q "INIT_COMPLETE=true" "/init.status"; then + kill $tail_pid 2>/dev/null + echo "----------------------------------------" + echo "Initialization completed." + break + fi + sleep 1 + done +} + +# Check if we're in an interactive shell +if [ -t 0 ]; then + INTERACTIVE=true +else + INTERACTIVE=false +fi + +# Check initialization status +if [ -f "/init.status" ]; then + if grep -q "INIT_COMPLETE=true" "/init.status"; then + echo "MC initialization has completed." + # No longer prompt to show logs when initialization is complete + else + echo "MC initialization is still in progress." + follow_init_logs + fi +else + echo "Cannot determine initialization status." + # Ask if user wants to see logs if they exist (only in interactive mode) + if [ -f "/init.log" ] && [ "$INTERACTIVE" = true ]; then + read -p "Do you want to see initialization logs? (y/n): " show_logs + if [[ "$show_logs" =~ ^[Yy] ]]; then + show_init_logs + fi + fi +fi \ No newline at end of file diff --git a/drivers/goose/mai-driver.yaml b/drivers/goose/mc-driver.yaml similarity index 97% rename from drivers/goose/mai-driver.yaml rename to drivers/goose/mc-driver.yaml index d554dd3..ca62461 100644 --- a/drivers/goose/mai-driver.yaml +++ b/drivers/goose/mc-driver.yaml @@ -4,7 +4,7 @@ version: 1.0.0 maintainer: team@monadical.com init: - pre_command: /mai-init.sh + pre_command: /mc-init.sh command: /entrypoint.sh environment: diff --git a/drivers/goose/mai-init.sh b/drivers/goose/mc-init.sh similarity index 80% rename from drivers/goose/mai-init.sh rename to drivers/goose/mc-init.sh index 48330e7..d40bfc9 100755 --- a/drivers/goose/mai-init.sh +++ b/drivers/goose/mc-init.sh @@ -1,6 +1,13 @@ #!/bin/bash # Standardized initialization script for MC drivers +# Redirect all output to both stdout and the log file +exec > >(tee -a /init.log) 2>&1 + +# Mark initialization as started +echo "=== MC Initialization started at $(date) ===" +echo "INIT_COMPLETE=false" > /init.status + # Project initialization if [ -n "$MC_PROJECT_URL" ]; then echo "Initializing project: $MC_PROJECT_URL" @@ -51,4 +58,8 @@ if [ -n "$LANGFUSE_SECRET_KEY" ] && [ -n "$LANGFUSE_PUBLIC_KEY" ]; then export LANGFUSE_HOST="${LANGFUSE_HOST:-https://api.langfuse.com}" fi -echo "MC driver initialization complete" \ No newline at end of file +echo "MC driver initialization complete" + +# Mark initialization as complete +echo "=== MC Initialization completed at $(date) ===" +echo "INIT_COMPLETE=true" > /init.status \ No newline at end of file diff --git a/mcontainer/cli.py b/mcontainer/cli.py index ef512a7..abca393 100644 --- a/mcontainer/cli.py +++ b/mcontainer/cli.py @@ -104,7 +104,9 @@ def create_session( False, "--no-connect", help="Don't automatically connect to the session" ), no_mount: bool = typer.Option( - False, "--no-mount", help="Don't mount local directory to /app" + False, + "--no-mount", + help="Don't mount local directory to /app (ignored if --project is used)", ), ) -> None: """Create a new MC session""" @@ -216,15 +218,33 @@ def connect_session( def session_logs( session_id: str = typer.Argument(..., help="Session ID to get logs from"), follow: bool = typer.Option(False, "--follow", "-f", help="Follow log output"), + init: bool = typer.Option( + False, "--init", "-i", help="Show initialization logs instead of container logs" + ), ) -> None: """Stream logs from a MC session""" - if follow: - console.print(f"Streaming logs from session {session_id}... (Ctrl+C to exit)") - container_manager.get_session_logs(session_id, follow=True) + if init: + # Show initialization logs + if follow: + console.print( + f"Streaming initialization logs from session {session_id}... (Ctrl+C to exit)" + ) + container_manager.get_init_logs(session_id, follow=True) + else: + logs = container_manager.get_init_logs(session_id) + if logs: + console.print(logs) else: - logs = container_manager.get_session_logs(session_id) - if logs: - console.print(logs) + # Show regular container logs + if follow: + console.print( + f"Streaming logs from session {session_id}... (Ctrl+C to exit)" + ) + container_manager.get_session_logs(session_id, follow=True) + else: + logs = container_manager.get_session_logs(session_id) + if logs: + console.print(logs) @app.command() @@ -255,7 +275,9 @@ def quick_create( False, "--no-connect", help="Don't automatically connect to the session" ), no_mount: bool = typer.Option( - False, "--no-mount", help="Don't mount local directory to /app" + False, + "--no-mount", + help="Don't mount local directory to /app (ignored if a project is specified)", ), ) -> None: """Create a new MC session with a project repository""" diff --git a/mcontainer/container.py b/mcontainer/container.py index 9dcc091..36d1e22 100644 --- a/mcontainer/container.py +++ b/mcontainer/container.py @@ -119,6 +119,10 @@ class ContainerManager: # Prepare environment variables env_vars = environment or {} + # Add project URL to environment if provided + if project: + env_vars["MC_PROJECT_URL"] = project + # Pull image if needed try: self.client.images.get(driver.image) @@ -128,13 +132,19 @@ class ContainerManager: # Set up volume mounts volumes = {} - if mount_local: + # If project URL is provided, don't mount local directory (will clone into /app) + # If no project URL and mount_local is True, mount local directory to /app + if not project and mount_local: # Mount current directory to /app in the container import os current_dir = os.getcwd() volumes[current_dir] = {"bind": "/app", "mode": "rw"} print(f"Mounting local directory {current_dir} to /app") + elif project: + print( + f"Project URL provided - container will clone {project} into /app during initialization" + ) # Create container container = self.client.containers.create( @@ -220,6 +230,8 @@ class ContainerManager: return False # Execute interactive shell in container + # The init-status.sh script will automatically show logs if needed + print(f"Connecting to session {session_id}...") os.system(f"docker exec -it {session.container_id} /bin/bash") return True @@ -345,3 +357,53 @@ class ContainerManager: except DockerException as e: print(f"Error getting session logs: {e}") return None + + def get_init_logs(self, session_id: str, follow: bool = False) -> Optional[str]: + """Get initialization logs from a MC session + + Args: + session_id: The session ID + follow: Whether to follow the logs + + Returns: + The logs as a string, or None if there was an error + """ + try: + sessions = self.list_sessions() + for session in sessions: + if session.id == session_id and session.container_id: + container = self.client.containers.get(session.container_id) + + # Check if initialization is complete + init_complete = False + try: + exit_code, output = container.exec_run( + "grep -q 'INIT_COMPLETE=true' /init.status" + ) + init_complete = exit_code == 0 + except DockerException: + pass + + if follow and not init_complete: + print( + f"Following initialization logs for session {session_id}..." + ) + print("Press Ctrl+C to stop following") + container.exec_run( + "tail -f /init.log", stream=True, demux=True, tty=True + ) + return None + else: + exit_code, output = container.exec_run("cat /init.log") + if exit_code == 0: + return output.decode() + else: + print("No initialization logs found") + return None + + print(f"Session '{session_id}' not found") + return None + + except DockerException as e: + print(f"Error getting initialization logs: {e}") + return None