payload/config/powershell/Microsoft.PowerShell_profile.ps1
|
# ============================================================ # furnizsh — PowerShell profile # https://github.com/Wosmos/furnizsh # # The Windows half of the setup: same tools, same aliases, same # helper commands, same Starship prompt as the zsh version. # # PSReadLine covers what zsh-autosuggestions and # zsh-syntax-highlighting do on the Unix side — it's built in. # # Installed by install.ps1 to $PROFILE. Nothing here is # machine-specific: no PATH edits, no secrets. # ============================================================ # ------------------------------------------------------------ # PSReadLine — history suggestions + syntax colours # ------------------------------------------------------------ if (Get-Module -ListAvailable -Name PSReadLine) { Import-Module PSReadLine $psrlVersion = (Get-Module PSReadLine).Version Set-PSReadLineOption -EditMode Windows Set-PSReadLineOption -HistorySearchCursorMovesToEnd Set-PSReadLineOption -BellStyle None # Ghost-text suggestions from history (the zsh-autosuggestions equivalent). # PredictionSource landed in 2.1, ListView in 2.2. if ($psrlVersion -ge [version]'2.1.0') { Set-PSReadLineOption -PredictionSource History } if ($psrlVersion -ge [version]'2.2.0') { Set-PSReadLineOption -PredictionViewStyle ListView } # Catppuccin Mocha syntax colours (the zsh-syntax-highlighting equivalent) Set-PSReadLineOption -Colors @{ Command = "#89b4fa" Parameter = "#cba6f7" Operator = "#f5c2e7" Variable = "#f9e2af" String = "#a6e3a1" Number = "#fab387" Type = "#f9e2af" Comment = "#6c7086" InlinePrediction = "#6c7086" } # Up/Down filter history by what you've already typed — # the history-substring-search equivalent. Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete } # ------------------------------------------------------------ # Terminal-Icons — file-type glyphs in listings (needs the Nerd Font) # ------------------------------------------------------------ if (Get-Module -ListAvailable -Name Terminal-Icons) { Import-Module Terminal-Icons } # ------------------------------------------------------------ # Modern CLI tool replacements # # Aliases beat functions in PowerShell's command resolution order, # so the built-in ls/cat aliases have to be removed before our # functions can claim those names. # ------------------------------------------------------------ function Remove-BuiltinAlias { param([string]$Name) if (Test-Path "Alias:$Name") { Remove-Item "Alias:$Name" -Force -ErrorAction SilentlyContinue } } if (Get-Command eza -ErrorAction SilentlyContinue) { Remove-BuiltinAlias 'ls' # --icons=auto, not a bare --icons. Since eza 0.18 the flag takes an # optional WHEN value, so a bare --icons swallows the next argument and # `ls somedir` fails with "invalid value 'somedir' for '--icons [<WHEN>]'". function ls { eza --icons=auto @args } function ll { eza -la --icons=auto @args } function lt { eza --icons=auto --tree @args } } if (Get-Command bat -ErrorAction SilentlyContinue) { Remove-BuiltinAlias 'cat' function cat { bat @args } } if (Get-Command lazygit -ErrorAction SilentlyContinue) { function lg { lazygit @args } } # ------------------------------------------------------------ # zoxide — frecency-based cd. Use `z <partial-dir-name>`. # ------------------------------------------------------------ if (Get-Command zoxide -ErrorAction SilentlyContinue) { Invoke-Expression (& { (zoxide init powershell | Out-String) }) } # ------------------------------------------------------------ # fzf — Ctrl+r history, Ctrl+t files, via PSFzf # ------------------------------------------------------------ if ((Get-Command fzf -ErrorAction SilentlyContinue) -and (Get-Module -ListAvailable -Name PSFzf)) { Import-Module PSFzf Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r' } # ------------------------------------------------------------ # Window title = current directory # ------------------------------------------------------------ function prompt { $Host.UI.RawUI.WindowTitle = (Get-Location).Path.Replace($HOME, '~') # Starship (initialized at the bottom) overwrites the rest of this. "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " } # ============================================================ # furnizsh commands — the PowerShell ports # ============================================================ $script:AGColors = @{ Orange = "`e[38;2;250;179;135m" Yellow = "`e[38;2;249;226;175m" Blue = "`e[38;2;137;180;250m" Green = "`e[38;2;166;227;161m" Gray = "`e[38;2;108;112;134m" Text = "`e[38;2;205;214;244m" Bold = "`e[1m" Reset = "`e[0m" } function cheatsheet { <# .SYNOPSIS Print the furnizsh terminal reference. -Full for the exhaustive version. #> param([switch]$Full) $c = $script:AGColors Write-Host "" Write-Host "$($c.Bold)$($c.Blue)furnizsh cheatsheet$($c.Reset) $($c.Text)(PowerShell)$($c.Reset)" Write-Host "" Write-Host "$($c.Bold)$($c.Orange)Tools$($c.Reset)" Write-Host " $($c.Yellow)zoxide$($c.Reset) z <partial-dir> frecency-based cd" Write-Host " $($c.Yellow)fzf$($c.Reset) Ctrl+r / Ctrl+t fuzzy history / files" Write-Host " $($c.Yellow)eza$($c.Reset) ls, ll, lt icons + tree view" Write-Host " $($c.Yellow)bat$($c.Reset) cat <file> syntax-highlighted cat" Write-Host " $($c.Yellow)fd$($c.Reset) fd <pattern> faster file search" Write-Host " $($c.Yellow)git-delta$($c.Reset) git diff / show side-by-side colored diffs" Write-Host " $($c.Yellow)lazygit$($c.Reset) lg full git TUI" Write-Host "" Write-Host "$($c.Bold)$($c.Orange)furnizsh commands$($c.Reset)" Write-Host " $($c.Yellow)mkcd$($c.Reset) <dir> make + enter a dir $($c.Yellow)up$($c.Reset) [n] cd up n levels" Write-Host " $($c.Yellow)serve$($c.Reset) [port] static server here $($c.Yellow)ports$($c.Reset) what's listening" Write-Host " $($c.Yellow)killport$($c.Reset) <p> free a port $($c.Yellow)fkill$($c.Reset) fzf-pick + kill a process" Write-Host " $($c.Yellow)fe$($c.Reset) [query] fzf-pick + edit a file $($c.Yellow)bak$($c.Reset) <file> timestamped backup" Write-Host " $($c.Yellow)sizeof$($c.Reset) [dir] what's eating the disk $($c.Yellow)gprune$($c.Reset) prune merged branches" Write-Host " $($c.Yellow)paths$($c.Reset) `$env:PATH, one per line $($c.Yellow)reload$($c.Reset) restart PowerShell" Write-Host " $($c.Yellow)fzdoctor$($c.Reset) health-check the setup" Write-Host "" if ($Full) { Write-Host "$($c.Bold)$($c.Orange)PSReadLine keys$($c.Reset)" Write-Host " Right / End accept the ghost-text suggestion" Write-Host " Up / Down filter history by what you've typed" Write-Host " Tab menu completion" Write-Host " Ctrl+r / Ctrl+t fuzzy history / file search (PSFzf)" Write-Host " F2 toggle inline vs list prediction view" Write-Host "" Write-Host "$($c.Bold)$($c.Orange)Windows Terminal keys$($c.Reset)" Write-Host " ctrl+shift+t new tab alt+shift+d split pane" Write-Host " ctrl+shift+w close pane alt+arrows move focus" Write-Host " ctrl+tab next tab ctrl+shift+f search" Write-Host " ctrl+alt+1..9 jump to tab N ctrl+, settings" Write-Host "" Write-Host "$($c.Gray)Ghostty is macOS/Linux only — see docs/WINDOWS.md for the WSL2 route.$($c.Reset)" Write-Host "" } Write-Host "$($c.Text)Docs: https://wosmos.github.io/furnizsh$($c.Reset)" Write-Host "" } Set-Alias chs cheatsheet function fzdoctor { <# .SYNOPSIS Health-check every part of the furnizsh setup. #> $c = $script:AGColors $failures = 0 function Test-Item { param([string]$Label, [string]$Note, [scriptblock]$Check) $pass = $false try { $pass = [bool](& $Check) } catch { $pass = $false } if ($pass) { Write-Host (" {0}OK{1} {2}" -f $c.Green, $c.Reset, $Label) } else { Write-Host (" {0}FAIL{1} {2} {3}{4}{5}" -f $c.Orange, $c.Reset, $Label, $c.Gray, $Note, $c.Reset) $script:failures++ } } $script:failures = 0 Write-Host "" Write-Host "$($c.Bold)$($c.Blue)furnizsh doctor$($c.Reset)" Write-Host "" Write-Host "$($c.Bold)$($c.Orange)Tools$($c.Reset)" foreach ($tool in @('starship', 'zoxide', 'fzf', 'eza', 'bat', 'fd', 'delta', 'lazygit', 'git')) { Test-Item $tool "not on PATH - see docs/WINDOWS.md" { Get-Command $tool -ErrorAction SilentlyContinue } } Write-Host "" Write-Host "$($c.Bold)$($c.Orange)Modules$($c.Reset)" Test-Item "PSReadLine >= 2.2" "Install-Module PSReadLine -Force" { (Get-Module -ListAvailable PSReadLine | Sort-Object Version -Descending | Select-Object -First 1).Version -ge [version]'2.2.0' } Test-Item "Terminal-Icons" "Install-Module Terminal-Icons" { Get-Module -ListAvailable Terminal-Icons } Test-Item "PSFzf" "Install-Module PSFzf" { Get-Module -ListAvailable PSFzf } Write-Host "" Write-Host "$($c.Bold)$($c.Orange)Config$($c.Reset)" Test-Item "profile installed" "rerun install.ps1" { Test-Path $PROFILE } Test-Item "starship.toml" "missing ~/.config/starship.toml" { Test-Path "$HOME/.config/starship.toml" } Test-Item "delta as git pager" "git config --global core.pager delta" { (git config --global core.pager 2>$null) -eq 'delta' } Write-Host "" Write-Host "$($c.Gray)If these are boxes, the Nerd Font isn't active in your terminal:$($c.Reset)" Write-Host " " Write-Host "" if ($script:failures -eq 0) { Write-Host "$($c.Bold)$($c.Green)All good.$($c.Reset) Run $($c.Yellow)cheatsheet$($c.Reset) for the reference." } else { Write-Host "$($c.Bold)$($c.Orange)$($script:failures) check(s) failed.$($c.Reset) See https://wosmos.github.io/furnizsh" } Write-Host "" } function mkcd { <# .SYNOPSIS Create a directory (including parents) and cd into it. #> param([Parameter(Mandatory)][string]$Path) New-Item -ItemType Directory -Force -Path $Path | Out-Null Set-Location $Path } function up { <# .SYNOPSIS cd up N levels (default 1). #> param([int]$Levels = 1) if ($Levels -lt 1) { $Levels = 1 } Set-Location (('..\' * $Levels).TrimEnd('\')) } function serve { <# .SYNOPSIS Static HTTP server in the current directory. #> param([int]$Port = 8000) $py = (Get-Command python -ErrorAction SilentlyContinue) ?? (Get-Command python3 -ErrorAction SilentlyContinue) if (-not $py) { Write-Error "serve: python not found"; return } $lan = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notlike '127.*' -and $_.IPAddress -notlike '169.254.*' } | Select-Object -First 1).IPAddress $c = $script:AGColors Write-Host "$($c.Gray)Serving $((Get-Location).Path)$($c.Reset)" Write-Host " local $($c.Green)http://localhost:$Port$($c.Reset)" if ($lan) { Write-Host " network $($c.Green)http://${lan}:$Port$($c.Reset)" } Write-Host "" & $py.Source -m http.server $Port } function ports { <# .SYNOPSIS Everything currently listening, with PID and process name. #> Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, OwningProcess, @{ Name = 'Process'; Expression = { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName }} | Sort-Object LocalPort | Format-Table -AutoSize } function killport { <# .SYNOPSIS Kill whatever is holding a TCP port. #> param([Parameter(Mandatory)][int]$Port) $c = $script:AGColors $owners = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique if (-not $owners) { Write-Host "$($c.Gray)Nothing listening on port $Port.$($c.Reset)" return } foreach ($processId in $owners) { $proc = Get-Process -Id $processId -ErrorAction SilentlyContinue try { Stop-Process -Id $processId -Force -ErrorAction Stop Write-Host "$($c.Orange)Killed$($c.Reset) $($proc.ProcessName) (pid $processId) on port $Port" } catch { Write-Host "$($c.Gray)Could not kill pid $processId - try an elevated shell.$($c.Reset)" } } } function fkill { <# .SYNOPSIS fzf-pick one or more processes and kill them. #> if (-not (Get-Command fzf -ErrorAction SilentlyContinue)) { Write-Error "fkill: fzf not installed"; return } $c = $script:AGColors $picked = Get-Process | Sort-Object -Property WS -Descending | ForEach-Object { "{0,-8} {1,-30} {2,10:N0} KB" -f $_.Id, $_.ProcessName, ($_.WS / 1KB) } | fzf --multi --height 60% --reverse --header 'tab = select multiple, enter = kill' foreach ($line in $picked) { $procId = ($line -split '\s+')[0] try { Stop-Process -Id $procId -Force -ErrorAction Stop Write-Host "$($c.Orange)killed$($c.Reset) $procId" } catch { Write-Host "$($c.Gray)could not kill $procId$($c.Reset)" } } } function fe { <# .SYNOPSIS fzf-pick a file with a preview and open it in $env:EDITOR. #> param([string]$Query = '') if (-not (Get-Command fzf -ErrorAction SilentlyContinue)) { Write-Error "fe: fzf not installed"; return } $preview = if (Get-Command bat -ErrorAction SilentlyContinue) { 'bat --style=numbers --color=always {}' } else { 'type {}' } $files = if (Get-Command fd -ErrorAction SilentlyContinue) { fd --type f --hidden --exclude .git } else { Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch '\\\.git\\' } | ForEach-Object { $_.FullName } } $file = $files | fzf --query $Query --height 70% --reverse ` --preview $preview --preview-window 'right:60%' if ($file) { $editor = if ($env:EDITOR) { $env:EDITOR } else { 'notepad' } & $editor $file } } function bak { <# .SYNOPSIS Timestamped backup copy alongside the original. #> param([Parameter(Mandatory)][string]$Path) if (-not (Test-Path $Path)) { Write-Error "bak: $Path not found"; return } $dest = "$($Path.TrimEnd('\'))." + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.bak' Copy-Item -Path $Path -Destination $dest -Recurse -Force Write-Host "$($script:AGColors.Gray)->$($script:AGColors.Reset) $dest" } function sizeof { <# .SYNOPSIS Biggest items in a directory, largest first. #> param([string]$Path = '.') Get-ChildItem -Path $Path -Force -ErrorAction SilentlyContinue | ForEach-Object { $bytes = if ($_.PSIsContainer) { (Get-ChildItem $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum } else { $_.Length } [PSCustomObject]@{ Size = switch ($bytes) { { $_ -ge 1GB } { '{0:N1} GB' -f ($_ / 1GB); break } { $_ -ge 1MB } { '{0:N1} MB' -f ($_ / 1MB); break } { $_ -ge 1KB } { '{0:N1} KB' -f ($_ / 1KB); break } default { "$_ B" } } Bytes = [long]$bytes Name = $_.Name } } | Sort-Object Bytes -Descending | Select-Object -First 20 Size, Name | Format-Table -AutoSize } function gprune { <# .SYNOPSIS Delete local branches already merged into the default branch. #> $c = $script:AGColors git rev-parse --is-inside-work-tree 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "gprune: not inside a git repository"; return } $default = (git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>$null) -replace '^origin/', '' if (-not $default) { foreach ($candidate in @('main', 'master', 'trunk')) { git show-ref --verify --quiet "refs/heads/$candidate" 2>$null if ($LASTEXITCODE -eq 0) { $default = $candidate; break } } } if (-not $default) { Write-Error "gprune: could not determine the default branch"; return } $protected = @($default, 'main', 'master', 'develop', 'trunk') $stale = git branch --merged $default --format='%(refname:short)' | Where-Object { $_ -and $protected -notcontains $_ } if (-not $stale) { Write-Host "$($c.Gray)Nothing to prune - no branches merged into $default.$($c.Reset)" return } Write-Host "$($c.Gray)Merged into ${default}, safe to delete:$($c.Reset)" $stale | ForEach-Object { Write-Host " $($c.Yellow)$_$($c.Reset)" } $reply = Read-Host "`nDelete $($stale.Count) branch(es)? [y/N]" if ($reply -notmatch '^[yY]') { Write-Host "Aborted."; return } $stale | ForEach-Object { git branch -d $_ } } function paths { <# .SYNOPSIS $env:PATH one entry per line; flags duplicates and missing dirs. #> $c = $script:AGColors $seen = @{} foreach ($dir in ($env:PATH -split ';' | Where-Object { $_ })) { if ($seen.ContainsKey($dir)) { Write-Host ("{0}{1,-50} dup{2}" -f $c.Gray, $dir, $c.Reset) } elseif (-not (Test-Path $dir)) { Write-Host ("{0}{1,-50} missing{2}" -f $c.Orange, $dir, $c.Reset) } else { Write-Host ("{0}{1}{2}" -f $c.Text, $dir, $c.Reset) } $seen[$dir] = $true } } function reload { <# .SYNOPSIS Restart PowerShell, picking up profile changes. #> Write-Host "$($script:AGColors.Gray)Reloading PowerShell...$($script:AGColors.Reset)" $exe = (Get-Process -Id $PID).Path Start-Process -FilePath $exe -ArgumentList '-NoLogo' exit } # ============================================================ # theme — switch the whole look in one command # # Ghostty has no Windows build, so on this side "theme" means # Starship + lazygit + the Windows Terminal colour scheme. # ============================================================ $script:AGHome = Join-Path $HOME '.config\furnizsh' function Get-FurnizshVersion { <# .SYNOPSIS The installed version, as stamped by install.ps1. #> $f = Join-Path $script:AGHome 'VERSION' if (Test-Path $f) { (Get-Content $f -Raw).Trim() } else { 'unknown' } } function Get-FurnizshTheme { $marker = Join-Path $script:AGHome 'current-theme' if (Test-Path $marker) { (Get-Content $marker -Raw).Trim() } else { 'neon' } } function theme { <# .SYNOPSIS List furnizsh themes, or switch to one. .EXAMPLE theme # list, with the active one marked .EXAMPLE theme gruvbox # switch starship + lazygit + Windows Terminal #> param([string]$Name) $c = $script:AGColors $themesDir = Join-Path $script:AGHome 'themes' if (-not (Test-Path $themesDir)) { Write-Error "theme: no themes installed at $themesDir - rerun .\install.ps1" return } $current = Get-FurnizshTheme # The .theme files are plain KEY="value" lines, shared with the Unix side. function Read-ThemeFile { param([string]$Path) $t = @{} foreach ($line in Get-Content $Path) { if ($line -match '^\s*([A-Z_]+)="(.*)"\s*$') { $t[$Matches[1]] = $Matches[2] } } return $t } if (-not $Name) { Write-Host "" Write-Host "$($c.Bold)$($c.Blue)Themes$($c.Reset) $($c.Gray)(theme <name> to switch)$($c.Reset)" Write-Host "" foreach ($f in Get-ChildItem $themesDir -Filter *.theme | Sort-Object Name) { $t = Read-ThemeFile $f.FullName $id = $f.BaseName if ($id -eq $current) { Write-Host (" {0}*{1} {2}{3,-12}{4} {5,-20} {6}{7}{8}" -f ` $c.Green, $c.Reset, $c.Bold, $id, $c.Reset, $t.THEME_LABEL, $c.Gray, $t.THEME_BLURB, $c.Reset) } else { Write-Host (" {0}-{1} {2}{3,-12}{4} {5,-20} {6}{7}{8}" -f ` $c.Gray, $c.Reset, $c.Yellow, $id, $c.Reset, $t.THEME_LABEL, $c.Gray, $t.THEME_BLURB, $c.Reset) } } Write-Host "" return } $themeFile = Join-Path $themesDir "$Name.theme" if (-not (Test-Path $themeFile)) { Write-Error "theme: unknown theme '$Name'. Run ``theme`` to list them." return } $t = Read-ThemeFile $themeFile Write-Host "" Write-Host "$($c.Gray)Switching to $($c.Bold)$($t.THEME_LABEL)$($c.Reset)" # --- Starship. --force is required: it refuses to overwrite otherwise. --- if (Get-Command starship -ErrorAction SilentlyContinue) { $out = Join-Path $HOME '.config\starship.toml' New-Item -ItemType Directory -Force -Path (Split-Path $out -Parent) | Out-Null starship preset $t.STARSHIP_PRESET --force -o $out 2>$null if ($LASTEXITCODE -eq 0) { Write-Host " $($c.Green)OK$($c.Reset) starship $($c.Gray)$($t.STARSHIP_PRESET)$($c.Reset)" } else { Write-Host " $($c.Orange)!$($c.Reset) starship has no preset '$($t.STARSHIP_PRESET)' - prompt unchanged" } } # --- lazygit --- $lgSrc = Join-Path $themesDir "lazygit\$($t.LAZYGIT_PALETTE).yml" if (Test-Path $lgSrc) { $lgDest = Join-Path $env:APPDATA 'lazygit\config.yml' New-Item -ItemType Directory -Force -Path (Split-Path $lgDest -Parent) | Out-Null Copy-Item $lgSrc $lgDest -Force Write-Host " $($c.Green)OK$($c.Reset) lazygit $($c.Gray)$($t.LAZYGIT_PALETTE)$($c.Reset)" } New-Item -ItemType Directory -Force -Path $script:AGHome | Out-Null Set-Content -Path (Join-Path $script:AGHome 'current-theme') -Value $Name -NoNewline Write-Host "" Write-Host "$($c.Gray)Set the matching Windows Terminal scheme in ctrl+, if you want the" Write-Host "terminal colours to follow too. Run $($c.Yellow)reload$($c.Reset)$($c.Gray) for the new prompt.$($c.Reset)" Write-Host "" } function fzupdate { <# .SYNOPSIS Update furnizsh, whichever way it was installed. #> $c = $script:AGColors $here = Get-FurnizshVersion $latest = $null try { $latest = (Invoke-RestMethod -TimeoutSec 5 ` 'https://api.github.com/repos/Wosmos/furnizsh/releases/latest').tag_name -replace '^v', '' } catch { # Offline or rate-limited: fall through and update without a version # comparison rather than refusing to run. $latest = $null } if ($latest -and $latest -eq $here) { Write-Host "$($c.Gray)furnizsh $here is already the latest.$($c.Reset)" return } if ($latest) { Write-Host "$($c.Gray)$here -> $latest$($c.Reset)`n" } # install.ps1 records how furnizsh arrived. Without it we can only guess, # and guessing wrong means telling someone to git pull a directory that # was never a checkout. $marker = Join-Path $script:AGHome 'install-source' $method = ''; $source = '' if (Test-Path $marker) { $lines = Get-Content $marker $method = $lines[0] if ($lines.Count -gt 1) { $source = $lines[1] } } switch ($method) { 'psgallery' { Write-Host "$($c.Gray)Installed from the PowerShell Gallery.$($c.Reset)" Update-Module furnizsh -Force if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { Write-Error 'fzupdate: update failed'; return } } 'git' { Write-Host "$($c.Gray)Installed from a checkout at $source.$($c.Reset)" if (-not (Test-Path (Join-Path $source '.git'))) { Write-Error "fzupdate: $source is no longer a checkout - reinstall to fix"; return } git -C $source pull --ff-only if ($LASTEXITCODE -ne 0) { Write-Error 'fzupdate: pull failed'; return } & (Join-Path $source 'install.ps1') -Yes if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { Write-Error 'fzupdate: install failed'; return } } default { Write-Host "$($c.Orange)Can't tell how furnizsh was installed.$($c.Reset) Update it the way you got it:" Write-Host " $($c.Yellow)Update-Module furnizsh$($c.Reset)" Write-Host " $($c.Yellow)git -C <checkout> pull; .\install.ps1 -Yes$($c.Reset)" return } } Write-Host "`n$($c.Green)OK$($c.Reset) updated - run $($c.Yellow)reload$($c.Reset)" } # >>> furnizsh extras >>> # ============================================================ # Extras — the network-dependent helpers, matching extras.zsh # install.ps1 -NoExtras strips everything between these markers. # ============================================================ function weather { <# .SYNOPSIS Forecast in the terminal (wttr.in). #> param([Parameter(ValueFromRemainingArguments)][string[]]$Location) $where = ($Location -join '+') try { (Invoke-WebRequest -Uri "https://wttr.in/${where}?F" -UserAgent 'curl' -TimeoutSec 10).Content } catch { Write-Error "weather: could not reach wttr.in" } } function cheat { <# .SYNOPSIS Practical examples for any command (cheat.sh). #> param([Parameter(Mandatory)][string]$Command, [Parameter(ValueFromRemainingArguments)][string[]]$Search) $topic = $Command if ($Search) { $topic += '~' + ($Search -join '+') } try { (Invoke-WebRequest -Uri "https://cheat.sh/$topic" -UserAgent 'curl' -TimeoutSec 10).Content } catch { Write-Error "cheat: could not reach cheat.sh" } } function qr { <# .SYNOPSIS Render a QR code in the terminal (qrenco.de). #> param([Parameter(Mandatory, ValueFromRemainingArguments)][string[]]$Text) try { (Invoke-WebRequest -Uri 'https://qrenco.de/' -Method Post -UserAgent 'curl' ` -Body @{ text = ($Text -join ' ') } -TimeoutSec 10).Content } catch { Write-Error "qr: could not reach qrenco.de" } } function gitignore { <# .SYNOPSIS Fetch a .gitignore. -Write saves it instead of printing. #> param([Parameter(Mandatory)][string]$Languages, [switch]$Write) try { $body = (Invoke-WebRequest -TimeoutSec 10 ` -Uri "https://www.toptal.com/developers/gitignore/api/$($Languages -replace '\s','')").Content } catch { Write-Error "gitignore: could not reach the generator"; return } if ($Write) { if (Test-Path .gitignore) { Add-Content .gitignore $body } else { Set-Content .gitignore $body } Write-Host "$($script:AGColors.Green)OK$($script:AGColors.Reset) wrote .gitignore ($Languages)" } else { $body } } function note { <# .SYNOPSIS Timestamped scratch notes, stored locally. #> param([Parameter(ValueFromRemainingArguments)][string[]]$Text, [switch]$List) $file = if ($env:FURNIZSH_NOTES) { $env:FURNIZSH_NOTES } else { Join-Path $HOME '.furnizsh-notes.md' } if (-not (Test-Path $file)) { Set-Content $file "# notes`n" } if ($List) { Get-Content $file -Tail 20 } elseif (-not $Text) { & $(if ($env:EDITOR) { $env:EDITOR } else { 'notepad' }) $file } else { Add-Content $file ("- {0} {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm'), ($Text -join ' ')) Write-Host "$($script:AGColors.Green)OK$($script:AGColors.Reset) noted" } } function timer { <# .SYNOPSIS Countdown, then a notification. #> param([Parameter(Mandatory)][double]$Minutes, [Parameter(ValueFromRemainingArguments)][string[]]$Label) $c = $script:AGColors $name = if ($Label) { $Label -join ' ' } else { 'timer' } $end = (Get-Date).AddMinutes($Minutes) Write-Host "$($c.Bold)$name$($c.Reset) - $Minutes min. ctrl+c to cancel." while ((Get-Date) -lt $end) { $left = $end - (Get-Date) Write-Host -NoNewline ("`r {0}{1:mm\:ss} remaining{2} " -f $c.Gray, $left, $c.Reset) Start-Sleep -Seconds 1 } Write-Host ("`r{0}OK {1} done{2}{3}" -f $c.Green, $name, $c.Reset, (' ' * 20)) [console]::beep(880, 400) } function sysinfo { <# .SYNOPSIS A compact summary of the machine. #> $c = $script:AGColors $os = Get-CimInstance Win32_OperatingSystem $cs = Get-CimInstance Win32_ComputerSystem $cpu = (Get-CimInstance Win32_Processor | Select-Object -First 1) $disk = Get-PSDrive C -ErrorAction SilentlyContinue function Row { param($k, $v) Write-Host (" {0}{1,-12}{2} {3}" -f $c.Yellow, $k, $c.Reset, $v) } Write-Host "" Write-Host "$($c.Bold)$($c.Blue)sysinfo$($c.Reset)" Write-Host "" Row 'os' "$($os.Caption) $($os.Version)" Row 'host' $env:COMPUTERNAME Row 'cpu' $cpu.Name.Trim() Row 'cores' "$($cpu.NumberOfLogicalProcessors) logical" Row 'memory' ("{0:N0} GB" -f ($cs.TotalPhysicalMemory / 1GB)) Row 'uptime' ((Get-Date) - $os.LastBootUpTime).ToString('d\d\ hh\h\ mm\m') if ($disk) { Row 'disk' ("{0:N0} GB free of {1:N0} GB" -f ($disk.Free / 1GB), (($disk.Free + $disk.Used) / 1GB)) } Row 'shell' "PowerShell $($PSVersionTable.PSVersion)" Row 'theme' (Get-FurnizshTheme) Write-Host "" } function dockerclean { <# .SYNOPSIS Reclaim disk from docker. Named volumes are never touched. #> $c = $script:AGColors if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { Write-Error "dockerclean: docker not installed"; return } docker info 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "dockerclean: docker isn't running"; return } Write-Host "`n$($c.Bold)Current usage$($c.Reset)" docker system df Write-Host "`n$($c.Gray)Removes stopped containers, unused networks, dangling images and build cache." Write-Host "Named volumes are NOT touched - your database data is safe.$($c.Reset)" if ((Read-Host "`nProceed? [y/N]") -notmatch '^[yY]') { Write-Host "Aborted."; return } docker system prune -f Write-Host "`n$($c.Green)OK$($c.Reset) done`n" } # <<< furnizsh extras <<< # ------------------------------------------------------------ # Starship prompt — must stay last so nothing below overrides # the prompt function it defines. # ------------------------------------------------------------ if (Get-Command starship -ErrorAction SilentlyContinue) { $ENV:STARSHIP_CONFIG = "$HOME\.config\starship.toml" Invoke-Expression (&starship init powershell) } |