Public/New-KritOpenApiAgenticSidecar.ps1

function New-KritOpenApiAgenticSidecar {
<#
.SYNOPSIS
    Emit an agentic-sidecar companion script for a Krit.<Brand>OpenApi module.

.DESCRIPTION
    Per WAVE-5079 design: each generated OpenAPI client module ships with a
    companion sidecar that lets an off-to-the-side agent (claude / codex /
    cowork) connect, prove and report end-to-end without the main session
    orchestrating step-by-step.

    The sidecar exposes:
      - Test-<Brand>ApiConnectivity : single-call health probe (auth + base
        URI + one read-only GET against a canary endpoint). Returns
        structured { ok, latencyMs, baseUri, authShape, error }.
      - Resolve-<Brand>AgentTask : takes a natural-language task hint plus
        a dictionary of inputs, routes to the most-likely module verb,
        invokes it, and returns the structured result. Caller agent then
        decides next step.

    v0.1.0 emits a template skeleton; the matchmaker logic in
    Resolve-<Brand>AgentTask is a stub that the consuming agent fills with
    domain heuristics (or replaces with an LLM-routing call).

.PARAMETER Spec
    OpenAPI spec — used to enumerate available verbs the sidecar exposes.

.PARAMETER Brand
    Pascal-cased brand.

.PARAMETER OutFile
    Output .ps1 path (sidecar is a single script that dot-sources alongside
    the generated module).

.EXAMPLE
    New-KritOpenApiAgenticSidecar -Spec ./pax8-openapi.json -Brand Pax8 `
        -OutFile ./Krit.Pax8OpenApi.AgenticSidecar.ps1
#>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string]$Spec,
        [Parameter(Mandatory)][string]$Brand,
        [Parameter(Mandatory)][string]$OutFile
    )

    if (-not (Test-Path -LiteralPath $Spec)) { throw "Spec not found: $Spec" }

    $doc = Get-Content -LiteralPath $Spec -Raw | ConvertFrom-Json -Depth 100
    $baseUri = if ($doc.servers -and $doc.servers.Count -gt 0) { $doc.servers[0].url } else { 'http://127.0.0.1:80' }

    # First GET path = canary
    $canary = $null
    foreach ($p in $doc.paths.PSObject.Properties.Name) {
        if ($doc.paths.$p.PSObject.Properties.Name -contains 'get') { $canary = $p; break }
    }
    if (-not $canary) { $canary = '/' }

    $verbsCsv = ($doc.paths.PSObject.Properties.Name | Select-Object -First 20) -join "', '"

    $tpl = @"
# ──────────────────────────────────────────────────────────────────────────
# Krit.${Brand}OpenApi — Agentic Sidecar (auto-generated WAVE-5105)
# ──────────────────────────────────────────────────────────────────────────
# Dot-source this script alongside Krit.${Brand}OpenApi.psm1 to expose two
# operator-callable functions that let an off-to-the-side agent connect,
# prove, and route tasks against the upstream without main-session help.
# ──────────────────────────────────────────────────────────────────────────

function Test-${Brand}ApiConnectivity {
    [CmdletBinding()]
    param(
        [string]`$BaseUri = '$baseUri',
        [string]`$CanaryPath = '$canary'
    )
    `$startUtc = [DateTime]::UtcNow
    try {
        `$resp = Invoke-RestMethod -Method GET -Uri (`$BaseUri.TrimEnd('/') + `$CanaryPath) -TimeoutSec 15
        [pscustomobject]@{
            Ok = `$true
            LatencyMs = [int]([DateTime]::UtcNow - `$startUtc).TotalMilliseconds
            BaseUri = `$BaseUri
            Canary = `$CanaryPath
            Sample = `$resp | Select-Object -First 1
            Error = `$null
            AtUtc = `$startUtc.ToString('o')
        }
    } catch {
        [pscustomobject]@{
            Ok = `$false
            LatencyMs = [int]([DateTime]::UtcNow - `$startUtc).TotalMilliseconds
            BaseUri = `$BaseUri
            Canary = `$CanaryPath
            Sample = `$null
            Error = `$_.Exception.Message
            AtUtc = `$startUtc.ToString('o')
        }
    }
}

function Resolve-${Brand}AgentTask {
<#
.SYNOPSIS
    Route a natural-language task hint to the most-likely Krit.${Brand}OpenApi verb.
.PARAMETER TaskHint
    Free-text task description from the calling agent.
.PARAMETER Inputs
    Hashtable of named parameters to pass to the chosen verb.
.PARAMETER DryRun
    Plan-only: returns the chosen verb + inputs without invoking.
#>
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string]`$TaskHint,
        [hashtable]`$Inputs = @{},
        [switch]`$DryRun
    )

    # Stub matchmaker — replace with LLM router OR domain-specific heuristics.
    # Lists the first 20 paths the spec exposed at sidecar-gen time so the
    # calling agent can see what's reachable.
    `$availablePaths = @('$verbsCsv')

    `$candidate = `$availablePaths | Where-Object {
        `$_ -match (`$TaskHint -replace '[^\w]','.*')
    } | Select-Object -First 1

    `$plan = [pscustomobject]@{
        TaskHint = `$TaskHint
        ChosenPath = `$candidate
        Inputs = `$Inputs
        AvailablePaths = `$availablePaths
        DryRun = `$DryRun.IsPresent
        AtUtc = (Get-Date).ToUniversalTime().ToString('o')
    }
    if (`$DryRun) { return `$plan }

    # Real-invocation hook: caller fills in mapping path -> Get-Command verb.
    Write-Warning "Resolve-${Brand}AgentTask: real-invocation logic not implemented in v0.1.0 sidecar. Returning plan only."
    `$plan
}
"@


    if ($PSCmdlet.ShouldProcess($OutFile, "Emit Krit.${Brand}OpenApi agentic sidecar")) {
        $tpl | Set-Content -LiteralPath $OutFile -Encoding UTF8
        [pscustomobject]@{
            OutFile     = (Resolve-Path -LiteralPath $OutFile).Path
            Brand       = $Brand
            BaseUri     = $baseUri
            CanaryPath  = $canary
            GeneratedAt = (Get-Date).ToUniversalTime().ToString('o')
        }
    }
}