PoshKelvin.psm1

#Region './private/_GetPaginatedData.ps1' -1

function _GetPaginatedData {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [uri]$Uri,
        
        [Parameter(Mandatory)]
        [string]$Method,

        [Parameter()]
        [object]$Body,

        [Parameter()]
        [string]$ContentType,

        [Parameter()]
        [hashtable]$Headers,

        [Parameter()]
        $TypeName
    )

    $currentUri = $Uri
    $pageCount = 1
    
    do {
        try {
            Write-Verbose "Requesting data from $currentUri"
            
            $response = Invoke-RestMethod -Uri $currentUri -Method $Method -Body $Body `
                -ContentType $ContentType -Headers $Headers -ErrorAction Stop

            if ($response.PSObject.Properties.Name -contains 'pagination') {
                Write-Verbose "Pagination detected. Processing page $pageCount"
                Write-Verbose $response.pagination | ConvertTo-Json -Compress

                try {
                    $response.data | ForEach-Object {
                        $item = $_
                        if ($TypeName) {
                            $_.PSObject.TypeNames.Insert(0, $TypeName)
                        }
                        Write-Output $item
                    }
                }
                catch {
                    Write-Error "Failed to process page $pageCount data: $($_.Exception.Message)"
                    return
                }
                
                if ($response.pagination.next_page) {
                    try {
                        Add-Type -AssemblyName System.Web
                        $builder = [UriBuilder]$Uri
                        $query = [System.Web.HttpUtility]::ParseQueryString($builder.Query)
                        $query['next'] = $response.pagination.next_page
                        $query['previous'] = $response.pagination.previous_page
                        $builder.Query = $query.ToString()
                        $currentUri = $builder.Uri
                        $pageCount++
                    }
                    catch {
                        Write-Error "Failed to build pagination URL: $($_.Exception.Message)"
                        return
                    }
                }
                else {
                    $currentUri = $null
                }
            }
            else {
                Write-Output $response
                $currentUri = $null
            }
        }
        catch [System.Net.WebException] {
            Write-Error "HTTP request failed on page $pageCount`: $_"
            return
        }
        catch {
            Write-Error "Unexpected error on page $pageCount`: $_"
            return
        }
    } while ($currentUri)
}
#EndRegion './private/_GetPaginatedData.ps1' 86
#Region './private/_GetParams.ps1' -1

<#
.SYNOPSIS
    Derives query parameters for an API call from the caller's bound parameters.
.DESCRIPTION
    This function inspects the calling command via the PowerShell call stack and uses
    its InvocationInfo.BoundParameters and parameter metadata to construct a hashtable
    of query parameters. It selects only parameters that belong to the 'Query' parameter
    set and that were explicitly bound by the caller, then maps each to its query name
    (preferring the first alias when available). Parameters that were not supplied or
    have a value of $null are omitted so they are not sent to the API.
#>

Function _GetParams {

    $caller = (Get-PSCallStack)[1]
    $invocationInfo = $caller.InvocationInfo
    $callerCommand = $invocationInfo.MyCommand
    $boundParameters = $invocationInfo.BoundParameters

    $allParams = $callerCommand.Parameters.Values `
    | Where-Object { $_.ParameterSets.Keys -contains 'Query' } `
    | Where-Object { $boundParameters.Keys -contains $_.Name }
    
    $result = @{}

    foreach ($parm in $allParams) {

        $key = $parm.Name
        $val = $boundParameters[$key]
        $parmName = _GetParamName $parm

        if ($null -eq $val) { continue }

        $result[$parmName] = $val
    }

    return $result
}

Function _GetParamName ($parm) {

    $paramName = $parm.Name.ToLower()

    if ($parm.Aliases.Count -gt 0) {
        $paramName = $parm.Aliases[0].ToLower()
    }

    return $paramName
}
#EndRegion './private/_GetParams.ps1' 49
#Region './public/AppManager/Get-KelvinApp.ps1' -1

<#
.SYNOPSIS
    Gets applications from the App Registry.

.DESCRIPTION
    Retrieves application information from the currently connected Kelvin instance
    App Registry.

.EXAMPLE
    PS> Get-KelvinApp

    Lists all registered applications.

.EXAMPLE
    PS> Get-KelvinApp -Name my-app -Detailed

    Retrieves detailed information for a specific application.
#>

Function Get-KelvinApp {
    [OutputType('Kelvin.App')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across application fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter applications by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string[]] $Name,

        # Filter applications by type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Type,

        # Return detailed information for each application.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'appregistry/list' -Method Get -TypeName 'Kelvin.App' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "appregistry/$($_.name)/get" -Method Get -TypeName 'Kelvin.App')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/AppManager/Get-KelvinApp.ps1' 57
#Region './public/AppManager/Get-KelvinAppManagerResource.ps1' -1

<#
.SYNOPSIS
    Gets app manager details for a specific resource.

.DESCRIPTION
    Retrieves app manager details for a specific resource from the currently
    connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinAppManagerResource -ResourceKrn 'krn:my-resource'

    Retrieves app manager details for the specified resource KRN.
#>

Function Get-KelvinAppManagerResource {
    [OutputType('Kelvin.AppManagerResource')]
    [CmdletBinding()]
    Param
    (
        # The Kelvin Resource Name (KRN) identifier.
        [Parameter(Mandatory = $true, Position = 0)]
        [Alias('resource_krn')]
        [string] $ResourceKrn
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi "app-manager/resource/$ResourceKrn/get" -Method Get -TypeName 'Kelvin.AppManagerResource' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/AppManager/Get-KelvinAppManagerResource.ps1' 34
#Region './public/AppManager/Get-KelvinAppResource.ps1' -1

<#
.SYNOPSIS
    Gets resources for a specific application.

.DESCRIPTION
    Retrieves resources associated with a specific application from the currently
    connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinAppResource -AppName my-app

    Lists all resources for the specified application.

.EXAMPLE
    PS> Get-KelvinAppResource -AppName my-app -Status running

    Lists only the running resources for the specified application.
#>

Function Get-KelvinAppResource {
    [OutputType('Kelvin.AppResource')]
    [CmdletBinding()]
    Param
    (
        # The name of the application to retrieve resources for.
        [Parameter(Mandatory = $true, Position = 0)]
        [Alias('app_name')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string] $AppName,

        # Free-form text search across resource fields.
        [Parameter()]
        [string[]] $Search,

        # Filter resources by name.
        [Parameter()]
        [string[]] $ResourceName,

        # Filter resources by status.
        [Parameter()]
        [ValidateSet('running', 'stopped', 'unknown')]
        [string[]] $Status
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi "app-manager/app/$AppName/resources/list" -Method Post -TypeName 'Kelvin.AppResource' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/AppManager/Get-KelvinAppResource.ps1' 53
#Region './public/Asset/Get-KelvinAsset.ps1' -1

<#
.SYNOPSIS
    Gets assets from Kelvin.

.DESCRIPTION
    Retrieves assets from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinAsset

    Lists all assets.

.EXAMPLE
    PS> Get-KelvinAsset -AssetType pump -Status online -Detailed

    Retrieves detailed information for online assets of type 'pump'.
#>

Function Get-KelvinAsset {
    [OutputType('Kelvin.Asset')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across asset fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter assets by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('names')]
        [string[]] $Name,

        # Filter assets by asset type.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('asset_type')]
        [string[]] $AssetType,

        # Filter assets by status.
        [Parameter(ParameterSetName = 'Query')]
        [ValidateSet('online', 'offline', 'unknown')]
        [string[]] $Status,

        # Return detailed information for each asset.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'assets/list' -Method Get -TypeName 'Kelvin.Asset' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "assets/$($_.name)/get" -Method Get -TypeName 'Kelvin.Asset')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Asset/Get-KelvinAsset.ps1' 64
#Region './public/Asset/Get-KelvinAssetType.ps1' -1

<#
.SYNOPSIS
    Gets asset types from Kelvin.

.DESCRIPTION
    Retrieves asset types from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinAssetType

    Lists all asset types.

.EXAMPLE
    PS> Get-KelvinAssetType -Name pump -Detailed

    Retrieves detailed information for the 'pump' asset type.
#>

Function Get-KelvinAssetType {
    [OutputType('Kelvin.AssetType')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across asset type fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter asset types by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('asset_type_name')]
        [string[]] $Name,

        # Return detailed information for each asset type.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'assets/types/list' -Method Get -TypeName 'Kelvin.AssetType' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "assets/types/$($_.name)/get" -Method Get -TypeName 'Kelvin.AssetType')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Asset/Get-KelvinAssetType.ps1' 53
#Region './public/Bridge/Get-KelvinBridge.ps1' -1

<#
.SYNOPSIS
    Gets bridges from Kelvin.

.DESCRIPTION
    Retrieves bridges from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinBridge

    Lists all bridges.

.EXAMPLE
    PS> Get-KelvinBridge -Running $true -Detailed

    Retrieves detailed information for all running bridges.
#>

Function Get-KelvinBridge {
    [OutputType('Kelvin.Bridge')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across bridge fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter bridges by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('bridge_name')]
        [string[]] $Name,

        # Filter bridges by running state.
        [Parameter(ParameterSetName = 'Query')]
        [Nullable[bool]] $Running,

        # Return detailed information for each bridge.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'bridges/list' -Method Get -TypeName 'Kelvin.Bridge' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "bridges/$($_.name)/get" -Method Get -TypeName 'Kelvin.Bridge')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Bridge/Get-KelvinBridge.ps1' 57
#Region './public/Connect-KelvinAccount.ps1' -1

<#
.SYNOPSIS
    Connects to a Kelvin instance.

.DESCRIPTION
    Authenticates against a Kelvin service using the provided URL and credentials.
    Upon successful authentication, stores the connection details and access token
    in module-scoped variables for use by subsequent cmdlets.

.EXAMPLE
    PS> $cred = Get-Credential
    PS> Connect-KelvinAccount -Url https://my-instance.kelvin.ai/ -Credentials $cred

    Connects to a Kelvin instance with the default API version (v4).

.EXAMPLE
    PS> Connect-KelvinAccount https://my-instance.kelvin.ai/ -Credentials $cred -ApiVersion v3

    Connects to a Kelvin instance targeting a specific API version.

.EXAMPLE
    PS> $env:KELVIN_USERNAME = 'admin'
    PS> $env:KELVIN_PASSWORD = 'secret'
    PS> Connect-KelvinAccount https://my-instance.kelvin.ai/

    Connects using credentials from environment variables.
#>


function Connect-KelvinAccount {
    [CmdletBinding()]
    param (
        # The base URL of the Kelvin service (e.g. https://my-instance.kelvin.ai/).
        [Parameter(Mandatory = $true, Position = 0)]
        [uri] $Url,

        # A PSCredential object containing the username and password for authentication.
        # If not supplied, the KELVIN_USERNAME and KELVIN_PASSWORD environment variables are used.
        [Parameter()]
        [pscredential] $Credentials,

        # The API version to target. Defaults to 'v4'.
        [Parameter()]
        [ValidateScript({ $_ -match 'v\d+' })]
        [string] $ApiVersion = 'v4'
    )

    $endpointUrl = $Url.AbsoluteUri.TrimEnd('/')
    Write-Verbose "Connecting to Kelvin service at $endpointUrl"

    # Define the authentication endpoint
    $authEndpoint = "$endpointUrl/auth/realms/kelvin/protocol/openid-connect/token"
    Write-Verbose "Authenticating against Kelvin service at $authEndpoint"

    # Extract username and password from the PSCredential object, if present.
    # Otherwise, use KELVIN_USERNAME and KELVIN_PASSWORD environment variables.
    # If neither are available, fails
    if ($Credentials) {
        $Username = $Credentials.UserName
        $Password = $Credentials.GetNetworkCredential().Password
    }
    else {
        $Username = $env:KELVIN_USERNAME
        $Password = $env:KELVIN_PASSWORD
    }

    if (-not $Username -or -not $Password) {
        Write-Error "Username and Password must be provided either via -Credentials parameter or KELVIN_USERNAME and KELVIN_PASSWORD environment variables."
        return
    }

    # Prepare the body for the authentication request
    $body = @{
        username   = $Username
        password   = $Password
        client_id  = "kelvin-client"
        grant_type = "password"
    }

    try {
        # Make the authentication request
        $response = Invoke-RestMethod -Uri $authEndpoint -Method Post -Body $body

        # Check if the response contains a token
        if ($response -and $response.access_token) {
            Write-Verbose "Successfully authenticated against Kelvin service."

            if ($endpointUrl -notmatch '/api/v\d+$') {
                $endpointUrl = "$endpointUrl/api/$ApiVersion"
                Write-Verbose "URL updated to include API version '$ApiVersion': $endpointUrl"
            }
            else {
                Write-Verbose "URL already includes API version '$ApiVersion'. Ignoring the ApiVersion parameter."
            }

            # Store the URL, credentials, and token in script-scoped variables
            $script:KelvinURL = $endpointUrl
            $script:KelvinUsername = $Username
            $script:KelvinPassword = $Password
            $script:KelvinToken = $response.access_token
        }
        else {
            Write-Error "Authentication failed. No token received."
        }
    }
    catch {
        Write-Error "An error occurred during authentication: $_"
    }
}
#EndRegion './public/Connect-KelvinAccount.ps1' 109
#Region './public/DataStream/Get-KelvinDataStream.ps1' -1

<#
.SYNOPSIS
    Gets data streams from Kelvin.

.DESCRIPTION
    Retrieves data streams from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinDataStream

    Lists all data streams.

.EXAMPLE
    PS> Get-KelvinDataStream -AssetName my-asset -Detailed

    Retrieves detailed data streams associated with the specified asset.
#>

Function Get-KelvinDataStream {
    [OutputType('Kelvin.DataStream')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across data stream fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter data streams by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('data_stream_name')]
        [string[]] $Name,

        # Filter data streams by data type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $DataType,

        # Filter data streams by semantic type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $SemanticType,

        # Filter data streams by associated asset name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('asset_name')]
        [string[]] $AssetName,

        # Return detailed information for each data stream.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'datastreams/list' -Method Get -TypeName 'Kelvin.DataStream' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "datastreams/$($_.name)/get" -Method Get -TypeName 'Kelvin.DataStream')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/DataStream/Get-KelvinDataStream.ps1' 67
#Region './public/DataStream/Get-KelvinDataStreamSemanticType.ps1' -1

<#
.SYNOPSIS
    Gets data stream semantic types from Kelvin.

.DESCRIPTION
    Retrieves data stream semantic types from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinDataStreamSemanticType

    Lists all data stream semantic types.

.EXAMPLE
    PS> Get-KelvinDataStreamSemanticType -Name temperature -Detailed

    Retrieves detailed information for the 'temperature' semantic type.
#>

Function Get-KelvinDataStreamSemanticType {
    [OutputType('Kelvin.DataStreamSemanticType')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across semantic type fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter semantic types by name.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('semantic_type_name')]
        [string[]] $Name,

        # Return detailed information for each semantic type.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'datastreams/semantic-types/list' -Method Get -TypeName 'Kelvin.DataStreamSemanticType' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "datastreams/semantic-types/$($_.name)/get" -Method Get -TypeName 'Kelvin.DataStreamSemanticType')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/DataStream/Get-KelvinDataStreamSemanticType.ps1' 52
#Region './public/DataStream/Get-KelvinDataStreamUnit.ps1' -1

<#
.SYNOPSIS
    Gets data stream units from Kelvin.

.DESCRIPTION
    Retrieves data stream units from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinDataStreamUnit

    Lists all data stream units.

.EXAMPLE
    PS> Get-KelvinDataStreamUnit -Name celsius -Detailed

    Retrieves detailed information for the 'celsius' unit.
#>

Function Get-KelvinDataStreamUnit {
    [OutputType('Kelvin.DataStreamUnit')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across unit fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter units by name.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('unit_name')]
        [string[]] $Name,

        # Return detailed information for each unit.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'datastreams/units/list' -Method Get -TypeName 'Kelvin.DataStreamUnit' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "datastreams/units/$($_.name)/get" -Method Get -TypeName 'Kelvin.DataStreamUnit')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/DataStream/Get-KelvinDataStreamUnit.ps1' 52
#Region './public/DataStream/Get-KelvinDataType.ps1' -1

<#
.SYNOPSIS
    Gets data types from Kelvin.

.DESCRIPTION
    Retrieves data types from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinDataType

    Lists all data types.

.EXAMPLE
    PS> Get-KelvinDataType -Name float

    Retrieves information for the 'float' data type.
#>

Function Get-KelvinDataType {
    [OutputType('Kelvin.DataType')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across data type fields.
        [Parameter(Position = 0)]
        [string[]] $Search,

        # Filter data types by name.
        [Parameter()]
        [string[]] $Name
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'datastreams/data-types/list' -Method Get -TypeName 'Kelvin.DataType' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/DataStream/Get-KelvinDataType.ps1' 41
#Region './public/DataTag/Get-KelvinDataTag.ps1' -1

<#
.SYNOPSIS
    Gets data tags from Kelvin.

.DESCRIPTION
    Retrieves data tags from the currently connected Kelvin instance. Supports
    querying either data tag instances or tag definitions via the -Tags switch.

.EXAMPLE
    PS> Get-KelvinDataTag

    Lists all data tag instances.

.EXAMPLE
    PS> Get-KelvinDataTag -Tags

    Lists all tag definitions.

.EXAMPLE
    PS> Get-KelvinDataTag -Tags -TagName my-tag -Detailed

    Retrieves detailed information for a specific tag definition.
#>

Function Get-KelvinDataTag {
    [OutputType('Kelvin.DataTag')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across data tag fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter data tags by ID.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('datatag_id')]
        [string[]] $Id,

        # When specified, queries tag definitions instead of data tag instances.
        [Parameter(ParameterSetName = 'Tags')]
        [switch] $Tags,

        # Filter tag definitions by name (used with -Tags).
        [Parameter(ParameterSetName = 'Tags')]
        [Alias('tag_name')]
        [string[]] $TagName,

        # Return detailed information for each result.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        if ($Tags.IsPresent) {
            $endpoint = 'datatags/tags/list'
            $detailEndpoint = "datatags/tags/{0}/get"
            $idProperty = "name"
        }
        else {
            $endpoint = 'datatags/list'
            $detailEndpoint = "datatags/{0}/get"
            $idProperty = "id"
        }

        Invoke-KelvinApi $endpoint -Method Get -TypeName 'Kelvin.DataTag' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi ($detailEndpoint -f $_.$idProperty) -Method Get -TypeName 'Kelvin.DataTag')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/DataTag/Get-KelvinDataTag.ps1' 78
#Region './public/File/Get-KelvinFile.ps1' -1

<#
.SYNOPSIS
    Gets files from Kelvin file storage.

.DESCRIPTION
    Retrieves files from the currently connected Kelvin instance file storage.

.EXAMPLE
    PS> Get-KelvinFile

    Lists all files in file storage.

.EXAMPLE
    PS> Get-KelvinFile -Type config -Detailed

    Retrieves detailed information for files of type 'config'.
#>

Function Get-KelvinFile {
    [OutputType('Kelvin.File')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across file fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter files by ID.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('file_id')]
        [string[]] $Id,

        # Filter files by name.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Name,

        # Filter files by type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Type,

        # Return detailed information for each file.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'filestorage/list' -Method Get -TypeName 'Kelvin.File' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "filestorage/$($_.id)/get" -Method Get -TypeName 'Kelvin.File')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/File/Get-KelvinFile.ps1' 60
#Region './public/Instance/Get-KelvinAuditLog.ps1' -1

<#
.SYNOPSIS
    Gets audit logs from Kelvin.

.DESCRIPTION
    Retrieves audit logs from the currently connected Kelvin instance.
    Supports filtering by user, action, resource, and time range.

.EXAMPLE
    PS> Get-KelvinAuditLog

    Lists all audit log entries.

.EXAMPLE
    PS> Get-KelvinAuditLog -User admin -StartTime (Get-Date).AddDays(-7)

    Lists audit log entries from the last 7 days for the 'admin' user.
#>

Function Get-KelvinAuditLog {
    [OutputType('Kelvin.AuditLog')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across audit log fields.
        [Parameter(Position = 0)]
        [string[]] $Search,

        # Filter by audit log entry ID.
        [Parameter()]
        [Alias('audit_logger_id')]
        [string[]] $Id,

        # Filter by the user who performed the action.
        [Parameter()]
        [string[]] $User,

        # Filter by action type.
        [Parameter()]
        [string[]] $Action,

        # Filter by resource name.
        [Parameter()]
        [string[]] $Resource,

        # Return only entries after this date/time.
        [Parameter()]
        [DateTime] $StartTime,

        # Return only entries before this date/time.
        [Parameter()]
        [DateTime] $EndTime,

        # Return detailed information for each entry.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        # Convert DateTime objects to ISO 8601 strings if provided
        if ($StartTime) {
            $params['start_time'] = $StartTime.ToString('o')
        }

        if ($EndTime) {
            $params['end_time'] = $EndTime.ToString('o')
        }

        Invoke-KelvinApi 'instance/auditlog/list' -Method Get -TypeName 'Kelvin.AuditLog' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "instance/auditlog/$($_.id)/get" -Method Get -TypeName 'Kelvin.AuditLog')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Instance/Get-KelvinAuditLog.ps1' 82
#Region './public/Instance/Get-KelvinInstanceSetting.ps1' -1

<#
.SYNOPSIS
    Gets instance settings from Kelvin.

.DESCRIPTION
    Retrieves instance settings from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinInstanceSetting

    Lists all instance settings.

.EXAMPLE
    PS> Get-KelvinInstanceSetting -Name my-setting -Detailed

    Retrieves detailed information for a specific setting.
#>

Function Get-KelvinInstanceSetting {
    [OutputType('Kelvin.InstanceSetting')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across setting fields.
        [Parameter(Position = 0)]
        [string[]] $Search,

        # Filter settings by name.
        [Parameter()]
        [Alias('setting_name')]
        [string[]] $Name,

        # Return detailed information for each setting.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'instance/settings/list' -Method Get -TypeName 'Kelvin.InstanceSetting' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent -and $_.name) {
                $ret = (Invoke-KelvinApi "instance/settings/$($_.name)/get" -Method Get -TypeName 'Kelvin.InstanceSetting')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Instance/Get-KelvinInstanceSetting.ps1' 52
#Region './public/Instance/Get-KelvinInstanceStatus.ps1' -1

<#
.SYNOPSIS
    Gets the status of the Kelvin instance.

.DESCRIPTION
    Retrieves the current status of the connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinInstanceStatus

    Returns the current instance status.
#>

Function Get-KelvinInstanceStatus {
    [OutputType('Kelvin.InstanceStatus')]
    [CmdletBinding()]
    Param()

    Process {
        $params = _GetParams

        Invoke-KelvinApi "instance/status/get" -Method Get -TypeName 'Kelvin.InstanceStatus' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/Instance/Get-KelvinInstanceStatus.ps1' 27
#Region './public/Invoke-KelvinApi.ps1' -1

<#
.SYNOPSIS
    Invokes a Kelvin API endpoint.

.DESCRIPTION
    Sends a request to a Kelvin API endpoint. Handles authentication, pagination,
    JSON serialization, and query-string building. Requires a prior call to
    Connect-KelvinAccount.

.EXAMPLE
    PS> Invoke-KelvinApi 'workloads/list'

    Sends a GET request to the workloads/list endpoint.

.EXAMPLE
    PS> Invoke-KelvinApi 'workloads/apply' -Method POST -Body @{ workload_names = @('w1','w2') }

    Sends a POST request with a JSON body.

.EXAMPLE
    PS> Invoke-KelvinApi 'workloads/my-app/download' -Accept 'application/zip' -AsStream

    Returns the raw response stream for binary content.
#>

function Invoke-KelvinApi {
    [CmdletBinding()]
    param (
        # The API path relative to the base Kelvin URL (e.g. 'workloads/list').
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path,

        # Optional query-string parameters as a hashtable.
        [Parameter(Position = 1)]
        [hashtable]$Parameters,

        # The HTTP method to use. Defaults to GET.
        [Parameter()]
        [ValidateSet('GET', 'POST', 'PUT', 'DELETE', 'PATCH')]
        [string]$Method = 'GET',

        # The request body. Hashtables are automatically serialized to JSON.
        [Parameter()]
        [object]$Body,

        # The Content-Type header for the request body. Defaults to 'application/json'.
        [Parameter()]
        [string]$BodyContentType = 'application/json',

        # The Accept header value. Defaults to 'application/json'.
        [Parameter()]
        [Alias('ContentType')]
        [string]$Accept = 'application/json',

        # A PSTypeName to attach to each returned object.
        [Parameter()]
        [string] $TypeName,

        # When specified, returns the raw response stream instead of parsed objects.
        [Parameter()]
        [switch]$AsStream
    )

    if (-not $script:KelvinURL -or -not $script:KelvinToken) {
        throw 'Connect-KelvinAccount must be called before invoking any API commands.'
    }

    $url = "$script:KelvinURL/$Path"
    Write-Verbose "Invoking API at $url"

    if ($Body -is [string] -or $Body -is [int] -or $Body -is [double]) {
        $requestBody = $Body
    }
    elseif ($null -ne $Body) {
        $requestBody = $Body | ConvertTo-Json -Depth 10
    }

    if ($Parameters.Count -gt 0) {
        $urlBuilder = [UriBuilder] $url
        $urlBuilder.Query = _GetQuery $Parameters 
        $url = $urlBuilder.Uri
        Write-Verbose "URL after adding query parameters: $url"
    }

    if ($AsStream) {

        Write-Verbose 'Returning response as a stream.'
        
        Add-Type -AssemblyName System.Net.Http
        $client = New-Object System.Net.Http.HttpClient
        $client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue('Bearer', $script:KelvinToken)
        $client.DefaultRequestHeaders.Accept.Add((New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue($Accept)))
        $client.DefaultRequestHeaders.Add('User-Agent', 'PoshKelvin/1.0')
        
        $request = New-Object System.Net.Http.HttpRequestMessage -ArgumentList $Method, $url
        $request.Content = $(if ($requestBody) { New-Object System.Net.Http.StringContent($requestBody, [System.Text.Encoding]::UTF8, $BodyContentType) })

        $response = $client.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result
        
        if ($response.IsSuccessStatusCode) {
            return $response.Content.ReadAsStreamAsync().Result
        }
        else {
            throw "Failed to invoke API: $($response.ReasonPhrase) - $($response.Content.ReadAsStringAsync().Result)"
        }
    }

    return _GetPaginatedData -Uri $url `
        -Method $Method `
        -Body $requestBody `
        -ContentType $BodyContentType `
        -Headers @{ Authorization = "Bearer $script:KelvinToken" } `
        -TypeName $TypeName
}

function _GetQuery($parameters) {
    $query = @()
    foreach ($key in $parameters.Keys) {
        $encodedKey = [System.Uri]::EscapeDataString($key)
        if ($parameters[$key] -is [array]) {
            foreach ($value in $parameters[$key]) {
                $query += "$encodedKey=$([System.Uri]::EscapeDataString($value))"
            }
        }
        else {
            $query += "$encodedKey=$([System.Uri]::EscapeDataString($parameters[$key]))"
        }
    }
    return [string]::Join('&', $query)
}
#EndRegion './public/Invoke-KelvinApi.ps1' 130
#Region './public/Orchestration/Get-KelvinCluster.ps1' -1

<#
.SYNOPSIS
    Gets clusters from Kelvin.

.DESCRIPTION
    Retrieves clusters from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinCluster

    Lists all clusters.

.EXAMPLE
    PS> Get-KelvinCluster -Status online -Detailed

    Retrieves detailed information for all online clusters.
#>

Function Get-KelvinCluster {
    [OutputType('Kelvin.Cluster')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across cluster fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter clusters by name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('names')]
        [string[]] $Name,

        # Filter clusters by readiness state.
        [Parameter(ParameterSetName = 'Query')]
        [Nullable[bool]] $Ready,

        # Filter clusters by type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Type,

        # Filter clusters by status.
        [Parameter(ParameterSetName = 'Query')]
        [ValidateSet('pending_provision', 'pending', 'online', 'unreachable', 'requires_attention')]
        [string[]] $Status,

        # Return detailed information for each cluster.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'orchestration/clusters/list' -Method Get -TypeName 'Kelvin.Cluster' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "orchestration/clusters/$($_.name)/get" -Method Get -TypeName 'Kelvin.Cluster')
            }
            else {
                $ret = $_
            }
            Write-Output ($ret | Add-Member -Name 'cluster_name' -Value $ret.name -MemberType NoteProperty -PassThru)
        }
    }
}
#EndRegion './public/Orchestration/Get-KelvinCluster.ps1' 66
#Region './public/Orchestration/Get-KelvinClusterNode.ps1' -1

<#
.SYNOPSIS
    Gets nodes from a Kelvin cluster.

.DESCRIPTION
    Retrieves the nodes from a specific cluster in the currently connected
    Kelvin instance.

.EXAMPLE
    PS> Get-KelvinClusterNode -ClusterName my-cluster

    Lists all nodes in the specified cluster.

.EXAMPLE
    PS> Get-KelvinCluster -Name my-cluster | Get-KelvinClusterNode -Status online

    Lists online nodes for a cluster obtained via pipeline.
#>

Function Get-KelvinClusterNode {
    [OutputType('Kelvin.ClusterNode')]
    [CmdletBinding()]
    Param
    (
        # The name of the cluster to retrieve nodes from.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias('cluster_name')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string] $ClusterName,

        # Free-form text search across node fields.
        [Parameter(Position = 1)]
        [string[]] $Search,

        # Filter nodes by name.
        [Parameter()]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('node_name', 'names')]
        [string[]] $Name,

        # Filter nodes by status.
        [Parameter()]
        [ValidateSet('online', 'offline', 'unknown')]
        [string[]] $Status,

        # Return detailed information for each node.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi "orchestration/clusters/$ClusterName/nodes/list" -Method Get -TypeName 'Kelvin.ClusterNode' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "orchestration/clusters/$ClusterName/nodes/$($_.name)/get" -Method Get -TypeName 'Kelvin.ClusterNode')
            }
            else {
                $ret = $_
            }
            Write-Output ($ret | Add-Member -Name 'node_name' -Value $ret.name -MemberType NoteProperty -PassThru)
        }
    }
}
#EndRegion './public/Orchestration/Get-KelvinClusterNode.ps1' 65
#Region './public/Orchestration/Get-KelvinClusterService.ps1' -1

<#
.SYNOPSIS
    Gets services from a Kelvin cluster.

.DESCRIPTION
    Retrieves the services from a specific cluster in the currently connected
    Kelvin instance.

.EXAMPLE
    PS> Get-KelvinClusterService -ClusterName my-cluster

    Lists all services in the specified cluster.

.EXAMPLE
    PS> Get-KelvinCluster -Name my-cluster | Get-KelvinClusterService -Status running

    Lists running services for a cluster obtained via pipeline.
#>

Function Get-KelvinClusterService {
    [OutputType('Kelvin.ClusterService')]
    [CmdletBinding()]
    Param
    (
        # The name of the cluster to retrieve services from.
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)]
        [Alias('cluster_name')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string] $ClusterName,

        # Free-form text search across service fields.
        [Parameter(Position = 1)]
        [string[]] $Search,

        # Filter services by name.
        [Parameter()]
        [string[]] $Name,

        # Filter services by type.
        [Parameter()]
        [string[]] $Type,

        # Filter services by status.
        [Parameter()]
        [ValidateSet('running', 'stopped', 'unknown')]
        [string[]] $Status
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi "orchestration/clusters/$ClusterName/services/list" -Method Get -TypeName 'Kelvin.ClusterService' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/Orchestration/Get-KelvinClusterService.ps1' 57
#Region './public/Parameter/Get-KelvinParameterDefinition.ps1' -1

<#
.SYNOPSIS
    Gets parameter definitions from Kelvin.

.DESCRIPTION
    Retrieves parameter definitions from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinParameterDefinition

    Lists all parameter definitions.

.EXAMPLE
    PS> Get-KelvinParameterDefinition -Type string

    Lists parameter definitions of type 'string'.
#>

Function Get-KelvinParameterDefinition {
    [OutputType('Kelvin.ParameterDefinition')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across parameter definition fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter parameter definitions by name.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Name,

        # Filter parameter definitions by type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Type
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'parameters/definitions/list' -Method Get -TypeName 'Kelvin.ParameterDefinition' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/Parameter/Get-KelvinParameterDefinition.ps1' 45
#Region './public/Parameter/Get-KelvinParameterResource.ps1' -1

<#
.SYNOPSIS
    Gets parameter resources from Kelvin.

.DESCRIPTION
    Retrieves parameter resources from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinParameterResource

    Lists all parameter resources.

.EXAMPLE
    PS> Get-KelvinParameterResource -ResourceName my-resource

    Retrieves a specific parameter resource by name.
#>

Function Get-KelvinParameterResource {
    [OutputType('Kelvin.ParameterResource')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across parameter resource fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter parameter resources by name.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Name,

        # Filter parameter resources by resource name.
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('resource_name')]
        [string[]] $ResourceName
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'parameters/resources/list' -Method Get -TypeName 'Kelvin.ParameterResource' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/Parameter/Get-KelvinParameterResource.ps1' 47
#Region './public/Recommendation/Get-KelvinRecommendation.ps1' -1

<#
.SYNOPSIS
    Gets recommendations from Kelvin.

.DESCRIPTION
    Retrieves recommendations from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinRecommendation

    Lists all recommendations.

.EXAMPLE
    PS> Get-KelvinRecommendation -Status pending -Detailed

    Retrieves detailed information for all pending recommendations.
#>

Function Get-KelvinRecommendation {
    [OutputType('Kelvin.Recommendation')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across recommendation fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter recommendations by ID.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('recommendation_id')]
        [string[]] $Id,

        # Filter recommendations by type.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Type,

        # Filter recommendations by status.
        [Parameter(ParameterSetName = 'Query')]
        [ValidateSet('accepted', 'rejected', 'pending')]
        [string[]] $Status,

        # Return detailed information for each recommendation.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'recommendations/list' -Method Get -TypeName 'Kelvin.Recommendation' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "recommendations/$($_.id)/get" -Method Get -TypeName 'Kelvin.Recommendation')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Recommendation/Get-KelvinRecommendation.ps1' 61
#Region './public/Recommendation/Get-KelvinRecommendationType.ps1' -1

<#
.SYNOPSIS
    Gets recommendation types from Kelvin.

.DESCRIPTION
    Retrieves recommendation types from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinRecommendationType

    Lists all recommendation types.

.EXAMPLE
    PS> Get-KelvinRecommendationType -Name my-type -Detailed

    Retrieves detailed information for a specific recommendation type.
#>

Function Get-KelvinRecommendationType {
    [OutputType('Kelvin.RecommendationType')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across recommendation type fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter recommendation types by name.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Name,

        # Return detailed information for each recommendation type.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'recommendations/types/list' -Method Get -TypeName 'Kelvin.RecommendationType' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "recommendations/types/$($_.name)/get" -Method Get -TypeName 'Kelvin.RecommendationType')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Recommendation/Get-KelvinRecommendationType.ps1' 51
#Region './public/Secret/Get-KelvinSecret.ps1' -1

<#
.SYNOPSIS
    Gets secrets from Kelvin.

.DESCRIPTION
    Retrieves secrets from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinSecret

    Lists all secrets.

.EXAMPLE
    PS> Get-KelvinSecret -Name my-secret

    Retrieves a specific secret by name.
#>

Function Get-KelvinSecret {
    [OutputType('Kelvin.Secret')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across secret fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter secrets by name.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('secret_name')]
        [string[]] $Name
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'secrets/list' -Method Get -TypeName 'Kelvin.Secret' -Parameters $params `
        | ForEach-Object {
            Write-Output $_
        }
    }
}
#EndRegion './public/Secret/Get-KelvinSecret.ps1' 42
#Region './public/Thread/Get-KelvinThread.ps1' -1

<#
.SYNOPSIS
    Gets threads from Kelvin.

.DESCRIPTION
    Retrieves threads from the currently connected Kelvin instance.

.EXAMPLE
    PS> Get-KelvinThread

    Lists all threads.

.EXAMPLE
    PS> Get-KelvinThread -Title 'my-thread' -Detailed

    Retrieves detailed information for threads matching the specified title.
#>

Function Get-KelvinThread {
    [OutputType('Kelvin.Thread')]
    [CmdletBinding()]
    Param
    (
        # Free-form text search across thread fields.
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter threads by ID.
        [Parameter(ParameterSetName = 'Query')]
        [Alias('thread_id')]
        [string[]] $Id,

        # Filter threads by title.
        [Parameter(ParameterSetName = 'Query')]
        [string[]] $Title,

        # Return detailed information for each thread.
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        Invoke-KelvinApi 'threads/list' -Method Get -TypeName 'Kelvin.Thread' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "threads/$($_.id)/get" -Method Get -TypeName 'Kelvin.Thread')
            }
            else {
                $ret = $_
            }
            Write-Output $ret
        }
    }
}
#EndRegion './public/Thread/Get-KelvinThread.ps1' 56
#Region './public/Workload/Export-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Downloads (exports) a workload package.

.DESCRIPTION
    Downloads a workload package (a ZIP file containing the workload binaries and
    manifest files) from the Kelvin API. The package can be saved to a local path
    or returned as a stream for further processing.

.EXAMPLE
    PS> Export-KelvinWorkload -Name my-workload

    Downloads the workload package to the current directory as my-workload.zip.

.EXAMPLE
    PS> Export-KelvinWorkload -Name my-workload -DestinationPath C:\packages

    Downloads the workload package to the specified directory.

.EXAMPLE
    PS> $stream = Export-KelvinWorkload -Name my-workload -AsStream

    Returns the raw response stream for custom processing.
#>

function Export-KelvinWorkload {
    [CmdletBinding(DefaultParameterSetName = 'File')]
    param 
    (
        # The name of the workload to download.
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string] $Name,

        # The local directory to save the downloaded package. Defaults to the current directory.
        [Parameter(ParameterSetName = 'File')]
        [string] $DestinationPath = '.',

        # When specified, returns the raw response stream instead of saving to a file.
        [Parameter(ParameterSetName = 'Stream', Mandatory = $true)]
        [switch] $AsStream
    )

    process {
        $params = @{
            workload_name = $Name
        }

        $stream = Invoke-KelvinApi "workloads/$Name/download" -Method Get -TypeName 'Kelvin.Workload' -Parameters $params -ContentType 'application/zip' -AsStream

        if ($AsStream) {
            return $stream
        }

        if (-not (Test-Path -Path $DestinationPath)) {
            New-Item -Path $DestinationPath -ItemType Directory | Out-Null
        }

        $fileName = Join-Path $DestinationPath "${Name}.zip"

        Write-Verbose "Saving workload package to $fileName"

        $fileStream = [System.IO.File]::Create($fileName)
        $stream.CopyTo($fileStream)

        $fileStream.Dispose()
        $stream.Dispose()

        Write-Verbose 'Workload package saved successfully.'
    }
}
#EndRegion './public/Workload/Export-KelvinWorkload.ps1' 72
#Region './public/Workload/Get-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Gets workloads from Kelvin.

.DESCRIPTION
    Retrieves a list of workloads from the currently connected Kelvin instance.
    Supports filtering by name, application, cluster, node, status, and more.

.EXAMPLE
    PS> Get-KelvinWorkload

    Lists all workloads.

.EXAMPLE
    PS> Get-KelvinWorkload -ClusterName my-cluster

    Lists workloads deployed to the specified cluster.

.EXAMPLE
    PS> Get-KelvinWorkload -Name my-app -Detailed

    Retrieves detailed information for a specific workload.

.EXAMPLE
    PS> Get-KelvinWorkload -AppName my-app -DownloadStatus ready

    Lists workloads for an application that are ready to run.
#>

Function Get-KelvinWorkload {
    [CmdletBinding()]
    [OutputType('Kelvin.Workload')]
    Param 
    (
        # Free-form text search across workload fields
        [Parameter(Position = 0, ParameterSetName = 'Query')]
        [string[]] $Search,

        # Filter workloads by name
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string[]] $Name,

        # Filter workloads by application name
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('app_name')]
        [string[]] $AppName,

        # Filter workloads by application version
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('app_version')]
        [string[]] $AppVersion,

        # Filter workloads by cluster name
        [Parameter(ParameterSetName = 'Query', ValueFromPipelineByPropertyName = $true)]
        [Alias('cluster_name')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string[]] $ClusterName,

        # Filter workloads by node name
        [Parameter(ParameterSetName = 'Query')]
        [Alias('node_name')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [string[]] $NodeName,

        # Filter workloads by enabled status
        [Parameter(ParameterSetName = 'Query')]
        [Nullable[bool]] $Enabled,

        # Filter workloads by associated asset name
        [Parameter(ParameterSetName = 'Query')]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('asset_name')]
        [string] $AssetName,

        # Filter workloads by staged status
        [Parameter(ParameterSetName = 'Query')]
        [Nullable[bool]] $Staged,

        # Filter workloads by download status: pending, scheduled, processing, ready, or failed
        [Parameter(ParameterSetName = 'Query')]
        [ValidateSet('pending', 'scheduled', 'processing', 'ready', 'failed')]
        [Alias('download_status')]
        [string[]] $DownloadStatus,

        # Return detailed workload information by making an additional API call for each workload
        [Parameter()]
        [switch] $Detailed
    )

    Process {
        $params = _GetParams

        # Call the Kelvin API to list workloads
        Invoke-KelvinApi 'workloads/list' -Method Get -TypeName 'Kelvin.Workload' -Parameters $params `
        | ForEach-Object {
            if ($Detailed.IsPresent) {
                $ret = (Invoke-KelvinApi "workloads/$($_.name)/get" -Method Get -TypeName 'Kelvin.Workload')
            }
            else {
                $ret = $_
            }
            Write-Output $ret | Add-Member -Name 'state' -Value $_.status.state -MemberType NoteProperty -PassThru
        }
    }
}
#EndRegion './public/Workload/Get-KelvinWorkload.ps1' 109
#Region './public/Workload/Get-KelvinWorkloadConfiguration.ps1' -1

<#
.SYNOPSIS
    Gets the configuration of a workload.

.DESCRIPTION
    Retrieves the configuration of a workload from the currently connected
    Kelvin instance.

.EXAMPLE
    PS> Get-KelvinWorkloadConfiguration -Name my-workload

    Returns the configuration for the specified workload.
#>

function Get-KelvinWorkloadConfiguration {
    [CmdletBinding()]
    param (
        # The name of the workload to get the configuration for
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string] $Name
    )

    process {
        $result = Invoke-KelvinApi "workloads/$Name/configurations/get" -Method Get
        if ($result.PSObject.Properties.Name -contains 'configuration') {
            Write-Output $result.configuration
        }
        else {
            Write-Output $result
        }
    }
}
#EndRegion './public/Workload/Get-KelvinWorkloadConfiguration.ps1' 34
#Region './public/Workload/Get-KelvinWorkloadLog.ps1' -1

<#
.SYNOPSIS
    Gets logs for a workload.

.DESCRIPTION
    Retrieves the logs of a workload from the currently connected Kelvin instance.
    Supports filtering by number of recent lines or by start time.

.EXAMPLE
    PS> Get-KelvinWorkloadLog -Name my-workload

    Returns all available logs for the specified workload.

.EXAMPLE
    PS> Get-KelvinWorkloadLog -Name my-workload -TailLines 100

    Returns the last 100 log lines for the specified workload.

.EXAMPLE
    PS> Get-KelvinWorkloadLog -Name my-workload -SinceTime (Get-Date).AddHours(-1)

    Returns logs generated in the last hour.
#>

function Get-KelvinWorkloadLog {
    [CmdletBinding()]
    param (
        # The name of the workload to get logs for
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string] $Name,

        # Number of most recent log lines to retrieve
        [Parameter()]
        [Alias('tail_lines')]
        [int] $TailLines,

        # UTC start time for the log retrieval window
        [Parameter()]
        [Alias('since_time')]
        [datetime] $SinceTime
    )

    process {
        $params = @{}
        if ($PSBoundParameters.ContainsKey('TailLines')) {
            $params['tail_lines'] = $TailLines
        }
        if ($PSBoundParameters.ContainsKey('SinceTime')) {
            $params['since_time'] = $SinceTime.ToUniversalTime().ToString('o')
        }

        $result = Invoke-KelvinApi "workloads/$Name/logs/get" -Method Get -Parameters $params
        if ($result.PSObject.Properties.Name -contains 'logs') {
            Write-Output $result.logs
        }
        else {
            Write-Output $result
        }
    }
}
#EndRegion './public/Workload/Get-KelvinWorkloadLog.ps1' 62
#Region './public/Workload/Install-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Installs (applies) one or more staged workloads.

.DESCRIPTION
    Initiates the final deploy action for workloads that were previously deployed
    with the staged option. Only valid for workloads deployed with staged mode
    and without instant apply.

.EXAMPLE
    PS> Install-KelvinWorkload -Name my-workload

    Applies the staged workload to finalize deployment.

.EXAMPLE
    PS> Install-KelvinWorkload -Name wl-a, wl-b

    Applies multiple staged workloads at once.
#>

function Install-KelvinWorkload {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        # Staged workload name(s) to apply
        [Parameter(Position = 0, Mandatory = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_names')]
        [string[]] $Name
    )

    process {
        if (-not $PSCmdlet.ShouldProcess(($Name -join ', '), 'Apply staged workloads')) {
            return
        }

        $body = @{
            workload_names = $Name
        }

        try {
            Invoke-KelvinApi 'workloads/apply' -Method Post -Body $body | Out-Null
        }
        catch {
            Write-Error "Failed to apply staged workloads: $_"
        }
    }
}
#EndRegion './public/Workload/Install-KelvinWorkload.ps1' 47
#Region './public/Workload/New-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Creates a new workload on a cluster.

.DESCRIPTION
    Deploys an application from the App Registry as a new workload to a cluster
    or node on the currently connected Kelvin instance. Use individual parameters
    for common scenarios, or the Body parameter for advanced payloads.

.EXAMPLE
    PS> New-KelvinWorkload -AppName my-app -ClusterName my-cluster

    Creates a workload from the latest version of the application on the specified cluster.

.EXAMPLE
    PS> New-KelvinWorkload -AppName my-app -AppVersion 1.2.0 -ClusterName my-cluster -Name my-workload -Staged

    Creates a staged workload from a specific application version with a custom name.

.EXAMPLE
    PS> New-KelvinWorkload -Body @{ app_name = 'my-app'; cluster_name = 'my-cluster'; payload = @{ inputs = @{} } }

    Creates a workload using a raw body hashtable for advanced scenarios.
#>

function New-KelvinWorkload {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Properties')]
    [OutputType('Kelvin.Workload')]
    param (
        # The application name from the App Registry to deploy
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Properties')]
        [ValidatePattern('^[a-z]([-a-z0-9]*[a-z0-9])?$')]
        [Alias('app_name')]
        [string] $AppName,

        # A unique name for the workload (auto-generated if not specified)
        [Parameter(ParameterSetName = 'Properties')]
        [ValidatePattern('^[a-z]([-a-z0-9]*[a-z0-9])?$')]
        [ValidateLength(1, 32)]
        [string] $Name,

        # A display title for the workload
        [Parameter(ParameterSetName = 'Properties')]
        [string] $Title,

        # The version of the application to deploy
        [Parameter(ParameterSetName = 'Properties')]
        [Alias('app_version')]
        [string] $AppVersion,

        # The target cluster name to deploy the workload to
        [Parameter(ParameterSetName = 'Properties')]
        [ValidatePattern('^[a-z]([-a-z0-9]*[a-z0-9])?$')]
        [Alias('cluster_name')]
        [string] $ClusterName,

        # Application parameters (inputs, outputs, info, spec version, system)
        [Parameter(ParameterSetName = 'Properties')]
        [hashtable] $Payload,

        # If specified, applies the deploy immediately without requiring a separate Install call
        [Parameter(ParameterSetName = 'Properties')]
        [Alias('instantly_apply')]
        [switch] $InstantlyApply,

        # If specified, Kelvin handles deploy with pre-download
        [Parameter(ParameterSetName = 'Properties')]
        [switch] $Staged,

        # Who or what initiated the deploy (KRN format)
        [Parameter(ParameterSetName = 'Properties')]
        [string] $Source,

        # A raw request body hashtable, passed directly to the API
        [Parameter(Mandatory = $true, ParameterSetName = 'Body')]
        [hashtable] $Body
    )

    process {
        $requestBody = if ($PSCmdlet.ParameterSetName -eq 'Body') {
            $Body
        }
        else {
            $b = @{ app_name = $AppName }
            if ($PSBoundParameters.ContainsKey('Name'))           { $b['name']            = $Name }
            if ($PSBoundParameters.ContainsKey('Title'))          { $b['title']           = $Title }
            if ($PSBoundParameters.ContainsKey('AppVersion'))     { $b['app_version']     = $AppVersion }
            if ($PSBoundParameters.ContainsKey('ClusterName'))    { $b['cluster_name']    = $ClusterName }
            if ($PSBoundParameters.ContainsKey('Payload'))        { $b['payload']         = $Payload }
            if ($InstantlyApply.IsPresent)                        { $b['instantly_apply'] = $true }
            if ($Staged.IsPresent)                                { $b['staged']          = $true }
            if ($PSBoundParameters.ContainsKey('Source'))         { $b['source']          = $Source }
            $b
        }

        $target = if ($requestBody.ContainsKey('name') -and $requestBody['name']) {
            $requestBody['name']
        }
        elseif ($requestBody.ContainsKey('app_name') -and $requestBody['app_name']) {
            $requestBody['app_name']
        }
        else {
            '<unspecified workload>'
        }
        if (-not $PSCmdlet.ShouldProcess($target, 'Create workload')) {
            return
        }

        try {
            $result = Invoke-KelvinApi 'workloads/deploy' -Method Post -Body $requestBody -TypeName 'Kelvin.Workload'
            Write-Output $result
        }
        catch {
            Write-Error "Failed to create workload: $_"
        }
    }
}
#EndRegion './public/Workload/New-KelvinWorkload.ps1' 117
#Region './public/Workload/Remove-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Removes (undeploys) one or more workloads.

.DESCRIPTION
    Undeploys one or more workloads from the currently connected Kelvin instance.
    By default, prompts for confirmation before each removal.

.EXAMPLE
    PS> Remove-KelvinWorkload -Name my-workload

    Removes the specified workload after confirmation.

.EXAMPLE
    PS> Remove-KelvinWorkload -Name my-workload -Force

    Removes the specified workload without prompting for confirmation.

.EXAMPLE
    PS> Get-KelvinWorkload -ClusterName my-cluster | Remove-KelvinWorkload -Force

    Removes all workloads in the specified cluster via pipeline input.
#>

function Remove-KelvinWorkload {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType('Kelvin.Workload')]
    param (
        # Workload name(s) to undeploy
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string[]] $Name,

        # If true, undeploy the staged instance
        [Parameter()]
        [bool] $Staged,

        # Bypass confirmation prompt
        [Parameter()]
        [switch] $Force
    )

    process {
        foreach ($w in $Name) {
            $target = $w
            $action = "Undeploy workload"

            if (-not ($Force.IsPresent -or $PSCmdlet.ShouldProcess($target, $action))) {
                continue
            }

            $params = @{}
            if ($PSBoundParameters.ContainsKey('Staged')) {
                $params['staged'] = $Staged
            }

            try {
                Invoke-KelvinApi "workloads/$w/undeploy" -Method Post -Parameters $params | Out-Null
            }
            catch {
                Write-Error "Failed to undeploy workload '$w': $_"
            }
        }
    }
}
#EndRegion './public/Workload/Remove-KelvinWorkload.ps1' 66
#Region './public/Workload/Set-KelvinWorkloadConfiguration.ps1' -1

<#
.SYNOPSIS
    Updates the configuration of a workload.

.DESCRIPTION
    Updates the configuration of a workload on the currently connected Kelvin
    instance. Use the Configuration parameter to pass a hashtable with the
    desired settings, or the Body parameter for advanced scenarios.

    WARNING: Sending an empty configuration object will remove all configurations.

.EXAMPLE
    PS> Set-KelvinWorkloadConfiguration -Name my-workload -Configuration @{ key1 = 'value1' }

    Updates the configuration for the specified workload.

.EXAMPLE
    PS> Set-KelvinWorkloadConfiguration -Name my-workload -Body @{ configuration = @{ key1 = 'value1' } }

    Updates the configuration using a raw body hashtable.
#>

function Set-KelvinWorkloadConfiguration {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Properties')]
    param (
        # The name of the workload to update the configuration for
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string] $Name,

        # The configuration hashtable to set on the workload
        [Parameter(Mandatory = $true, ParameterSetName = 'Properties')]
        [hashtable] $Configuration,

        # A raw request body hashtable, passed directly to the API
        [Parameter(Mandatory = $true, ParameterSetName = 'Body')]
        [hashtable] $Body
    )

    process {
        if (-not $PSCmdlet.ShouldProcess($Name, 'Update workload configuration')) {
            return
        }

        $requestBody = if ($PSCmdlet.ParameterSetName -eq 'Body') {
            $Body
        }
        else {
            @{ configuration = $Configuration }
        }

        try {
            $result = Invoke-KelvinApi "workloads/$Name/configurations/update" -Method Post -Body $requestBody
            if ($result.PSObject.Properties.Name -contains 'configuration') {
                Write-Output $result.configuration
            }
            else {
                Write-Output $result
            }
        }
        catch {
            Write-Error "Failed to update configuration for workload '$Name': $_"
        }
    }
}
#EndRegion './public/Workload/Set-KelvinWorkloadConfiguration.ps1' 66
#Region './public/Workload/Start-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Starts one or more workloads.

.DESCRIPTION
    Starts one or more workloads on the currently connected Kelvin instance.

.EXAMPLE
    PS> Start-KelvinWorkload -Name my-workload

    Starts the specified workload.

.EXAMPLE
    PS> Get-KelvinWorkload -ClusterName my-cluster | Start-KelvinWorkload

    Starts all workloads in the specified cluster via pipeline input.
#>

function Start-KelvinWorkload {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        # Workload name(s) to start
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string[]] $Name
    )

    process {
        foreach ($w in $Name) {
            if (-not $PSCmdlet.ShouldProcess($w, 'Start workload')) {
                continue
            }

            try {
                Invoke-KelvinApi "workloads/$w/start" -Method Get | Out-Null
            }
            catch {
                Write-Error "Failed to start workload '$w': $_"
            }
        }
    }
}
#EndRegion './public/Workload/Start-KelvinWorkload.ps1' 43
#Region './public/Workload/Stop-KelvinWorkload.ps1' -1

<#
.SYNOPSIS
    Stops one or more workloads.

.DESCRIPTION
    Stops one or more running workloads on the currently connected Kelvin instance.

.EXAMPLE
    PS> Stop-KelvinWorkload -Name my-workload

    Stops the specified workload.

.EXAMPLE
    PS> Get-KelvinWorkload -ClusterName my-cluster | Stop-KelvinWorkload

    Stops all workloads in the specified cluster via pipeline input.
#>

function Stop-KelvinWorkload {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        # Workload name(s) to stop
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidatePattern('^[a-z0-9][-_.a-z0-9]*[a-z0-9]$')]
        [Alias('workload_name')]
        [string[]] $Name
    )

    process {
        foreach ($w in $Name) {
            if (-not $PSCmdlet.ShouldProcess($w, 'Stop workload')) {
                continue
            }

            try {
                Invoke-KelvinApi "workloads/$w/stop" -Method Get | Out-Null
            }
            catch {
                Write-Error "Failed to stop workload '$w': $_"
            }
        }
    }
}
#EndRegion './public/Workload/Stop-KelvinWorkload.ps1' 43