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 Runs the profile startup as a single call. In order it: 1. Shows the startup banner (Write-Figlet), unless -NoBanner. 2. Runs "Core" (always): the `which` global alias, PSReadLine, oh-my-posh (the prompt engine, always on), Terminal-Icons, posh-git, and the shell completions (winget, Azure CLI, Tailscale, Docker, 1Password, GitHub CLI — registration only; they detect external CLIs and install nothing). Everything here except the `which` alias and oh-my-posh is opt-in. 3. Runs "WinGet" (only when ≥1 winget tool is enabled): zoxide, fzf, fnm, xh, jq, bat, fd, and less — the CLIs installed via WinGet. fzf sits next to zoxide (zoxide's interactive picker auto-uses fzf when on PATH); fnm registers a LocationChangedAction so it auto-switches the node version on any directory change (independent of zoxide and call order); fd follows fzf so it can wire fzf to use fd as its file source; and less is bat's pager (and PowerShell's, via $env:PAGER). The two groups mirror the install model: WinGet = tools installed via WinGet (opt-in), Core = everything else. Each is its own top-level Invoke-Step (its own status spinner + summary line). 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. The theme likewise seeds bat's syntax theme (-BatTheme), fd's LS_COLORS palette (-FdColors), and fzf's picker palette (-FzfColors) so those tools' colors blend with the prompt (screwcity -> Dracula/purple, forestcity -> gruvbox-dark/green). fzf also gets the `full` UI style and — via the PSFzf module — Ctrl+T (file picker, with a bat preview when bat is in play) and Ctrl+R (fuzzy history) key bindings, fd-backed traversal, and the Ctrl+G fuzzy-git chords (when git is present). Those PSFzf pickers are sized to fill the shell (--height=100%), overriding PSFzf's inline 40% default; a bare fzf and zoxide's `cdi` keep their native alternate-screen fullscreen. Tool selection is opt-in. Pass -Enable with the tools you want (e.g. -Enable Zoxide,Bat); only those run, so a tool added to the module in a later version never installs until you ask for it. Pass -EnableAll to enable every current tool and auto-adopt future additions. -Enable wins if both are given (the explicit list is the safer choice) and a warning notes -EnableAll was ignored. A bare call (neither, e.g. a hand-typed Initialize-PwshProfile) prompts before enabling everything when interactive, and enables nothing in a non-interactive session. oh-my-posh and the `which` alias always run; the banner is on by default and suppressed with -NoBanner. A tool-specific parameter (e.g. -ReplaceCat) for a tool that isn't enabled is warned about and ignored rather than throwing. Use -ZoxideCommand to rename zoxide's jump command, -StepIcon to rebrand the step marker, -BatTheme / -BatStyle to tune bat's appearance, -ReplaceCat to alias cat -> bat, -ReplaceMore to route the pager (more.com -> less) through $env:PAGER and alias more -> less, and -FdColors / -FzfColors to tune fd's and fzf's colors. 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. Must be non-empty — to render no banner, use -NoBanner. .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 BatTheme The bat syntax-highlighting theme, forwarded to Enable-Bat as -Theme (sets $env:BAT_THEME). When omitted, defaults to the selected theme's branding (screwcity's 'Dracula' or forestcity's 'gruvbox-dark') so bat's colors blend with the prompt. A value from `bat --list-themes`. .PARAMETER BatStyle The bat layout, forwarded to Enable-Bat as -Style (sets $env:BAT_STYLE) — a comma-separated list of components. Defaults to 'numbers,changes,header'. .PARAMETER ReplaceCat Forwarded to Enable-Bat as -ReplaceCat: when set, aliases cat -> bat for the session (so the built-in cat, an alias for Get-Content, is replaced by bat). Off by default. .PARAMETER ReplaceMore Forwarded to Enable-Less as -ReplaceMore: when set, sets $env:PAGER to 'less' (so PowerShell's `help`, bat, git, delta, and gh page through less instead of more.com) and aliases more -> less for the session. Off by default. .PARAMETER FdColors The LS_COLORS spec, forwarded to Enable-Fd as -LsColors (sets $env:LS_COLORS) so fd's output is tinted to match the prompt. When omitted, defaults to the selected theme's branding (screwcity's purple-led palette or forestcity's green-led one). fd stays a standalone utility and never replaces Get-ChildItem. Note: LS_COLORS is shared with ls/eza. .PARAMETER FzfColors The fzf `--color` spec, forwarded to Enable-Fzf as -Colors (folded into $env:FZF_DEFAULT_OPTS) so fzf's picker palette matches the prompt. When omitted, defaults to the selected theme's branding (screwcity's purple/cyan or forestcity's green/gold). .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 Enable The tools to enable (opt-in): any of 'PSReadLine', 'TerminalIcons', 'PoshGit', 'Zoxide', 'Fzf', 'Fnm', 'Xh', 'Jq', 'Bat', 'Fd', 'Less', 'Completions'. Only the listed tools run (and the auto-installing ones install); everything else is skipped, so a tool added in a later module version never installs unless you add it here. Pass -Enable @() to enable nothing. The set mirrors Get-PwshProfileToolCatalog. oh-my-posh and the `which` alias always run and are not tokens. .PARAMETER EnableAll Enable every tool in the catalog, including any added in future module versions. Convenient but opts into auto-installing future tools. If both -EnableAll and -Enable are given, -Enable wins (the explicit list is the safer choice) and a warning notes -EnableAll was ignored. .PARAMETER NoBanner Render no startup banner. Use this to suppress the banner instead of clearing -BannerText (which rejects empty). Passing banner params (e.g. -BannerColor) alongside -NoBanner warns and ignores them. .EXAMPLE Initialize-PwshProfile A bare call has no tool selection: interactively it asks whether to enable all tools; non-interactively it enables none. Generated profiles pass -Enable/-EnableAll, so they never prompt. .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 -Enable Zoxide,Bat,Fd Enables only zoxide, bat, and fd (plus the always-on prompt and `which`); no other tool installs. .EXAMPLE Initialize-PwshProfile -CustomTheme '~/.config/themes/custom.omp.json' -EnableAll -NoBanner Uses a custom oh-my-posh theme, enables every tool (and future additions), and shows no banner. .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 (color/icon use empty-string sentinels resolved in the body). BannerText takes a # real default and rejects empty — use -NoBanner to suppress the banner, not an empty string. [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [string]$BannerText = $env:COMPUTERNAME, [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', # Empty-string sentinel resolved in the body from the selected theme's branding (like # BannerColor), so -Theme alone gives bat a matching syntax theme. [Parameter()] [string]$BatTheme = '', [Parameter()] [string]$BatStyle = 'numbers,changes,header', [Parameter()] [switch]$ReplaceCat, [Parameter()] [switch]$ReplaceMore, # Empty-string sentinels resolved in the body from the selected theme's branding (like # BatTheme), so -Theme alone gives fd and fzf matching color palettes. [Parameter()] [string]$FdColors = '', [Parameter()] [string]$FzfColors = '', [Parameter()] [string]$StepIcon = '', # Opt-in tool selection. The ValidateSet mirrors Get-PwshProfileToolCatalog -Token; a test # (Tests/ToolCatalog.Tests.ps1) keeps the two in sync. No default, so PSBoundParameters tells # "passed empty (= nothing)" apart from "not passed (= bare-call confirm)". [Parameter()] [ValidateSet('PSReadLine', 'TerminalIcons', 'PoshGit', 'Completions', 'Zoxide', 'Fzf', 'Fnm', 'Xh', 'Jq', 'Bat', 'Fd', 'Less')] [string[]]$Enable, [Parameter()] [switch]$EnableAll, [Parameter()] [switch]$NoBanner ) # 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 } # Color/icon come from the theme branding when not set explicitly (BannerText has a real default). if (-not $PSBoundParameters.ContainsKey('BannerColor')) { $BannerColor = $branding.BannerColor } if (-not $PSBoundParameters.ContainsKey('StepIcon')) { $StepIcon = $branding.StepIcon } # bat's syntax theme follows the prompt theme unless set explicitly (screwcity -> Dracula, etc.). if (-not $PSBoundParameters.ContainsKey('BatTheme')) { $BatTheme = $branding.BatTheme } # fd's and fzf's color palettes likewise follow the prompt theme unless set explicitly. if (-not $PSBoundParameters.ContainsKey('FdColors')) { $FdColors = $branding.LsColors } if (-not $PSBoundParameters.ContainsKey('FzfColors')) { $FzfColors = $branding.FzfColors } # Resolve the opt-in tool set. -Enable wins over -EnableAll (the explicit list is the safer, more # conservative choice); a bare call (neither) asks before installing everything. These run before # any Invoke-Step, so the warnings land in scrollback rather than tearing a live spinner. $catalog = Get-PwshProfileToolCatalog -Token $hasEnable = $PSBoundParameters.ContainsKey('Enable') if ($hasEnable -and $EnableAll) { Write-Warning '-Enable and -EnableAll were both supplied; -EnableAll is ignored in favor of the explicit -Enable list.' } $enabled = if ($hasEnable) { @($Enable) } elseif ($EnableAll) { @($catalog) } else { if (Confirm-PwshProfileEnableAll -Catalog $catalog) { @($catalog) } else { @() } } # Soft-validate tool-specific params: a flag for a tool that isn't enabled is a no-op, so warn # (don't throw) rather than silently ignore it. Build-PwshProfileInitializeCall only emits these # for enabled tools, so a generated profile never trips this — only a hand-edited call does. $paramTool = [ordered]@{ ZoxideCommand = 'Zoxide'; BatTheme = 'Bat'; BatStyle = 'Bat'; ReplaceCat = 'Bat' ReplaceMore = 'Less'; FdColors = 'Fd'; FzfColors = 'Fzf' } foreach ($p in $paramTool.Keys) { if ($PSBoundParameters.ContainsKey($p) -and $enabled -notcontains $paramTool[$p]) { Write-Warning "-$p was supplied but $($paramTool[$p]) is not enabled; ignoring -$p." } } # Banner coupling: the banner params are moot under -NoBanner. if ($NoBanner) { foreach ($p in 'BannerText', 'BannerColor', 'BannerAlignment', 'BannerFont', 'BannerFontPath') { if ($PSBoundParameters.ContainsKey($p)) { Write-Warning "-$p was supplied with -NoBanner; ignoring it (no banner is rendered)." } } } # Belt-and-suspenders on the banner text: [ValidateNotNullOrEmpty()] guards an explicit value but # NOT the $env:COMPUTERNAME default, so a host where COMPUTERNAME is unset would otherwise reach # Write-Figlet -Text '' (a Mandatory param) and throw out of startup. Guard on non-empty here too. if (-not $NoBanner -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 '' } } elseif (-not $NoBanner) { # Banner text resolved empty (e.g. $env:COMPUTERNAME unset) so the banner is suppressed above # to avoid throwing into Write-Figlet. Warn for any explicitly-bound banner param so the silent # drop is visible, matching the -NoBanner coupling warnings. foreach ($p in 'BannerText', 'BannerColor', 'BannerAlignment', 'BannerFont', 'BannerFontPath') { if ($PSBoundParameters.ContainsKey($p)) { Write-Warning "-$p was supplied but no banner text resolved (banner suppressed); ignoring it." } } } # Core always renders. oh-my-posh and the `which` alias are always-on (not catalog tokens); the # rest are opt-in. PSReadLine runs before oh-my-posh, so PSFzf (in the WinGet section, which runs # after Core) still initializes after PSReadLine. Shell completions register here (Core): they # detect external CLIs and install nothing, so their position relative to the WinGet tools is free. Invoke-Step "Core" -Icon $StepIcon { Invoke-Step "Global Aliases" { Set-Alias -Name which -Value where.exe -Scope Global } if ($enabled -contains 'PSReadLine') { Invoke-Step "PSReadLine" { Initialize-PSReadline } } Invoke-Step "Oh-My-Posh" { Enable-OhMyPosh -Configuration $resolvedTheme } if ($enabled -contains 'TerminalIcons') { Invoke-Step "Terminal-Icons" { Import-ModuleSafe Terminal-Icons } } if ($enabled -contains 'PoshGit') { Invoke-Step "Posh-Git" { Import-ModuleSafe posh-git -Initialize { $env:POSH_GIT_ENABLED = $true } } } if ($enabled -contains '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 } } } } # WinGet renders only when at least one winget tool is enabled, so it isn't an empty section. The # token set is the catalog's WinGet group (Install -eq 'winget'), not a hardcoded list. $wingetTokens = @((Get-PwshProfileToolCatalog)['WinGet'].Token) if (@($enabled | Where-Object { $wingetTokens -contains $_ }).Count) { Invoke-Step "WinGet" -Icon $StepIcon { if ($enabled -contains 'Zoxide') { Invoke-Step "Zoxide" { Enable-Zoxide -Command $ZoxideCommand } } if ($enabled -contains 'Fzf') { Invoke-Step "fzf" { # Preview files with bat only when bat is in play (enabled). The preview runs at # fzf-use time — by then the bat step (which follows) has installed bat; bat # inherits $env:BAT_THEME so the preview colors match the prompt. $fzfPreview = if ($enabled -contains 'Bat') { 'bat --color=always --style=numbers {}' } else { '' } # PSFzf supplies the Ctrl+T/Ctrl+R bindings (fzf ships none for PowerShell); # -UseFd follows whether fd is enabled (PSFzf uses fd for traversal); -GitKeyBindings # is always requested and Enable-Fzf drops it when git isn't on PATH. -Height '100%' # makes those PSFzf widgets fill the shell instead of PSFzf's inline 40% default. # -TabExpansionChord puts PSFzf's fuzzy completion picker on Ctrl+Spacebar (a chord # that otherwise just duplicates Tab's MenuComplete), leaving Tab = MenuComplete. Enable-Fzf -Colors $FzfColors -Style 'full' -Height '100%' -PreviewCommand $fzfPreview ` -ProviderChord 'Ctrl+t' -HistoryChord 'Ctrl+r' -TabExpansionChord 'Ctrl+Spacebar' ` -UseFd:($enabled -contains 'Fd') -GitKeyBindings } } if ($enabled -contains 'Fnm') { Invoke-Step "Fast Node Manager (fnm)" { Enable-FastNodeManager } } if ($enabled -contains 'Xh') { Invoke-Step "xh" { Enable-Xh } } if ($enabled -contains 'Jq') { Invoke-Step "jq" { Enable-Jq } } if ($enabled -contains 'Bat') { Invoke-Step "bat" { Enable-Bat -Theme $BatTheme -Style $BatStyle -ReplaceCat:$ReplaceCat } } # fd follows fzf so fzf.exe is already on PATH when -IntegrateFzf is evaluated; fd wires # fzf to use fd as its source only when fzf is itself enabled and present. if ($enabled -contains 'Fd') { Invoke-Step "fd" { Enable-Fd -LsColors $FdColors -IntegrateFzf:($enabled -contains 'Fzf') } } # less is bat's pager (and PowerShell's via $env:PAGER); it has no init-time dependency # on the other tools, so its position is free. -ReplaceMore is opt-in (set by the wizard). if ($enabled -contains 'Less') { Invoke-Step "less" { Enable-Less -ReplaceMore:$ReplaceMore } } } } } |