Public/Invoke-DClaude.ps1

function Invoke-DClaude {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'ByImage', Mandatory)]
        [string]$Image,

        [Parameter(ParameterSetName = 'ByImageKey', Mandatory)]
        [string]$ImageKey,

        [Parameter()]
        [string]$Path = $PWD,

        [Parameter()]
        [string]$ClaudeConfigPath = (Join-Path $HOME '.claude'),

        [Parameter(ValueFromRemainingArguments)]
        [string[]]$ClaudeArgs
    )

    # Validate Docker environment and detect container OS
    $containerOS = Test-DockerAvailable
    if ($containerOS -notin @('windows', 'linux')) {
        throw "Unsupported Docker OS type '$containerOS'. Only 'windows' and 'linux' are supported."
    }

    # Resolve working directory to absolute path
    if (-not (Test-Path -Path $Path -PathType Container)) {
        throw "Path '$Path' does not exist or is not a directory."
    }
    $resolvedPath = (Resolve-Path -Path $Path).Path

    # Load project config
    $config = Get-DClaudeConfig -Path $resolvedPath

    # Determine image tag and image-level volumes
    $imageTag = $null
    $imageVolumes = @()
    switch ($PSCmdlet.ParameterSetName) {
        'ByImage' {
            $imageTag = $Image
        }
        'ByImageKey' {
            $resolved = Resolve-ImageKey $ImageKey $containerOS
            $imageTag = $resolved.tag
            $imageVolumes = $resolved.volumes
        }
        'Default' {
            if ($config -and $config.image) {
                $imageTag = $config.image
            }
            elseif ($config -and $config.imageKey) {
                $resolved = Resolve-ImageKey $config.imageKey $containerOS
                $imageTag = $resolved.tag
                $imageVolumes = $resolved.volumes
            }
        }
    }

    if (-not $imageTag) {
        throw "No image specified. Pass -Image, -ImageKey, or set 'image' or 'imageKey' in your project .dclaude/settings.json."
    }

    # Set container paths based on OS type
    if ($containerOS -eq 'windows') {
        $containerWorkspace = 'C:/workspace'
        $containerClaude = 'C:/Users/ContainerUser/.claude'
    }
    else {
        $containerWorkspace = '/workspace'
        $containerClaude = '/mnt/host-claude'
    }

    # Build docker run arguments
    $dockerArgs = @(
        'run', '-it', '--rm'
        '-v', "${resolvedPath}:${containerWorkspace}"
        '-w', $containerWorkspace
    )

    # Mount Claude config directory and settings file
    if (Test-Path $ClaudeConfigPath) {
        $dockerArgs += '-v'
        $dockerArgs += "${ClaudeConfigPath}:${containerClaude}:rw"
    }
    else {
        Write-Warning "Claude config path '$ClaudeConfigPath' not found. Container will start without Claude configuration."
    }

    # Mount .claude.json (lives in home dir, separate from .claude/ directory)
    $claudeJsonPath = Join-Path (Split-Path $ClaudeConfigPath) '.claude.json'
    if (Test-Path $claudeJsonPath) {
        $dockerArgs += '-v'
        $dockerArgs += "${claudeJsonPath}:/mnt/host-claude.json:ro"
    }

    # Mount volumes from image config and project config
    $allVolumes = @()
    if ($imageVolumes.Count -gt 0) {
        $allVolumes += $imageVolumes
    }
    if ($config -and $config.volumes) {
        $allVolumes += @($config.volumes)
    }
    foreach ($vol in $allVolumes) {
        $expanded = [Environment]::ExpandEnvironmentVariables($vol)
        # Enforce read-only unless the volume spec already includes a mode.
        # Use a regex that accounts for Windows drive letters (e.g. C:/host:C:/container).
        if ($expanded -notmatch ':(ro|rw)$') {
            $expanded = "${expanded}:ro"
        }
        $dockerArgs += '-v'
        $dockerArgs += $expanded
    }

    # Pass API key if set
    if ($env:ANTHROPIC_API_KEY) {
        $dockerArgs += '-e'
        $dockerArgs += 'ANTHROPIC_API_KEY'
    }

    # Add image tag
    $dockerArgs += $imageTag

    # Add any extra arguments for claude
    if ($ClaudeArgs -and $ClaudeArgs.Count -gt 0) {
        $dockerArgs += $ClaudeArgs
    }

    # Launch the container
    & docker @dockerArgs
}