refactor: rename driver to image, first pass

This commit is contained in:
2025-04-17 17:00:00 -06:00
parent 3799f04c13
commit 51fb79baa3
27 changed files with 290 additions and 306 deletions

View File

@@ -2,19 +2,20 @@
CLI for Monadical Container Tool.
"""
import os
import logging
import os
from typing import List, Optional
import typer
from rich.console import Console
from rich.table import Table
from .config import ConfigManager
from .container import ContainerManager
from .models import SessionStatus
from .user_config import UserConfigManager
from .session import SessionManager
from .mcp import MCPManager
from .models import SessionStatus
from .session import SessionManager
from .user_config import UserConfigManager
# Configure logging - will only show logs if --verbose flag is used
logging.basicConfig(
@@ -25,11 +26,11 @@ logging.basicConfig(
app = typer.Typer(help="Monadical Container Tool", no_args_is_help=True)
session_app = typer.Typer(help="Manage MC sessions", no_args_is_help=True)
driver_app = typer.Typer(help="Manage MC drivers", no_args_is_help=True)
image_app = typer.Typer(help="Manage MC images", no_args_is_help=True)
config_app = typer.Typer(help="Manage MC configuration", no_args_is_help=True)
mcp_app = typer.Typer(help="Manage MCP servers", no_args_is_help=True)
app.add_typer(session_app, name="session", no_args_is_help=True)
app.add_typer(driver_app, name="driver", no_args_is_help=True)
app.add_typer(image_app, name="image", no_args_is_help=True)
app.add_typer(config_app, name="config", no_args_is_help=True)
app.add_typer(mcp_app, name="mcp", no_args_is_help=True)
@@ -82,7 +83,7 @@ def list_sessions() -> None:
table = Table(show_header=True, header_style="bold")
table.add_column("ID")
table.add_column("Name")
table.add_column("Driver")
table.add_column("Image")
table.add_column("Status")
table.add_column("Ports")
@@ -110,7 +111,7 @@ def list_sessions() -> None:
table.add_row(
session.id,
session.name,
session.driver,
session.image,
f"[{status_color}]{status_name}[/{status_color}]",
ports_str,
)
@@ -120,7 +121,7 @@ def list_sessions() -> None:
@session_app.command("create")
def create_session(
driver: Optional[str] = typer.Option(None, "--driver", "-d", help="Driver to use"),
image: Optional[str] = typer.Option(None, "--image", "-i", help="Image to use"),
path_or_url: Optional[str] = typer.Argument(
None,
help="Local directory path to mount or repository URL to clone",
@@ -181,11 +182,13 @@ def create_session(
target_gid = gid if gid is not None else os.getgid()
console.print(f"Using UID: {target_uid}, GID: {target_gid}")
# Use default driver from user configuration
if not driver:
driver = user_config.get(
"defaults.driver", config_manager.config.defaults.get("driver", "goose")
# Use default image from user configuration
if not image:
image_name = user_config.get(
"defaults.image", config_manager.config.defaults.get("image", "goose")
)
else:
image_name = image
# Start with environment variables from user configuration
environment = user_config.get_environment_variables()
@@ -254,7 +257,7 @@ def create_session(
for host_path, mount_info in volume_mounts.items():
console.print(f" {host_path} -> {mount_info['bind']}")
with console.status(f"Creating session with driver '{driver}'..."):
with console.status(f"Creating session with image '{image_name}'..."):
# If path_or_url is a local directory, we should mount it
# If it's a Git URL or doesn't exist, handle accordingly
mount_local = False
@@ -262,7 +265,7 @@ def create_session(
mount_local = True
session = container_manager.create_session(
driver_name=driver,
image_name=image_name,
project=path_or_url,
project_name=project,
environment=environment,
@@ -282,7 +285,7 @@ def create_session(
if session:
console.print("[green]Session created successfully![/green]")
console.print(f"Session ID: {session.id}")
console.print(f"Driver: {session.driver}")
console.print(f"Image: {session.image}")
if session.ports:
console.print("Ports:")
@@ -402,13 +405,13 @@ def session_logs(
console.print(logs)
@driver_app.command("list")
def list_drivers() -> None:
"""List available MC drivers"""
drivers = config_manager.list_drivers()
@image_app.command("list")
def list_images() -> None:
"""List available MC images"""
images = config_manager.list_images()
if not drivers:
console.print("No drivers found")
if not images:
console.print("No images found")
return
table = Table(show_header=True, header_style="bold")
@@ -418,92 +421,92 @@ def list_drivers() -> None:
table.add_column("Maintainer")
table.add_column("Image")
for name, driver in drivers.items():
for name, image in images.items():
table.add_row(
driver.name,
driver.description,
driver.version,
driver.maintainer,
driver.image,
image.name,
image.description,
image.version,
image.maintainer,
image.image,
)
console.print(table)
@driver_app.command("build")
def build_driver(
driver_name: str = typer.Argument(..., help="Driver name to build"),
@image_app.command("build")
def build_image(
image_name: str = typer.Argument(..., help="Image name to build"),
tag: str = typer.Option("latest", "--tag", "-t", help="Image tag"),
push: bool = typer.Option(
False, "--push", "-p", help="Push image to registry after building"
),
) -> None:
"""Build a driver Docker image"""
# Get driver path
driver_path = config_manager.get_driver_path(driver_name)
if not driver_path:
console.print(f"[red]Driver '{driver_name}' not found[/red]")
"""Build an image Docker image"""
# Get image path
image_path = config_manager.get_image_path(image_name)
if not image_path:
console.print(f"[red]Image '{image_name}' not found[/red]")
return
# Check if Dockerfile exists
dockerfile_path = driver_path / "Dockerfile"
dockerfile_path = image_path / "Dockerfile"
if not dockerfile_path.exists():
console.print(f"[red]Dockerfile not found in {driver_path}[/red]")
console.print(f"[red]Dockerfile not found in {image_path}[/red]")
return
# Build image name
image_name = f"monadical/mc-{driver_name}:{tag}"
docker_image_name = f"monadical/mc-{image_name}:{tag}"
# Build the image
with console.status(f"Building image {image_name}..."):
result = os.system(f"cd {driver_path} && docker build -t {image_name} .")
with console.status(f"Building image {docker_image_name}..."):
result = os.system(f"cd {image_path} && docker build -t {docker_image_name} .")
if result != 0:
console.print("[red]Failed to build driver image[/red]")
console.print("[red]Failed to build image[/red]")
return
console.print(f"[green]Successfully built image: {image_name}[/green]")
console.print(f"[green]Successfully built image: {docker_image_name}[/green]")
# Push if requested
if push:
with console.status(f"Pushing image {image_name}..."):
result = os.system(f"docker push {image_name}")
with console.status(f"Pushing image {docker_image_name}..."):
result = os.system(f"docker push {docker_image_name}")
if result != 0:
console.print("[red]Failed to push driver image[/red]")
console.print("[red]Failed to push image[/red]")
return
console.print(f"[green]Successfully pushed image: {image_name}[/green]")
console.print(f"[green]Successfully pushed image: {docker_image_name}[/green]")
@driver_app.command("info")
def driver_info(
driver_name: str = typer.Argument(..., help="Driver name to get info for"),
@image_app.command("info")
def image_info(
image_name: str = typer.Argument(..., help="Image name to get info for"),
) -> None:
"""Show detailed information about a driver"""
driver = config_manager.get_driver(driver_name)
if not driver:
console.print(f"[red]Driver '{driver_name}' not found[/red]")
"""Show detailed information about an image"""
image = config_manager.get_image(image_name)
if not image:
console.print(f"[red]Image '{image_name}' not found[/red]")
return
console.print(f"[bold]Driver: {driver.name}[/bold]")
console.print(f"Description: {driver.description}")
console.print(f"Version: {driver.version}")
console.print(f"Maintainer: {driver.maintainer}")
console.print(f"Image: {driver.image}")
console.print(f"[bold]Image: {image.name}[/bold]")
console.print(f"Description: {image.description}")
console.print(f"Version: {image.version}")
console.print(f"Maintainer: {image.maintainer}")
console.print(f"Docker Image: {image.image}")
if driver.ports:
if image.ports:
console.print("\n[bold]Ports:[/bold]")
for port in driver.ports:
for port in image.ports:
console.print(f" {port}")
# Get driver path
driver_path = config_manager.get_driver_path(driver_name)
if driver_path:
console.print(f"\n[bold]Path:[/bold] {driver_path}")
# Get image path
image_path = config_manager.get_image_path(image_name)
if image_path:
console.print(f"\n[bold]Path:[/bold] {image_path}")
# Check for README
readme_path = driver_path / "README.md"
readme_path = image_path / "README.md"
if readme_path.exists():
console.print("\n[bold]README:[/bold]")
with open(readme_path, "r") as f:

View File

@@ -1,29 +1,30 @@
import yaml
from pathlib import Path
from typing import Dict, Optional
from .models import Config, Driver
import yaml
from .models import Config, Image
DEFAULT_CONFIG_DIR = Path.home() / ".config" / "mc"
DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml"
DEFAULT_DRIVERS_DIR = Path.home() / ".config" / "mc" / "drivers"
DEFAULT_IMAGES_DIR = Path.home() / ".config" / "mc" / "images"
PROJECT_ROOT = Path(__file__).parent.parent
BUILTIN_DRIVERS_DIR = Path(__file__).parent / "drivers" # mcontainer/drivers
BUILTIN_IMAGES_DIR = Path(__file__).parent / "images" # mcontainer/images
# Dynamically loaded from drivers directory at runtime
DEFAULT_DRIVERS = {}
# Dynamically loaded from images directory at runtime
DEFAULT_IMAGES = {}
class ConfigManager:
def __init__(self, config_path: Optional[Path] = None):
self.config_path = config_path or DEFAULT_CONFIG_FILE
self.config_dir = self.config_path.parent
self.drivers_dir = DEFAULT_DRIVERS_DIR
self.images_dir = DEFAULT_IMAGES_DIR
self.config = self._load_or_create_config()
# Always load package drivers on initialization
# Always load package images on initialization
# These are separate from the user config
self.builtin_drivers = self._load_package_drivers()
self.builtin_images = self._load_package_images()
def _load_or_create_config(self) -> Config:
"""Load existing config or create a new one with defaults"""
@@ -38,10 +39,10 @@ class ConfigManager:
defaults=config_data.get("defaults", {}),
)
# Add drivers
if "drivers" in config_data:
for driver_name, driver_data in config_data["drivers"].items():
config.drivers[driver_name] = Driver.model_validate(driver_data)
# Add images
if "images" in config_data:
for image_name, image_data in config_data["images"].items():
config.images[image_name] = Image.model_validate(image_data)
return config
except Exception as e:
@@ -53,16 +54,16 @@ class ConfigManager:
def _create_default_config(self) -> Config:
"""Create a default configuration"""
self.config_dir.mkdir(parents=True, exist_ok=True)
self.drivers_dir.mkdir(parents=True, exist_ok=True)
self.images_dir.mkdir(parents=True, exist_ok=True)
# Initial config without drivers
# Initial config without images
config = Config(
docker={
"socket": "/var/run/docker.sock",
"network": "mc-network",
},
defaults={
"driver": "goose",
"image": "goose",
},
)
@@ -83,92 +84,90 @@ class ConfigManager:
with open(self.config_path, "w") as f:
yaml.dump(config_dict, f)
def get_driver(self, name: str) -> Optional[Driver]:
"""Get a driver by name, checking builtin drivers first, then user-configured ones"""
# Check builtin drivers first (package drivers take precedence)
if name in self.builtin_drivers:
return self.builtin_drivers[name]
# If not found, check user-configured drivers
return self.config.drivers.get(name)
def get_image(self, name: str) -> Optional[Image]:
"""Get an image by name, checking builtin images first, then user-configured ones"""
# Check builtin images first (package images take precedence)
if name in self.builtin_images:
return self.builtin_images[name]
# If not found, check user-configured images
return self.config.images.get(name)
def list_drivers(self) -> Dict[str, Driver]:
"""List all available drivers (both builtin and user-configured)"""
# Start with user config drivers
all_drivers = dict(self.config.drivers)
def list_images(self) -> Dict[str, Image]:
"""List all available images (both builtin and user-configured)"""
# Start with user config images
all_images = dict(self.config.images)
# Add builtin drivers, overriding any user drivers with the same name
# This ensures that package-provided drivers always take precedence
all_drivers.update(self.builtin_drivers)
# Add builtin images, overriding any user images with the same name
# This ensures that package-provided images always take precedence
all_images.update(self.builtin_images)
return all_drivers
return all_images
# Session management has been moved to SessionManager in session.py
def load_driver_from_dir(self, driver_dir: Path) -> Optional[Driver]:
"""Load a driver configuration from a directory"""
# Try with mc-driver.yaml first (new format), then mai-driver.yaml (legacy)
yaml_path = driver_dir / "mc-driver.yaml"
def load_image_from_dir(self, image_dir: Path) -> Optional[Image]:
"""Load an image configuration from a directory"""
# Check for image config file
yaml_path = image_dir / "mc-image.yaml"
if not yaml_path.exists():
yaml_path = driver_dir / "mai-driver.yaml" # Backward compatibility
if not yaml_path.exists():
return None
return None
try:
with open(yaml_path, "r") as f:
driver_data = yaml.safe_load(f)
image_data = yaml.safe_load(f)
# Extract required fields
if not all(
k in driver_data
k in image_data
for k in ["name", "description", "version", "maintainer"]
):
print(f"Driver config {yaml_path} missing required fields")
print(f"Image config {yaml_path} missing required fields")
return None
# Use Driver.model_validate to handle all fields from YAML
# This will map all fields according to the Driver model structure
# Use Image.model_validate to handle all fields from YAML
# This will map all fields according to the Image model structure
try:
# Ensure image field is set if not in YAML
if "image" not in driver_data:
driver_data["image"] = f"monadical/mc-{driver_data['name']}:latest"
if "image" not in image_data:
image_data["image"] = f"monadical/mc-{image_data['name']}:latest"
driver = Driver.model_validate(driver_data)
return driver
image = Image.model_validate(image_data)
return image
except Exception as validation_error:
print(
f"Error validating driver data from {yaml_path}: {validation_error}"
f"Error validating image data from {yaml_path}: {validation_error}"
)
return None
except Exception as e:
print(f"Error loading driver from {yaml_path}: {e}")
print(f"Error loading image from {yaml_path}: {e}")
return None
def _load_package_drivers(self) -> Dict[str, Driver]:
"""Load all package drivers from the mcontainer/drivers directory"""
drivers = {}
def _load_package_images(self) -> Dict[str, Image]:
"""Load all package images from the mcontainer/images directory"""
images = {}
if not BUILTIN_DRIVERS_DIR.exists():
return drivers
if not BUILTIN_IMAGES_DIR.exists():
return images
# Search for mc-driver.yaml files in each subdirectory
for driver_dir in BUILTIN_DRIVERS_DIR.iterdir():
if driver_dir.is_dir():
driver = self.load_driver_from_dir(driver_dir)
if driver:
drivers[driver.name] = driver
# Search for mc-image.yaml files in each subdirectory
for image_dir in BUILTIN_IMAGES_DIR.iterdir():
if image_dir.is_dir():
image = self.load_image_from_dir(image_dir)
if image:
images[image.name] = image
return drivers
return images
def get_driver_path(self, driver_name: str) -> Optional[Path]:
"""Get the directory path for a driver"""
# Check package drivers first (these are the bundled ones)
package_path = BUILTIN_DRIVERS_DIR / driver_name
def get_image_path(self, image_name: str) -> Optional[Path]:
"""Get the directory path for an image"""
# Check package images first (these are the bundled ones)
package_path = BUILTIN_IMAGES_DIR / image_name
if package_path.exists() and package_path.is_dir():
return package_path
# Then check user drivers
user_path = self.drivers_dir / driver_name
# Then check user images
user_path = self.images_dir / image_name
if user_path.exists() and user_path.is_dir():
return user_path

View File

@@ -1,18 +1,19 @@
import concurrent.futures
import hashlib
import logging
import os
import pathlib
import sys
import uuid
import docker
import hashlib
import pathlib
import concurrent.futures
import logging
from typing import Dict, List, Optional, Tuple
import docker
from docker.errors import DockerException, ImageNotFound
from .models import Session, SessionStatus
from .config import ConfigManager
from .session import SessionManager
from .mcp import MCPManager
from .models import Session, SessionStatus
from .session import SessionManager
from .user_config import UserConfigManager
# Configure logging
@@ -109,7 +110,7 @@ class ContainerManager:
session = Session(
id=session_id,
name=labels.get("mc.session.name", f"mc-{session_id}"),
driver=labels.get("mc.driver", "unknown"),
image=labels.get("mc.image", "unknown"),
status=status,
container_id=container_id,
)
@@ -136,7 +137,7 @@ class ContainerManager:
def create_session(
self,
driver_name: str,
image_name: str,
project: Optional[str] = None,
project_name: Optional[str] = None,
environment: Optional[Dict[str, str]] = None,
@@ -155,7 +156,7 @@ class ContainerManager:
"""Create a new MC session
Args:
driver_name: The name of the driver to use
image_name: The name of the image to use
project: Optional project repository URL or local directory path
project_name: Optional explicit project name for configuration persistence
environment: Optional environment variables
@@ -170,10 +171,10 @@ class ContainerManager:
ssh: Whether to start the SSH server in the container (default: False)
"""
try:
# Validate driver exists
driver = self.config_manager.get_driver(driver_name)
if not driver:
print(f"Driver '{driver_name}' not found")
# Validate image exists
image = self.config_manager.get_image(image_name)
if not image:
print(f"Image '{image_name}' not found")
return None
# Generate session ID and name
@@ -210,10 +211,10 @@ class ContainerManager:
# Pull image if needed
try:
self.client.images.get(driver.image)
self.client.images.get(image.image)
except ImageNotFound:
print(f"Pulling image {driver.image}...")
self.client.images.pull(driver.image)
print(f"Pulling image {image.image}...")
self.client.images.pull(image.image)
# Set up volume mounts
session_volumes = {}
@@ -274,13 +275,13 @@ class ContainerManager:
# Add environment variables for config path
env_vars["MC_CONFIG_DIR"] = "/mc-config"
env_vars["MC_DRIVER_CONFIG_DIR"] = f"/mc-config/{driver_name}"
env_vars["MC_IMAGE_CONFIG_DIR"] = f"/mc-config/{image_name}"
# Create driver-specific config directories and set up direct volume mounts
if driver.persistent_configs:
# Create image-specific config directories and set up direct volume mounts
if image.persistent_configs:
persistent_links_data = [] # To store "source:target" pairs for symlinks
print("Setting up persistent configuration directories:")
for config in driver.persistent_configs:
for config in image.persistent_configs:
# Get target directory path on host
target_dir = project_config_path / config.target.removeprefix(
"/mc-config/"
@@ -494,7 +495,7 @@ class ContainerManager:
# Create container
container = self.client.containers.create(
image=driver.image,
image=image.image,
name=session_name,
hostname=session_name,
detach=True,
@@ -506,7 +507,7 @@ class ContainerManager:
"mc.session": "true",
"mc.session.id": session_id,
"mc.session.name": session_name,
"mc.driver": driver_name,
"mc.image": image_name,
"mc.project": project or "",
"mc.project_name": project_name or "",
"mc.mcps": ",".join(mcp_names) if mcp_names else "",
@@ -514,7 +515,7 @@ class ContainerManager:
network=network_list[0], # Connect to the first network initially
command=container_command, # Set the command
entrypoint=entrypoint, # Set the entrypoint (might be None)
ports={f"{port}/tcp": None for port in driver.ports},
ports={f"{port}/tcp": None for port in image.ports},
)
# Start container
@@ -613,7 +614,7 @@ class ContainerManager:
session = Session(
id=session_id,
name=session_name,
driver=driver_name,
image=image_name,
status=SessionStatus.RUNNING,
container_id=container.id,
ports=ports,

View File

@@ -1,28 +0,0 @@
"""
Base driver implementation for MAI
"""
from typing import Dict, Optional
from ..models import Driver
class DriverManager:
"""Manager for MAI drivers"""
@staticmethod
def get_default_drivers() -> Dict[str, Driver]:
"""Get the default built-in drivers"""
from ..config import DEFAULT_DRIVERS
return DEFAULT_DRIVERS
@staticmethod
def get_driver_metadata(driver_name: str) -> Optional[Dict]:
"""Get metadata for a specific driver"""
from ..config import DEFAULT_DRIVERS
if driver_name in DEFAULT_DRIVERS:
return DEFAULT_DRIVERS[driver_name].model_dump()
return None

View File

@@ -0,0 +1,3 @@
"""
MAI container image management
"""

28
mcontainer/images/base.py Normal file
View File

@@ -0,0 +1,28 @@
"""
Base image implementation for MAI
"""
from typing import Dict, Optional
from ..models import Image
class ImageManager:
"""Manager for MAI images"""
@staticmethod
def get_default_images() -> Dict[str, Image]:
"""Get the default built-in images"""
from ..config import DEFAULT_IMAGES
return DEFAULT_IMAGES
@staticmethod
def get_image_metadata(image_name: str) -> Optional[Dict]:
"""Get metadata for a specific image"""
from ..config import DEFAULT_IMAGES
if image_name in DEFAULT_IMAGES:
return DEFAULT_IMAGES[image_name].model_dump()
return None

View File

@@ -43,7 +43,7 @@ WORKDIR /app
# Copy initialization scripts
COPY mc-init.sh /mc-init.sh
COPY entrypoint.sh /entrypoint.sh
COPY mc-driver.yaml /mc-driver.yaml
COPY mc-image.yaml /mc-image.yaml
COPY init-status.sh /init-status.sh
COPY update-goose-config.py /usr/local/bin/update-goose-config.py

View File

@@ -1,6 +1,6 @@
# Goose Driver for MC
# Goose Image for MC
This driver provides a containerized environment for running [Goose](https://goose.ai).
This image provides a containerized environment for running [Goose](https://goose.ai).
## Features
@@ -23,7 +23,7 @@ This driver provides a containerized environment for running [Goose](https://goo
## Build
To build this driver:
To build this image:
```bash
cd drivers/goose
@@ -33,9 +33,9 @@ docker build -t monadical/mc-goose:latest .
## Usage
```bash
# Create a new session with this driver
# Create a new session with this image
mc session create --driver goose
# Create with project repository
mc session create --driver goose --project github.com/username/repo
```
```

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Entrypoint script for Goose driver
# Entrypoint script for Goose image
# Executes the standard initialization script, which handles user setup,
# service startup (like sshd), and switching to the non-root user
# before running the container's command (CMD).

View File

@@ -60,4 +60,4 @@ persistent_configs:
- source: "/home/mcuser/.config/goose"
target: "/mc-config/goose-config"
type: "directory"
description: "Goose configuration"
description: "Goose configuration"

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# Standardized initialization script for MC drivers
# Standardized initialization script for MC images
# Redirect all output to both stdout and the log file
exec > >(tee -a /init.log) 2>&1
@@ -117,7 +117,7 @@ if [ ! -d "/mc-config" ]; then
chown $MC_USER_ID:$MC_GROUP_ID /mc-config
fi
# Create symlinks for persistent configurations defined in the driver
# Create symlinks for persistent configurations defined in the image
if [ -n "$MC_PERSISTENT_LINKS" ]; then
echo "Creating persistent configuration symlinks..."
# Split by semicolon

View File

@@ -2,14 +2,15 @@
MCP (Model Control Protocol) server management for Monadical Container.
"""
import os
import docker
import logging
import os
import tempfile
from typing import Dict, List, Optional, Any
from typing import Any, Dict, List, Optional
import docker
from docker.errors import DockerException, ImageNotFound, NotFound
from .models import MCPStatus, RemoteMCP, DockerMCP, ProxyMCP, MCPContainer
from .models import DockerMCP, MCPContainer, MCPStatus, ProxyMCP, RemoteMCP
from .user_config import UserConfigManager
# Configure logging

View File

@@ -1,5 +1,6 @@
from enum import Enum
from typing import Dict, List, Optional, Union, Any
from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel, Field
@@ -17,7 +18,7 @@ class MCPStatus(str, Enum):
FAILED = "failed"
class DriverEnvironmentVariable(BaseModel):
class ImageEnvironmentVariable(BaseModel):
name: str
description: str
required: bool = False
@@ -37,19 +38,19 @@ class VolumeMount(BaseModel):
description: str = ""
class DriverInit(BaseModel):
class ImageInit(BaseModel):
pre_command: Optional[str] = None
command: str
class Driver(BaseModel):
class Image(BaseModel):
name: str
description: str
version: str
maintainer: str
image: str
init: Optional[DriverInit] = None
environment: List[DriverEnvironmentVariable] = []
init: Optional[ImageInit] = None
environment: List[ImageEnvironmentVariable] = []
ports: List[int] = []
volumes: List[VolumeMount] = []
persistent_configs: List[PersistentConfig] = []
@@ -97,7 +98,7 @@ class MCPContainer(BaseModel):
class Session(BaseModel):
id: str
name: str
driver: str
image: str
status: SessionStatus
container_id: Optional[str] = None
ports: Dict[int, int] = Field(default_factory=dict)
@@ -105,7 +106,7 @@ class Session(BaseModel):
class Config(BaseModel):
docker: Dict[str, str] = Field(default_factory=dict)
drivers: Dict[str, Driver] = Field(default_factory=dict)
images: Dict[str, Image] = Field(default_factory=dict)
defaults: Dict[str, object] = Field(
default_factory=dict
) # Can store strings, booleans, or other values

View File

@@ -1,14 +0,0 @@
"""
MC Service - Container Management Web Service
(This is a placeholder for Phase 2)
"""
def main() -> None:
"""Run the MC service"""
print("MC Service - Container Management Web Service")
print("This feature will be implemented in Phase 2")
if __name__ == "__main__":
main()

View File

@@ -3,10 +3,11 @@ Session storage management for Monadical Container Tool.
"""
import os
import yaml
from pathlib import Path
from typing import Dict, Optional
import yaml
DEFAULT_SESSIONS_FILE = Path.home() / ".config" / "mc" / "sessions.yaml"

View File

@@ -3,9 +3,10 @@ User configuration manager for Monadical Container Tool.
"""
import os
import yaml
from pathlib import Path
from typing import Any, Dict, Optional, List, Tuple
from typing import Any, Dict, List, Optional, Tuple
import yaml
# Define the environment variable mappings
ENV_MAPPINGS = {
@@ -89,7 +90,7 @@ class UserConfigManager:
"""Get the default configuration."""
return {
"defaults": {
"driver": "goose",
"image": "goose",
"connect": True,
"mount_local": True,
"networks": [], # Default networks to connect to (besides mc-network)
@@ -133,7 +134,7 @@ class UserConfigManager:
"""Get a configuration value by dot-notation path.
Args:
key_path: The configuration path (e.g., "defaults.driver")
key_path: The configuration path (e.g., "defaults.image")
default: The default value to return if not found
Returns:
@@ -165,7 +166,7 @@ class UserConfigManager:
"""Set a configuration value by dot-notation path.
Args:
key_path: The configuration path (e.g., "defaults.driver")
key_path: The configuration path (e.g., "defaults.image")
value: The value to set
"""
# Handle shorthand service paths (e.g., "langfuse.url")