functions/Get-EndjinGists.ps1

# <copyright file="Get-EndjinGists.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

<#
.SYNOPSIS
    Lists all available gists from the gist registry.
 
.DESCRIPTION
    The Get-EndjinGists function reads the gist-map configuration file and displays
    information about all available gists. By default, it outputs a human-readable
    grouped text summary. Use the -PassThru switch to return PSCustomObjects suitable
    for pipeline processing.
 
    You can optionally filter by Group and Name. Both parameters support positional
    binding, so you can write: Get-EndjinGists devcontainer ai-agent
 
.PARAMETER Group
    When specified, filters the output to only show gists in the given group.
 
.PARAMETER Name
    When specified together with Group, filters the output to only show the gist
    with the given name within that group. Requires Group to be specified.
 
.PARAMETER PassThru
    When specified, outputs PSCustomObjects instead of a grouped text summary.
 
.PARAMETER GistMapUrl
    The URL to fetch the gist-map.yml file from via HTTP. Defaults to the endjin-gists
    repository on GitHub. When GistMapPath is specified, remote fetching is skipped.
 
.PARAMETER GistMapPath
    The path to a local 'Gist Map' configuration file. When specified, remote fetching
    is skipped and this file is used directly.
 
.PARAMETER GistMapRepo
    A GitHub repository in 'owner/repo' format (e.g., 'endjin/endjin-gists') that hosts
    a gist-map file. When specified, the function constructs an HTTP URL and a vendir git
    source from the repo, ref, and path parameters.
 
.PARAMETER GistMapRef
    The git reference (branch, tag, or commit) to use when fetching the gist-map from
    GistMapRepo. Defaults to 'main'.
 
.PARAMETER GistMapRepoPath
    The file path within the GistMapRepo repository to the gist-map file. Defaults to
    'gist-map.yml'.
 
.EXAMPLE
    Get-EndjinGists
 
    Displays a grouped text summary of all available gists.
 
.EXAMPLE
    Get-EndjinGists devcontainer
 
    Displays only gists in the 'devcontainer' group.
 
.EXAMPLE
    Get-EndjinGists devcontainer ai-agent
 
    Displays only the 'ai-agent' gist in the 'devcontainer' group.
 
.EXAMPLE
    Get-EndjinGists -PassThru
 
    Returns all available gists as PSCustomObjects with Group, Name, Description, Source, and Ref properties.
 
.EXAMPLE
    Get-EndjinGists -PassThru | Where-Object { $_.Group -eq 'llm-kb' }
 
    Returns only gists in the 'llm-kb' group.
 
.EXAMPLE
    Get-EndjinGists -GistMapRepo "myorg/my-gists"
 
    Lists all available gists from a gist-map hosted in a different GitHub repository.
 
.NOTES
    The gist-map.yml file contains the registry of all available gists organized by group.
#>

function Get-EndjinGists {
    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param (
        [Parameter(Position=0)]
        [string] $Group,

        [Parameter(Position=1)]
        [string] $Name,

        [Parameter()]
        [switch] $PassThru,

        [Parameter()]
        [string] $GistMapUrl = $script:DefaultGistMapUrl,

        [Parameter()]
        [string] $GistMapPath,

        [Parameter()]
        [ValidatePattern('^[^/]+/[^/]+$')]
        [string] $GistMapRepo,

        [Parameter()]
        [string] $GistMapRef = 'main',

        [Parameter()]
        [string] $GistMapRepoPath = 'gist-map.yml',

        [Parameter()]
        [switch] $NoCache
    )

    begin {
        Set-StrictMode -Version Latest
    }

    process {
        if ($GistMapPath) {
            if (-not (Test-Path $GistMapPath -PathType Leaf)) {
                throw "GistMapPath '$GistMapPath' does not exist."
            }
            $gistMapContent = Get-Content -Path $GistMapPath -Raw
            $gistMap = ConvertFrom-Yaml $gistMapContent
        }
        else {
            if ($GistMapRepo) {
                $effectiveUrl = "https://raw.githubusercontent.com/$GistMapRepo/$GistMapRef/$GistMapRepoPath"
                $effectiveGitSource = @{
                    url  = "https://github.com/$GistMapRepo.git"
                    ref  = $GistMapRef
                    path = $GistMapRepoPath
                }
            }
            else {
                $effectiveUrl = $GistMapUrl
                $effectiveGitSource = $script:DefaultGistMapGitSource
            }
            # Resolve the path here to ensure the ScriptRoot portion of the cache key is consistent with other scenarios (e.g. the tab completers)
            $gistMap = _Get-GistMapData -ScriptRoot (Resolve-Path (Join-Path $PSScriptRoot '..')).Path `
                                        -GistMapUrl $effectiveUrl `
                                        -GistMapGitSource $effectiveGitSource `
                                        -NoCache:$NoCache
            if (-not $gistMap) {
                Write-Warning "Unable to load gist-map from any source."
                return
            }

            # Update the defaults so subsequent commands do not need to re-specify the gist-map override arguments
            $script:DefaultGistMapUrl = $effectiveUrl
            $script:DefaultGistMapGitSource = $effectiveGitSource
            Write-Verbose "Updating defaults:`nDefaultGistMapUrl=$script:DefaultGistMapUrl`nDefaultGistMapGitSource=$($script:DefaultGistMapGitSource | ConvertTo-Json)"
        }

        # Filter by Group and/or Name if specified
        if ($Name -and -not $Group) {
            Write-Warning "The -Name parameter requires -Group to be specified. Use: Get-EndjinGists -Group <group> -Name <name>"
            return
        }

        if ($Group) {
            if (-not $gistMap.ContainsKey($Group)) {
                Write-Warning "Group '$Group' not found. Use Get-EndjinGists to see available groups."
                return
            }

            if ($Name) {
                $matchingGist = $gistMap[$Group] | Where-Object { $_.name -eq $Name }
                if (-not $matchingGist) {
                    Write-Warning "Gist '$Name' not found in group '$Group'. Use Get-EndjinGists $Group to see available gists."
                    return
                }
                $gistMap = @{ $Group = @($matchingGist) }
            }
            else {
                $gistMap = @{ $Group = $gistMap[$Group] }
            }
        }

        if (!$PassThru) {
            Write-Output "Available Gists:"
            Write-Output ""
            foreach ($group in ($gistMap.Keys | Sort-Object)) {
                Write-Output "🗂️ $group"
                foreach ($gist in $gistMap[$group]) {
                    $desc = if ($gist.ContainsKey('description')) { $gist.description } else { $null }
                    $descText = if ($desc) { " - $desc" } else { "" }
                    Write-Output " 📑 $($gist.name)$descText"
                }
                Write-Output ""
            }
        }
        else {
            $gists = [System.Collections.Generic.List[PSCustomObject]]::new()
            foreach ($group in $gistMap.Keys) {
                foreach ($gist in $gistMap[$group]) {
                    $desc = if ($gist.ContainsKey('description')) { $gist.description } else { $null }
                    $gists.Add([PSCustomObject]@{
                        Group       = $group
                        Name        = $gist.name
                        Description = $desc
                        Source      = $gist.source
                        Ref         = $gist.ref
                    })
                }
            }
            return $gists
        }
    }
}
New-Alias -Name gists -Value Get-EndjinGists -Force