Public/Install-Chatmode.ps1

function Install-Chatmode {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true, Position = 0)]
        [string[]]$ChatModeName,
        # Accept FileName from pipeline (e.g. produced by Get-Chatmode)
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$FileName,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$Owner,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [string]$RepoName,
        [string]$Destination = ".github/chatmodes"
    )

    begin {
        if (-not (Test-Path $Destination)) {
            New-Item -ItemType Directory -Path $Destination -Force | Out-Null
        }
    }
    process {
        # Input can come in three ways:
        # 1) Direct parameters: -ChatModeName 'a','b'
        # 2) Property-bound pipeline: Get-Chatmode | Install-Chatmode (properties bind to parameters)
        # 3) Full piped object: Get-Chatmode | Install-Chatmode (object available as $PSItem)

        # Build per-invocation values by preferring properties from a piped object when present.
        $invFileName = $null
        $invOwner = $null
        $invRepoName = $null
        $invChatModeNames = @()

        if ($PSItem -and ($PSItem -is [PSObject])) {
            # Extract common properties if the caller piped a full object
            if ($PSItem.PSObject.Properties.Match('FileName')) { $invFileName = $PSItem.FileName }
            if ($PSItem.PSObject.Properties.Match('ChatModeName')) { $invChatModeNames += $PSItem.ChatModeName }
            elseif ($PSItem.PSObject.Properties.Match('Name')) { $invChatModeNames += $PSItem.Name }
            if ($PSItem.PSObject.Properties.Match('Owner')) { $invOwner = $PSItem.Owner }
            if ($PSItem.PSObject.Properties.Match('RepoName')) { $invRepoName = $PSItem.RepoName }
            if ($PSItem.PSObject.Properties.Match('Repo')) { $invRepoName = $PSItem.Repo }
        }

        # Fall back to bound parameters when per-item properties are not provided
        if (-not $invFileName -and $FileName) { $invFileName = $FileName }
        if (-not $invOwner -and $Owner) { $invOwner = $Owner }
        if (-not $invRepoName -and $RepoName) { $invRepoName = $RepoName }
        if (($invChatModeNames.Count -eq 0) -and $ChatModeName) {
            if ($ChatModeName -is [System.Array]) { $invChatModeNames += $ChatModeName } else { $invChatModeNames += $ChatModeName }
        }

        # If we still have nothing to install, error out for this invocation
        if (($invChatModeNames.Count -eq 0) -and -not $invFileName) {
            Write-Error 'You must specify a ChatModeName.'
            return
        }

        # Normalize candidate names to iterate: if FileName is provided prefer that (it may be an array)
        $names = @()
        if ($invFileName) {
            if ($invFileName -is [System.Array]) { $names += $invFileName } else { $names += $invFileName }
        }
        else {
            $names += $invChatModeNames
        }

        foreach ($item in $names) {
            # For each item, build parameters for Get-ChatmodeContent. Use per-call overrides if present.
            $params = @{ ChatModeName = $item }

            if ($script:RepoOwner) { $params.Owner = $script:RepoOwner }
            if ($script:RepoName) { $params.RepoName = $script:RepoName }

            if ($Owner) { $params.Owner = $Owner }
            if ($RepoName) { $params.RepoName = $RepoName }

            if ($params.Owner -and -not $params.RepoName -and $script:RepoMap -and $script:RepoMap.ContainsKey($params.Owner)) {
                $params.RepoName = $script:RepoMap[$params.Owner]
            }

            if (-not $PSCmdlet.ShouldProcess($item, "Install chatmode")) {
                continue
            }

            $content = Get-ChatmodeContent @params
            if ($content) {
                # If the incoming pipeline object included the original FileName (with extension), prefer it.
                if ($FileName -and -not ($FileName -is [System.Array])) {
                    $fileName = $FileName
                }
                elseif ($item -match '\.(md|chatmode)$') {
                    $fileName = $item
                }
                else {
                    $fileName = "$item.md"
                }

                # Ensure destination folder exists
                if (-not (Test-Path $Destination)) { New-Item -ItemType Directory -Path $Destination -Force | Out-Null }

                $filePath = Join-Path $Destination $fileName
                Set-Content -Path $filePath -Value $content -Force
                Write-Host "Installed $fileName to $Destination" -ForegroundColor Green
            }
            else {
                Write-Host "No content found for $item, nothing installed." -ForegroundColor Yellow
            }
        }
    }
}

# Example usage:
# Get-Chatmode | Install-Chatmode
# Install-Chatmode -ChatModeName 'sql-expert'