Public/Startup/Initialize-PwshProfile.ps1

function Initialize-PwshProfile {
    <#
    .SYNOPSIS
        Runs the default Screw City profile startup: banner, shell config, prompt, tools, and
        shell completions.
 
    .DESCRIPTION
        Reproduces the historical inline profile startup as a single call. In order it:
          1. Shows the startup banner (Write-Figlet).
          2. Runs "Shell": the `which` global alias and PSReadLine setup.
          3. Runs "Prompt": oh-my-posh, Terminal-Icons, and posh-git — oh-my-posh first, since
             it is the prompt engine and table stakes for this profile.
          4. Runs "Tools": zoxide, fzf, fnm, and xh — fzf sits next to zoxide (zoxide's
             interactive picker auto-uses fzf when it's on PATH), and fnm follows zoxide since
             Enable-FastNodeManager wraps zoxide's cd hook — then "Completions": winget,
             Azure CLI, Tailscale, Docker, 1Password, and GitHub CLI (registration only — these install nothing),
             since the completions are operations on the tools.
 
        Each of the three sections is its own top-level Invoke-Step, so each renders its own status
        spinner and a single summary line (Completions is a nested step under Tools). Steps that
        depend on a missing tool degrade silently (guarded by Get-Command / Import-ModuleSafe), so
        this never throws out of profile startup.
 
        Use -Theme to choose a bundled theme ('screwcity' or 'forestcity'), or -CustomTheme to point
        oh-my-posh at a theme file of your own (the two are mutually exclusive). The banner text
        defaults to the machine name ($env:COMPUTERNAME) regardless of theme; the bundled themes each
        carry a matching banner color and step marker — picking 'forestcity' defaults the banner to
        the theme's green with a 🌳 marker, while 'screwcity' keeps purple / 🔩 — applied only to the
        banner color/icon you don't set explicitly.
        Use -ZoxideCommand to rename zoxide's jump command, -StepIcon to rebrand the step marker,
        -Skip to opt out of individual tools (e.g. to avoid an unwanted winget auto-install), and
        -SkipSection to opt out of whole sections.
 
        It deliberately runs only the module's own startup — any other personal profile scripts you
        keep in $PROFILE are left untouched.
 
    .PARAMETER BannerText
        Text rendered by the startup banner. When omitted, defaults to the machine name
        ($env:COMPUTERNAME) for every theme. An empty or whitespace value renders no banner at all.
 
    .PARAMETER BannerColor
        Spectre color name or hex for the banner. When omitted, defaults to the selected theme's
        signature color (screwcity's purple '#c9aaff' or forestcity's green '#8fce72').
 
    .PARAMETER BannerAlignment
        Banner alignment: 'Left', 'Center', or 'Right'. Defaults to 'Left'.
 
    .PARAMETER BannerFont
        A bundled FIGlet font for the banner (tab-completes), forwarded to Write-Figlet as -Font.
        Mutually exclusive with -BannerFontPath. When neither is given, Write-Figlet's default
        ('ANSIShadow') is used. Run Show-FigletFont to list the bundled fonts (or -Preview to see
        samples).
 
    .PARAMETER BannerFontPath
        Path to a custom .flf FIGlet font for the banner, forwarded to Write-Figlet as -FontPath.
        Mutually exclusive with -BannerFont. Validated to exist at call time.
 
    .PARAMETER Theme
        The bundled oh-my-posh theme to use (tab-completes): 'screwcity' (default) or 'forestcity'.
        Resolved to its file under Assets/Themes and forwarded to Enable-OhMyPosh as -Configuration.
        The choice also seeds the banner color and step icon for any you don't set explicitly (the
        banner text defaults to the machine name regardless of theme). Mutually exclusive with
        -CustomTheme. Run Get-OhMyPoshTheme to dump a bundled theme's JSON as a starting point for your own.
 
    .PARAMETER CustomTheme
        Path (relative or absolute) to a custom oh-my-posh theme file, forwarded to Enable-OhMyPosh
        as -Configuration in place of a bundled theme. The path is validated to exist at call time,
        so a typo surfaces immediately rather than silently falling back to the bundle. Mutually
        exclusive with -Theme; banner branding falls back to the screwcity defaults.
 
    .PARAMETER ZoxideCommand
        The command name zoxide binds for jumping, forwarded to Enable-Zoxide as -Command.
        Defaults to 'cd' (replacing the built-in cd); pass e.g. 'z' to keep cd intact.
 
    .PARAMETER StepIcon
        The marker printed before each top-level step description, forwarded to Invoke-Step as
        -Icon. Defaults to ':nut_and_bolt:' (a Spectre emoji shortcode, rendered as 🔩). No trailing
        space is needed — the separator between the icon and the step text is added at render time.
 
    .PARAMETER Skip
        Individual tools to skip: 'Banner', 'PSReadLine', 'TerminalIcons', 'PoshGit', 'Zoxide',
        'Fzf', 'Fnm', 'Xh', 'Jq', 'Completions'. Dropping one omits its step; the auto-installing ones
        (Zoxide, Fzf, Fnm, Xh, Jq) thereby decline an unwanted winget install. 'Completions' drops the shell-completion
        registrations (winget, Azure CLI, Tailscale, Docker, 1Password, GitHub CLI) that run as the final Tools sub-step.
        oh-my-posh is table stakes for this profile and has no token in either parameter — it always
        runs. To skip whole sections, use -SkipSection.
 
    .PARAMETER SkipSection
        Whole sections to skip: 'Shell', 'Prompt', 'Tools'. Each drops the block and its summary
        line; skipping 'Tools' also drops the completions registered under it. 'Prompt' is special:
        because oh-my-posh is unskippable, passing it does NOT drop oh-my-posh — it drops only the
        cosmetic extras (Terminal-Icons + posh-git) and emits a warning explaining oh-my-posh was kept.
 
    .EXAMPLE
        Initialize-PwshProfile
 
        Runs the full default startup — equivalent to the former inline profile.
 
    .EXAMPLE
        Initialize-PwshProfile -BannerText 'HELLO' -BannerColor Green -BannerAlignment Center
 
        Same startup with a centered green "HELLO" banner.
 
    .EXAMPLE
        Initialize-PwshProfile -BannerFont ANSIShadow
 
        Renders the startup banner in the bundled large ANSI Shadow block font.
 
    .EXAMPLE
        Initialize-PwshProfile -Theme forestcity
 
        Uses the bundled Forest City theme, with the machine-name banner in the theme's green and a 🌳
        step marker applied automatically.
 
    .EXAMPLE
        Initialize-PwshProfile -CustomTheme '~/.config/themes/custom.omp.json' -Skip Fnm,Xh
 
        Uses a custom oh-my-posh theme and skips the fnm and xh steps (so neither is auto-installed).
 
    .EXAMPLE
        Initialize-PwshProfile -Skip Completions
 
        Runs startup but skips the shell-completion registrations (winget, Azure CLI, Tailscale,
        Docker, 1Password) under Tools.
 
    .NOTES
        Call from $PROFILE right after Import-Module of the manifest. The Completions step uses the
        per-tool enablers Enable-WingetCompletion, Enable-AzureCliCompletion, Enable-TailscaleCompletion,
        Enable-DockerCompletion, Enable-1PasswordCompletion, and Enable-GithubCliCompletion.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Bundled')]
    param(
        # Banner text defaults to the machine name; color/icon default to the selected theme's
        # branding. The empty-string sentinels are resolved in the body (banner text from
        # $env:COMPUTERNAME, color/icon from Get-BundledThemeBranding).
        [Parameter(Position = 0)]
        [string]$BannerText = '',

        [Parameter()]
        [string]$BannerColor = '',

        [Parameter()]
        [ValidateSet('Left', 'Center', 'Right')]
        [string]$BannerAlignment = 'Left',

        [Parameter()]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                # Completers run in the caller's scope; Show-FigletFont (no args) lists the names.
                Show-FigletFont | Where-Object { $_ -like "$wordToComplete*" } |
                    ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
            })]
        [ValidateScript({ $_ -in (Get-BundledFontName) },
            ErrorMessage = "'{0}' is not a bundled font. Run Show-FigletFont to list the available fonts.")]
        [string]$BannerFont,

        [Parameter()]
        [ValidateScript({
                [string]::IsNullOrWhiteSpace($_) -or (Test-Path -Path $_) },
            ErrorMessage = "BannerFontPath '{0}' does not exist (expected a path to a .flf FIGlet font file).")]
        [string]$BannerFontPath,

        [Parameter(ParameterSetName = 'Bundled')]
        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                # Completers run in the caller's scope, where the module-private Get-BundledThemeName
                # is not visible — resolve the bundled themes from the loaded module's base path.
                $base = (Get-Module ScrewCitySoftware.PwshProfile).ModuleBase
                if ($base) {
                    Get-ChildItem -Path (Join-Path $base 'Assets' 'Themes') -Filter *.omp.json -ErrorAction SilentlyContinue |
                        ForEach-Object { $_.Name -replace '\.omp\.json$', '' } |
                        Where-Object { $_ -like "$wordToComplete*" } |
                        ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
                }
            })]
        [ValidateScript({ $_ -in (Get-BundledThemeName) },
            ErrorMessage = "'{0}' is not a bundled theme. Run Get-OhMyPoshTheme or check Assets/Themes for the available themes.")]
        [string]$Theme = 'screwcity',

        [Parameter(Mandatory, ParameterSetName = 'Custom')]
        [ValidateScript({ Test-Path -Path $_ },
            ErrorMessage = "CustomTheme path '{0}' does not exist (expected a relative or absolute path to an .omp.json).")]
        [string]$CustomTheme,

        [Parameter()]
        [string]$ZoxideCommand = 'cd',

        [Parameter()]
        [string]$StepIcon = '',

        [Parameter()]
        [ValidateSet('Banner', 'PSReadLine', 'TerminalIcons', 'PoshGit', 'Zoxide', 'Fzf', 'Fnm', 'Xh', 'Jq', 'Completions')]
        [string[]]$Skip = @(),

        [Parameter()]
        [ValidateSet('Shell', 'Prompt', 'Tools')]
        [string[]]$SkipSection = @()
    )

    # Resolve the oh-my-posh configuration and the matching banner branding from the chosen theme.
    # A custom theme has no bundled branding, so it falls back to the screwcity defaults ($Theme
    # keeps its 'screwcity' default value even in the Custom parameter set).
    if ($PSCmdlet.ParameterSetName -eq 'Custom') {
        $resolvedTheme = $CustomTheme
        $branding = Get-BundledThemeBranding -Name 'screwcity'
    }
    else {
        $resolvedTheme = Get-BundledThemePath -Name $Theme
        $branding = Get-BundledThemeBranding -Name $Theme
    }
    # Banner text defaults to the machine name for every theme; color/icon come from the theme branding.
    if (-not $PSBoundParameters.ContainsKey('BannerText'))  { $BannerText  = $env:COMPUTERNAME }
    if (-not $PSBoundParameters.ContainsKey('BannerColor')) { $BannerColor = $branding.BannerColor }
    if (-not $PSBoundParameters.ContainsKey('StepIcon'))    { $StepIcon    = $branding.StepIcon }

    if ($Skip -notcontains 'Banner' -and -not [string]::IsNullOrWhiteSpace($BannerText)) {
        # Forward the font only when supplied; -Font and -FontPath are mutually exclusive on
        # Write-Figlet, so pass at most one.
        $bannerFontArgs = @{}
        if ($PSBoundParameters.ContainsKey('BannerFont'))     { $bannerFontArgs.Font = $BannerFont }
        elseif ($PSBoundParameters.ContainsKey('BannerFontPath')) { $bannerFontArgs.FontPath = $BannerFontPath }

        Write-Figlet -Text $BannerText -Color $BannerColor -Alignment $BannerAlignment @bannerFontArgs
        # Write-Figlet no longer emits a trailing blank line; add the gap before the Shell step
        # (guarded like the rest of the module so a missing PwshSpectreConsole never throws).
        if (Get-Command Write-SpectreHost -ErrorAction SilentlyContinue) { Write-SpectreHost '' }
    }

    if ($SkipSection -notcontains 'Shell') {
        Invoke-Step "Shell" -Icon $StepIcon {
            Invoke-Step "Global Aliases" {
                Set-Alias -Name which -Value where.exe -Scope Global
            }
            if ($Skip -notcontains 'PSReadLine') {
                Invoke-Step "PSReadLine" {
                    Initialize-PSReadline
                }
            }
        }
    }

    # The Prompt block always runs — oh-my-posh is table stakes, has no skip token,
    # and initializes first. -SkipSection Prompt drops only the cosmetic extras
    # (Terminal-Icons + posh-git); warn so the user knows oh-my-posh was kept.
    if ($SkipSection -contains 'Prompt') {
        Write-Warning "oh-my-posh is core to this profile and cannot be skipped; -SkipSection Prompt drops only Terminal-Icons and posh-git. Use -Skip TerminalIcons,PoshGit to control those individually."
    }
    Invoke-Step "Prompt" -Icon $StepIcon {
        Invoke-Step "Oh-My-Posh" { Enable-OhMyPosh -Configuration $resolvedTheme }
        if ($Skip -notcontains 'TerminalIcons' -and $SkipSection -notcontains 'Prompt') { Invoke-Step "Terminal-Icons" { Import-ModuleSafe Terminal-Icons } }
        if ($Skip -notcontains 'PoshGit' -and $SkipSection -notcontains 'Prompt') { Invoke-Step "Posh-Git" { Import-ModuleSafe posh-git -Initialize { $env:POSH_GIT_ENABLED = $true } } }
    }

    if ($SkipSection -notcontains 'Tools') {
        Invoke-Step "Tools" -Icon $StepIcon {
            if ($Skip -notcontains 'Zoxide') { Invoke-Step "Zoxide" { Enable-Zoxide -Command $ZoxideCommand } }
            if ($Skip -notcontains 'Fzf')    { Invoke-Step "fzf" { Enable-Fzf } }
            if ($Skip -notcontains 'Fnm')    { Invoke-Step "Fast Node Manager (fnm)" { Enable-FastNodeManager } }
            if ($Skip -notcontains 'Xh')     { Invoke-Step "xh" { Enable-Xh } }
            if ($Skip -notcontains 'Jq')     { Invoke-Step "jq" { Enable-Jq } }
            # Shell completions are operations on the tools, so they register as the final Tools
            # sub-step (registration only — these install nothing). Skipped via -Skip Completions,
            # and dropped wholesale when the whole Tools section is skipped.
            if ($Skip -notcontains 'Completions') {
                Invoke-Step "Completions" {
                    Invoke-Step "Winget Completions"    { Enable-WingetCompletion }
                    Invoke-Step "Azure CLI Completions" { Enable-AzureCliCompletion }
                    Invoke-Step "Tailscale Completions" { Enable-TailscaleCompletion }
                    Invoke-Step "Docker Completions"    { Enable-DockerCompletion }
                    Invoke-Step "1Password Completions" { Enable-1PasswordCompletion }
                    Invoke-Step "GitHub CLI Completions" { Enable-GithubCliCompletion }
                }
            }
        }
    }
}