Add Windows support: linker, oh-my-posh installer, README
- link-dotfiles.ps1: stow-equivalent for Windows using junctions/symlinks (no admin required), idempotent with .bak backups and -DryRun - lk-installers/install-ohmyposh.ps1: winget-based install with fallback - README.md: document Linux/macOS (stow) and Windows flows - .gitignore: resolve leftover merge conflict markers, ignore *.bak - ohmyposh/lkraven.toml: oh-my-posh v3 auto-migrated this on load (same theme, upgraded schema) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-4
@@ -2,9 +2,6 @@
|
|||||||
.config/btop
|
.config/btop
|
||||||
.config/neofetch
|
.config/neofetch
|
||||||
/.aider*
|
/.aider*
|
||||||
<<<<<<< HEAD
|
|
||||||
./home_config/nvim/lazy-lock.json
|
|
||||||
home_config/nvim/lazy-lock.json
|
home_config/nvim/lazy-lock.json
|
||||||
=======
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
>>>>>>> 00717500056db846954b09bcfac4ebb3f1aa84e6
|
*.bak
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# dotfiles
|
||||||
|
|
||||||
|
Personal dotfiles. Configs are organized by where they get linked:
|
||||||
|
|
||||||
|
| Folder | Links into | Examples |
|
||||||
|
| -------------- | ------------ | --------------------------------- |
|
||||||
|
| `home_root` | `~` | `.zshrc`, `.wezterm.lua`, `.nanorc` |
|
||||||
|
| `home_config` | `~/.config` | `nvim`, `ohmyposh`, `zellij`, `ghostty` |
|
||||||
|
| `lk-installers`| n/a | helper scripts to install tooling |
|
||||||
|
|
||||||
|
## Linux / macOS (GNU stow)
|
||||||
|
|
||||||
|
Requires [`stow`](https://www.gnu.org/software/stow/).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./link-dotfiles
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs `stow home_root` and `stow --target=$HOME/.config home_config`.
|
||||||
|
|
||||||
|
## Windows (PowerShell)
|
||||||
|
|
||||||
|
`stow` doesn't run natively on Windows, so use the PowerShell equivalent. It
|
||||||
|
mirrors stow by creating **junctions** for directories and **symlinks/hardlinks**
|
||||||
|
for files — none of which require administrator rights:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pwsh -File .\link-dotfiles.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
- Re-running is safe: already-correct links are skipped.
|
||||||
|
- Any real file/dir that would be overwritten is moved aside to `<name>.bak`.
|
||||||
|
- Preview without changing anything: `pwsh -File .\link-dotfiles.ps1 -DryRun`
|
||||||
|
|
||||||
|
The repo stays the source of truth — edit files under `home_root` /
|
||||||
|
`home_config` and the linked locations update live.
|
||||||
|
|
||||||
|
## Installers
|
||||||
|
|
||||||
|
| Script | Platform | Purpose |
|
||||||
|
| ------------------------------- | -------- | ------------------ |
|
||||||
|
| `lk-installers/install-ohmyposh` | bash | oh-my-posh (Linux/macOS) |
|
||||||
|
| `lk-installers/install-ohmyposh.ps1` | pwsh | oh-my-posh (Windows, via winget) |
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Windows: install oh-my-posh, then link
|
||||||
|
pwsh -File .\lk-installers\install-ohmyposh.ps1
|
||||||
|
pwsh -File .\link-dotfiles.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### oh-my-posh
|
||||||
|
|
||||||
|
The prompt theme lives at `home_config/ohmyposh/lkraven.toml` and is loaded from
|
||||||
|
your shell profile, e.g. in PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
oh-my-posh init pwsh --config '~/.config/ohmyposh/lkraven.toml' | Invoke-Expression
|
||||||
|
```
|
||||||
|
|
||||||
|
It expects a Nerd Font (e.g. **VictorMono Nerd Font Mono**) to be installed and
|
||||||
|
selected in your terminal for the glyphs to render correctly.
|
||||||
@@ -1,126 +1,152 @@
|
|||||||
#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json
|
|
||||||
console_title_template = '{{if .Root}}(Admin){{end}} {{.PWD}}'
|
console_title_template = '{{if .Root}}(Admin){{end}} {{.PWD}}'
|
||||||
version = 2
|
version = 3
|
||||||
|
|
||||||
[palette]
|
[palette]
|
||||||
black = '#222531'
|
black = '#222531'
|
||||||
bright_black = '#373b46'
|
|
||||||
blue = '#6388D8'
|
blue = '#6388D8'
|
||||||
|
bright_70 = '#9daeb6'
|
||||||
|
bright_80 = '#b3cbcf'
|
||||||
|
bright_90 = '#cce7ec'
|
||||||
|
bright_black = '#373b46'
|
||||||
bright_blue = '#a4c4ff'
|
bright_blue = '#a4c4ff'
|
||||||
cyan = '#00b1a8'
|
|
||||||
bright_cyan = '#42dcd1'
|
bright_cyan = '#42dcd1'
|
||||||
green = '#16B866'
|
|
||||||
bright_green = '#51e08a'
|
bright_green = '#51e08a'
|
||||||
magenta = '#9d78ff'
|
|
||||||
bright_magenta = '#d8adff'
|
bright_magenta = '#d8adff'
|
||||||
red = '#ff491a'
|
|
||||||
bright_red = '#ff854f'
|
bright_red = '#ff854f'
|
||||||
white = '#a9bcc3'
|
|
||||||
bright_white = '#cce7ec'
|
bright_white = '#cce7ec'
|
||||||
yellow = '#e1c631'
|
|
||||||
bright_yellow = '#FFE14E'
|
bright_yellow = '#FFE14E'
|
||||||
|
cyan = '#00b1a8'
|
||||||
dark_30 = '#414751'
|
dark_30 = '#414751'
|
||||||
dark_40 = '#565f69'
|
dark_40 = '#565f69'
|
||||||
dark_50 = '#6e7882'
|
dark_50 = '#6e7882'
|
||||||
dark_60 = '#86929d'
|
dark_60 = '#86929d'
|
||||||
bright_70 = '#9daeb6'
|
green = '#16B866'
|
||||||
bright_80 = '#b3cbcf'
|
magenta = '#9d78ff'
|
||||||
bright_90 = '#cce7ec'
|
red = '#ff491a'
|
||||||
|
white = '#a9bcc3'
|
||||||
|
yellow = '#e1c631'
|
||||||
|
|
||||||
|
[secondary_prompt]
|
||||||
|
template = '❯❯ '
|
||||||
|
foreground = 'p:bright_green'
|
||||||
|
background = 'transparent'
|
||||||
|
|
||||||
|
[transient_prompt]
|
||||||
|
template = '❯ '
|
||||||
|
background = 'transparent'
|
||||||
|
foreground_templates = ['{{if gt .Code 0}}p:red{{end}}', '{{if eq .Code 0}}p:bright_green{{end}}']
|
||||||
|
|
||||||
[[blocks]]
|
[[blocks]]
|
||||||
alignment = 'left'
|
|
||||||
type = 'prompt'
|
type = 'prompt'
|
||||||
|
alignment = 'left'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
|
template = '{{.Icon}} '
|
||||||
|
foreground = 'p:cyan'
|
||||||
type = 'os'
|
type = 'os'
|
||||||
style = 'plain'
|
style = 'plain'
|
||||||
foreground = 'p:cyan'
|
|
||||||
template = '{{.Icon}} '
|
|
||||||
|
|
||||||
[[blocks.segments]]
|
|
||||||
foreground = 'p:dark_50'
|
|
||||||
style = 'plain'
|
|
||||||
template = '{{ .Path }} {{ if not .Writable}}<p:bright_red></> {{end}}'
|
|
||||||
type = 'path'
|
|
||||||
|
|
||||||
[blocks.segments.properties]
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
|
|
||||||
|
[[blocks.segments]]
|
||||||
|
template = '{{ .Path }} {{ if not .Writable}}<p:bright_red></> {{end}}'
|
||||||
|
foreground = 'p:dark_50'
|
||||||
|
type = 'path'
|
||||||
|
style = 'plain'
|
||||||
|
|
||||||
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
style = 'agnoster'
|
style = 'agnoster'
|
||||||
|
|
||||||
[[blocks]]
|
[[blocks]]
|
||||||
alignment = 'left'
|
|
||||||
type = 'prompt'
|
type = 'prompt'
|
||||||
|
alignment = 'left'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
type = 'python'
|
leading_diamond = ''
|
||||||
|
trailing_diamond = ' '
|
||||||
|
template = ' {{ .Venv }} '
|
||||||
foreground = 'p:blue'
|
foreground = 'p:blue'
|
||||||
background = "p:bright_black"
|
background = 'p:bright_black'
|
||||||
leading_diamond = ""
|
type = 'python'
|
||||||
style = "diamond"
|
style = 'diamond'
|
||||||
trailing_diamond = " "
|
|
||||||
template = " {{ .Venv }} "
|
|
||||||
|
|
||||||
[blocks.segments.properties]
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
display_default = false
|
display_default = false
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
|
leading_diamond = ''
|
||||||
|
trailing_diamond = ''
|
||||||
|
template = ' {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }}<p:yellow> </>{{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} {{ .StashCount }}{{ end }} '
|
||||||
foreground = 'p:bright_cyan'
|
foreground = 'p:bright_cyan'
|
||||||
background = "p:bright_black"
|
background = 'p:bright_black'
|
||||||
leading_diamond = ""
|
|
||||||
style = "diamond"
|
|
||||||
trailing_diamond = ""
|
|
||||||
template = " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }}<p:yellow> </>{{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} {{ .StashCount }}{{ end }} "
|
|
||||||
type = 'git'
|
type = 'git'
|
||||||
|
style = 'diamond'
|
||||||
|
|
||||||
[blocks.segments.properties]
|
[blocks.segments.properties]
|
||||||
branch_icon = ' '
|
branch_icon = ' '
|
||||||
|
cache_duration = 'none'
|
||||||
commit_icon = ' '
|
commit_icon = ' '
|
||||||
|
fetch_stash_count = true
|
||||||
|
fetch_status = true
|
||||||
|
fetch_upstream_icon = true
|
||||||
merge_icon = ' '
|
merge_icon = ' '
|
||||||
no_commits_icon = ' '
|
no_commits_icon = ' '
|
||||||
rebase_icon = ' '
|
rebase_icon = ' '
|
||||||
revert_icon = ' '
|
revert_icon = ' '
|
||||||
tag_icon = ' '
|
tag_icon = ' '
|
||||||
fetch_stash_count = true
|
|
||||||
fetch_status = true
|
|
||||||
fetch_upstream_icon = true
|
|
||||||
|
|
||||||
[[blocks]]
|
[[blocks]]
|
||||||
type = "prompt"
|
type = 'prompt'
|
||||||
alignment = "right"
|
alignment = 'right'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
foreground = 'p:green'
|
|
||||||
style = 'plain'
|
|
||||||
template = '{{ .FormattedMs }}'
|
template = '{{ .FormattedMs }}'
|
||||||
|
foreground = 'p:green'
|
||||||
type = 'executiontime'
|
type = 'executiontime'
|
||||||
|
style = 'plain'
|
||||||
|
|
||||||
[blocks.segments.properties]
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
style = 'austin'
|
style = 'austin'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
type = "time"
|
foreground = 'p:dark_30'
|
||||||
style = "plain"
|
type = 'time'
|
||||||
foreground = "p:dark_30"
|
style = 'plain'
|
||||||
|
|
||||||
[blocks.segments.properties]
|
[blocks.segments.properties]
|
||||||
time_format = "3:04:05pm"
|
cache_duration = 'none'
|
||||||
|
time_format = '3:04:05pm'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
type = "session"
|
template = '{{.UserName}}'
|
||||||
style = "plain"
|
foreground = 'p:dark_50'
|
||||||
foreground = "p:dark_50"
|
type = 'session'
|
||||||
template = "{{.UserName}}"
|
style = 'plain'
|
||||||
|
|
||||||
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
type = "session"
|
template = ' {{ if .SSHSession }} {{ else }}@{{ end }} '
|
||||||
style = "plain"
|
foreground = 'p:cyan'
|
||||||
foreground = "p:cyan"
|
type = 'session'
|
||||||
template = " {{ if .SSHSession }} {{ else }}@{{ end }} "
|
style = 'plain'
|
||||||
|
|
||||||
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
type = "session"
|
template = '{{.HostName}}'
|
||||||
style = "plain"
|
foreground = 'p:blue'
|
||||||
foreground = "p:blue"
|
type = 'session'
|
||||||
template = "{{.HostName}}"
|
style = 'plain'
|
||||||
|
|
||||||
|
[blocks.segments.properties]
|
||||||
|
cache_duration = 'none'
|
||||||
|
|
||||||
[[blocks]]
|
[[blocks]]
|
||||||
type = 'prompt'
|
type = 'prompt'
|
||||||
@@ -128,24 +154,11 @@ version = 2
|
|||||||
newline = true
|
newline = true
|
||||||
|
|
||||||
[[blocks.segments]]
|
[[blocks.segments]]
|
||||||
|
template = '❯ '
|
||||||
|
background = 'transparent'
|
||||||
type = 'text'
|
type = 'text'
|
||||||
style = 'plain'
|
style = 'plain'
|
||||||
foreground_templates = [
|
foreground_templates = ['{{if gt .Code 0}}p:red{{end}}', '{{if eq .Code 0}}p:bright_green{{end}}']
|
||||||
"{{if gt .Code 0}}p:red{{end}}",
|
|
||||||
"{{if eq .Code 0}}p:bright_green{{end}}",
|
|
||||||
]
|
|
||||||
background = 'transparent'
|
|
||||||
template = '❯ '
|
|
||||||
|
|
||||||
[transient_prompt]
|
[blocks.segments.properties]
|
||||||
foreground_templates = [
|
cache_duration = 'none'
|
||||||
"{{if gt .Code 0}}p:red{{end}}",
|
|
||||||
"{{if eq .Code 0}}p:bright_green{{end}}",
|
|
||||||
]
|
|
||||||
background = 'transparent'
|
|
||||||
template = '❯ '
|
|
||||||
|
|
||||||
[secondary_prompt]
|
|
||||||
foreground = 'p:bright_green'
|
|
||||||
background = 'transparent'
|
|
||||||
template = '❯❯ '
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Windows equivalent of ./link-dotfiles (the GNU stow script).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
stow can't run natively on Windows, so this mirrors what it does:
|
||||||
|
home_root/* -> ~\<name> (e.g. ~\.zshrc)
|
||||||
|
home_config/* -> ~\.config\<name> (e.g. ~\.config\ohmyposh)
|
||||||
|
|
||||||
|
Directories are linked with junctions and files with symlinks/hardlinks,
|
||||||
|
none of which need administrator rights (junctions and hardlinks never do;
|
||||||
|
symlinks are attempted first in case Developer Mode is on, then we fall
|
||||||
|
back). Re-running is safe: correct links are skipped, and any real file or
|
||||||
|
directory that would be clobbered is moved aside to <name>.bak first.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
pwsh -File .\link-dotfiles.ps1
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
# Print what would happen without changing anything.
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$repo = $PSScriptRoot
|
||||||
|
$home_ = $HOME
|
||||||
|
|
||||||
|
# stow metadata files we must never link into the home tree.
|
||||||
|
$skip = @('.stow-local-ignore', '.stow-global-ignore')
|
||||||
|
|
||||||
|
function Get-LinkTarget {
|
||||||
|
param([string]$Path)
|
||||||
|
$item = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
|
||||||
|
if ($item -and $item.LinkType) { return $item.Target }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Link-Item {
|
||||||
|
param(
|
||||||
|
[string]$Source, # absolute path inside the repo
|
||||||
|
[string]$Dest # absolute path under the home tree
|
||||||
|
)
|
||||||
|
|
||||||
|
$isDir = (Get-Item -LiteralPath $Source -Force).PSIsContainer
|
||||||
|
|
||||||
|
# Already linked to the right place? Nothing to do.
|
||||||
|
$existingTarget = Get-LinkTarget $Dest
|
||||||
|
if ($existingTarget -and ($existingTarget -ieq $Source)) {
|
||||||
|
Write-Host " ok $Dest" -ForegroundColor DarkGray
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $Dest) {
|
||||||
|
if ($existingTarget) {
|
||||||
|
# A link, but pointing elsewhere -> replace it.
|
||||||
|
if ($DryRun) { Write-Host " relink $Dest (was -> $existingTarget)" -ForegroundColor Yellow; return }
|
||||||
|
Remove-Item -LiteralPath $Dest -Force -Recurse
|
||||||
|
} else {
|
||||||
|
# A real file/dir -> back it up rather than destroy it.
|
||||||
|
$bak = "$Dest.bak"
|
||||||
|
Write-Host " backup $Dest -> $bak" -ForegroundColor Yellow
|
||||||
|
if (-not $DryRun) { Move-Item -LiteralPath $Dest -Destination $bak -Force }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DryRun) { Write-Host " link $Dest -> $Source" -ForegroundColor Cyan; return }
|
||||||
|
|
||||||
|
$parent = Split-Path $Dest -Parent
|
||||||
|
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
|
||||||
|
|
||||||
|
if ($isDir) {
|
||||||
|
New-Item -ItemType Junction -Path $Dest -Target $Source | Out-Null
|
||||||
|
Write-Host " junc $Dest -> $Source" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
# Prefer a real symlink (needs admin or Developer Mode); fall back to a
|
||||||
|
# hardlink (same volume, no admin); finally just copy.
|
||||||
|
try {
|
||||||
|
New-Item -ItemType SymbolicLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null
|
||||||
|
Write-Host " link $Dest -> $Source" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
New-Item -ItemType HardLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null
|
||||||
|
Write-Host " hard $Dest -> $Source" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Copy-Item -LiteralPath $Source -Destination $Dest -Force
|
||||||
|
Write-Host " copy $Dest (could not link; copied)" -ForegroundColor Magenta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "home_root -> $home_"
|
||||||
|
Get-ChildItem -LiteralPath (Join-Path $repo 'home_root') -Force |
|
||||||
|
Where-Object { $skip -notcontains $_.Name } |
|
||||||
|
ForEach-Object { Link-Item $_.FullName (Join-Path $home_ $_.Name) }
|
||||||
|
|
||||||
|
$configDir = Join-Path $home_ '.config'
|
||||||
|
Write-Host "home_config -> $configDir"
|
||||||
|
Get-ChildItem -LiteralPath (Join-Path $repo 'home_config') -Force |
|
||||||
|
Where-Object { $skip -notcontains $_.Name } |
|
||||||
|
ForEach-Object { Link-Item $_.FullName (Join-Path $configDir $_.Name) }
|
||||||
|
|
||||||
|
Write-Host "Done." -ForegroundColor Green
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Install oh-my-posh on Windows (parity with ./lk-installers/install-ohmyposh).
|
||||||
|
.DESCRIPTION
|
||||||
|
Uses winget when available; otherwise falls back to the official installer.
|
||||||
|
No administrator rights are required (installs to the current user).
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
if (Get-Command oh-my-posh -ErrorAction SilentlyContinue) {
|
||||||
|
Write-Host "oh-my-posh already installed: $((Get-Command oh-my-posh).Source)"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
||||||
|
Write-Host "Installing oh-my-posh via winget..."
|
||||||
|
winget install JanDeDobbeleer.OhMyPosh -s winget --accept-source-agreements --accept-package-agreements
|
||||||
|
} else {
|
||||||
|
Write-Host "winget not found; using the official installer script..."
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://ohmyposh.dev/install.ps1'))
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Done. Open a new shell so PATH refreshes, then run .\link-dotfiles.ps1"
|
||||||
Reference in New Issue
Block a user