Public/Enter-LLMChat.ps1

function Enter-LLMChat {
<#
.SYNOPSIS
    Enter an interactive REPL inside an [LLMChat] session.
    Type /help for all commands. /exit or Ctrl+C to leave.

.PARAMETER Chat
    An [LLMChat] from New-LLMChat. Accepts pipeline input.

.EXAMPLE
    $chat = New-LLMChat -Provider Anthropic -WithEnvironment -Agentic -Name "Dev"
    Enter-LLMChat $chat
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [PSCustomObject]$Chat
    )
    process {
        $c = $script:C; $b = $script:Box
        script:Write-Banner
        $agentTag = if ($Chat.Agentic) { "$($c.Green) agentic$($c.Reset)" } else { '' }
        script:Write-Status "Entering $($Chat.Id) · $($Chat.Provider) · $($Chat.Model)$agentTag" 'ok'
        script:Write-Status "Type /help for commands · /exit to leave" 'info'
        Write-Host ""

        $last = $null

        while ($true) {
            script:Write-Prompt -Id $Chat.Id -Turn ($Chat.TurnCount + 1)
            $line = $Host.UI.ReadLine()
            if ([string]::IsNullOrWhiteSpace($line)) { continue }

            switch -Regex ($line.Trim()) {

                '^/(exit|quit|bye)$' {
                    Write-Host ""
                    script:Write-Status "Left $($Chat.Id) · $($Chat.TurnCount) turns · $($Chat.TotalTokensUsed) tokens" 'ok'
                    script:Write-Rule
                    return
                }

                '^/help$' {
                    $rows = @(
                        '/help',                'Show this reference'
                        '/exit /quit',         'Leave the chat session'
                        '/history',             'Print conversation history'
                        '/env',                 'Show the PS environment snapshot'
                        '/directives',          'List discovered claude.md module directives'
                        '/expand',              'Expand steps in the last response'
                        '/expand <N>',          'Expand steps in response N (1-based)'
                        '/tools',               'Show tool calls made in the last response'
                        '/stats',               'Token usage and session info'
                        '/clear',               'Clear the screen'
                        '/model <name>',        'Switch model mid-session'
                        '/system <text>',       'Replace the system prompt'
                        '/agentic on|off',      'Toggle agentic tool-use mode'
                        '/swarm <goal>',        'Spawn a parallel swarm from this chat'
                        '/swarm-results',       'Show task breakdown from last swarm'
                        '/run <expression>',    'Execute a PS expression locally and print result'
                    )
                    Write-Host ""
                    Write-Host " $($c.Amber)$($c.Bold)CHAT COMMANDS$($c.Reset)"
                    script:Write-Rule -Color $c.Slate
                    for ($i = 0; $i -lt $rows.Count; $i += 2) {
                        Write-Host " $($c.Cyan)$($rows[$i].PadRight(22))$($c.Reset)$($c.Silver)$($rows[$i+1])$($c.Reset)"
                    }
                    Write-Host ""
                    continue
                }

                '^/history$' {
                    Write-Host ""; script:Write-Rule -Label 'HISTORY' -Color $c.Slate
                    $n = 1
                    foreach ($t in $Chat.History) {
                        $rc  = if ($t.Role -eq 'user') { $c.Cyan } else { $c.Amber }
                        $pre = if ($t.Content.Length -gt 110) { $t.Content.Substring(0,107)+'...' } else { $t.Content }
                        Write-Host " $($c.Silver)$($n.ToString().PadLeft(3))$($c.Reset) $rc$($t.Role.ToUpper().PadRight(10))$($c.Reset) $($c.White)$pre$($c.Reset)"
                        $n++
                    }
                    Write-Host ""
                    continue
                }

                '^/env$' {
                    $e = Get-LLMEnvironment
                    Write-Host ""; script:Write-Rule -Label 'ENVIRONMENT' -Color $c.Slate
                    Write-Host " $($c.Silver)PS Version $($c.Reset)$($e.PSVersion)"
                    Write-Host " $($c.Silver)OS $($c.Reset)$($e.OS)"
                    Write-Host " $($c.Silver)Platform $($c.Reset)$($e.Platform) / $($e.Architecture)"
                    Write-Host " $($c.Silver)Directory $($c.Reset)$($e.CurrentDirectory)"
                    Write-Host " $($c.Silver)User $($c.Reset)$($e.UserName) @ $($e.MachineName)"
                    $ml = ($e.LoadedModules | Select-Object -First 10 | ForEach-Object { $_.Name }) -join ', '
                    Write-Host " $($c.Silver)Modules ($($e.ModuleCount)) $($c.Reset)$ml…"
                    Write-Host " $($c.Silver)Commands $($c.Reset)$($e.CommandCount)"
                    Write-Host ""
                    continue
                }

                '^/directives$' {
                    $directives = @(Get-LLMModuleDirectives)
                    if ($directives.Count -eq 0) {
                        script:Write-Status 'No claude.md directives found in loaded modules' 'warn'
                    } else {
                        script:Write-DirectivesBlock -Directives $directives
                    }
                    continue
                }

                '^/expand$' {
                    if ($null -eq $last) { script:Write-Status 'No response yet' 'warn'; continue }
                    if ($last.Steps.Count -eq 0) { script:Write-Status 'No structured steps in last response' 'warn'; continue }
                    Write-Host ""; script:Write-Rule -Label "STEPS — last response ($($last.Steps.Count))" -Color $c.Slate
                    script:Write-StepsBlock -Steps $last.Steps -Expanded $true
                    continue
                }

                '^/expand\s+(\d+)$' {
                    $idx = [int]$Matches[1] - 1
                    if ($idx -lt 0 -or $idx -ge $Chat.Responses.Count) {
                        script:Write-Status "Response $($Matches[1]) not found (session has $($Chat.Responses.Count))" 'warn'; continue
                    }
                    $tgt = $Chat.Responses[$idx]
                    if ($tgt.Steps.Count -eq 0) { script:Write-Status "Response $($Matches[1]) has no steps" 'warn'; continue }
                    Write-Host ""; script:Write-Rule -Label "STEPS — response $($Matches[1]) ($($tgt.Steps.Count))" -Color $c.Slate
                    script:Write-StepsBlock -Steps $tgt.Steps -Expanded $true
                    continue
                }

                '^/tools$' {
                    if ($null -eq $last) { script:Write-Status 'No response yet' 'warn'; continue }
                    if ($last.ToolCalls.Count -eq 0) { script:Write-Status 'Last response made no tool calls' 'info'; continue }
                    Write-Host ""; script:Write-Rule -Label "TOOL CALLS ($($last.ToolCalls.Count))" -Color $c.Slate
                    foreach ($tc in $last.ToolCalls) {
                        script:Write-ToolCallBox -Expression $tc.Expression -Result $tc.Output `
                            -IsError $tc.IsError -CallNum $tc.CallNum
                    }
                    continue
                }

                '^/stats$' {
                    Write-Host ""; script:Write-Rule -Label 'SESSION STATS' -Color $c.Slate
                    Write-Host " $($c.Silver)Session $($c.Reset)$($Chat.Id)"
                    Write-Host " $($c.Silver)Provider $($c.Reset)$($Chat.Provider)"
                    Write-Host " $($c.Silver)Model $($c.Reset)$($Chat.Model)"
                    Write-Host " $($c.Silver)Agentic $($c.Reset)$($Chat.Agentic)"
                    Write-Host " $($c.Silver)Turns $($c.Reset)$($Chat.TurnCount)"
                    Write-Host " $($c.Silver)Tokens used $($c.Reset)$($Chat.TotalTokensUsed)"
                    if ($Chat.Responses.Count -gt 0) {
                        $avg = [math]::Round(($Chat.Responses | Measure-Object ElapsedSec -Average).Average,2)
                        Write-Host " $($c.Silver)Avg latency $($c.Reset)${avg}s"
                        $tc = ($Chat.Responses | ForEach-Object { $_.ToolCalls.Count } | Measure-Object -Sum).Sum
                        Write-Host " $($c.Silver)Tool calls $($c.Reset)$tc"
                    }
                    Write-Host " $($c.Silver)Started $($c.Reset)$($Chat.CreatedAt.ToString('u'))"
                    Write-Host ""
                    continue
                }

                '^/clear$' {
                    Clear-Host; script:Write-Banner
                    continue
                }

                '^/model\s+(.+)$' {
                    $Chat.Model = $Matches[1].Trim()
                    script:Write-Status "Model → $($Chat.Model)" 'ok'
                    continue
                }

                '^/system\s+(.+)$' {
                    $Chat.SystemPrompt = $Matches[1].Trim()
                    script:Write-Status "System prompt updated" 'ok'
                    continue
                }

                '^/agentic\s+(on|off)$' {
                    $Chat.Agentic = ($Matches[1] -eq 'on')
                    $state = if ($Chat.Agentic) { "$($c.Green)enabled$($c.Reset)" } else { "$($c.Silver)disabled$($c.Reset)" }
                    script:Write-Status "Agentic mode $state" 'ok'
                    continue
                }

                '^/swarm\s+(.+)$' {
                    $swarmGoal = $Matches[1].Trim()
                    script:Write-Status "Launching swarm from chat $($Chat.Id)…" 'info'
                    try {
                        $swResult = Invoke-LLMSwarm -Goal $swarmGoal -Provider $Chat.Provider -Model $Chat.Model
                        $Chat.LastSwarm = $swResult
                        $Chat.TotalTokensUsed += $swResult.TotalTokens
                        $Chat.History.Add([PSCustomObject]@{Role='user';     Content="[SWARM] $swarmGoal"})
                        $Chat.History.Add([PSCustomObject]@{Role='assistant';Content=$swResult.Synthesis})
                        $Chat.TurnCount++
                    } catch {
                        script:Write-Status "Swarm failed: $_" 'err'
                    }
                    continue
                }

                '^/swarm-results$' {
                    if ($null -eq $Chat.LastSwarm) {
                        script:Write-Status 'No swarm has run in this session yet' 'warn'; continue
                    }
                    Write-Host ""; script:Write-Rule -Label "SWARM TASKS ($($Chat.LastSwarm.Tasks.Count))" -Color $c.Slate
                    foreach ($t in $Chat.LastSwarm.Tasks) { script:Write-SwarmTaskLine -Task $t }
                    Write-Host ""
                    continue
                }

                '^/run\s+(.+)$' {
                    $expr = $Matches[1].Trim()
                    try {
                        Write-Host ""; script:Write-Rule -Label "PS › $expr" -Color $c.Slate
                        Invoke-Expression $expr 2>&1 | ForEach-Object {
                            Write-Host " $($c.Dim)$_$($c.Reset)"
                        }
                        Write-Host ""
                    } catch {
                        script:Write-Status "Error: $_" 'err'
                    }
                    continue
                }

                default {
                    $last = Send-LLMMessage -Chat $Chat -Message $line
                    if ($last.Steps.Count -gt 0) {
                        script:Write-StepsBlock -Steps $last.Steps -Expanded $false
                    }
                }
            }
        }
    }
}