nu: add nushell config and apex-styled prompt

Initial nushell dotfiles: config, modules (git, k8s, sys, hypr, pkg,
dev), apex theme files, and a chezmoi-templated prompt that shows dir
and git branch/dirty using apex-neon/aeon colors with vi mode indicators.
This commit is contained in:
2026-05-17 08:48:19 +02:00
parent daa4fe8bc9
commit ff1cdc02b9
12 changed files with 576 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
let carapace_completer = {|spans: list<string>|
^carapace $spans.0 nushell ...$spans
| from json
| if ($in | default [] | where value =~ '^-.*ERR$' | is-empty) { $in } else { null }
}
$env.config = {
show_banner: false
buffer_editor: "nvim"
edit_mode: vi
cursor_shape: {
vi_insert: line
vi_normal: block
}
history: {
max_size: 100_000
sync_on_enter: true
file_format: "plaintext"
}
completions: {
case_sensitive: false
quick: true
partial: true
algorithm: "fuzzy"
external: {
enable: true
completer: $carapace_completer
}
}
}
plugin use gstat
plugin use polars
plugin use formats
plugin use query
plugin use skim
plugin use dns
plugin use audio
plugin use highlight
plugin use file
plugin use ls_colorize
plugin use regex
plugin use json_path
plugin use semver
plugin use parquet
# All files including hidden, sorted by type then name
def ll [path?: path] {
ls -a ($path | default .) | sort-by type name
}
# Files only, sorted by size descending
def lf [path?: path] {
ls -a ($path | default .) | where type == file | sort-by size -r
}
# Directories only, sorted by name
def ld [path?: path] {
ls -a ($path | default .) | where type == dir | sort-by name
}
# Listing with per-file git status column
def lg [path?: path] {
let files = ls -a ($path | default .) | sort-by type name
let dirty = try {
^git status --porcelain
| lines
| each {|l| {name: ($l | str substring 3..), git: ($l | str substring 0..2 | str trim)}}
} catch { [] }
if ($dirty | is-empty) { $files } else {
$files | join $dirty name --left
}
}
# Repo-level git status as a structured record
def gs [] { gstat }
# Show all custom commands with their descriptions
def nu-help [] {
help commands
| where command_type == "custom"
| select name description
| sort-by name
}
alias ez = ^eza --icons
alias ezl = ^eza -la --git --icons
alias cat = bat --paging=never
source ($nu.default-config-dir | path join "zoxide.nu")
source ($nu.default-config-dir | path join "k8s.nu")
source ($nu.default-config-dir | path join "pkg.nu")
source ($nu.default-config-dir | path join "git.nu")
source ($nu.default-config-dir | path join "sys.nu")
source ($nu.default-config-dir | path join "hypr.nu")
source ($nu.default-config-dir | path join "dev.nu")
use ~/.config/nushell/themes/{{ .chezmoi.config.data.theme }}.nu
source ($nu.default-config-dir | path join "prompt.nu")
+9
View File
@@ -0,0 +1,9 @@
# Cargo workspace crates as a table — run from workspace root
def cargo-ws [] {
^cargo metadata --format-version 1 --no-deps 2>/dev/null
| from json
| get packages
| select name version manifest_path
| each {|p| $p | upsert path ($p.manifest_path | path dirname) | reject manifest_path}
| sort-by name
}
+2
View File
@@ -0,0 +1,2 @@
$env.PNPM_HOME = ($env.HOME | path join ".local" "share" "pnpm")
$env.PATH = ($env.PATH | prepend $env.PNPM_HOME)
+44
View File
@@ -0,0 +1,44 @@
# List branches with upstream, last commit date and subject
def gb [] {
^git for-each-ref --format "%(refname:short)\t%(upstream:short)\t%(creatordate:relative)\t%(subject)" refs/heads
| lines
| each {|l|
let p = $l | split row "\t"
{
branch: $p.0
upstream: (if ($p.1 | is-empty) { null } else { $p.1 })
date: $p.2
subject: $p.3
}
}
}
# Git log as a table — default 20 entries, pass n to change
def gl [n: int = 20] {
^git log --format "%H\t%as\t%an\t%s" -n $n
| lines
| each {|l|
let p = $l | split row "\t"
{
hash: ($p.0 | str substring 0..7)
date: $p.1
author: $p.2
subject: $p.3
}
}
}
# Diff stats as a table (unstaged by default; --cached for staged)
def gd [--cached] {
let flags = if $cached { [--cached] } else { [] }
^git diff --numstat ...$flags
| lines
| each {|l|
let p = $l | split row "\t"
{
ins: ($p.0 | into int)
del: ($p.1 | into int)
file: $p.2
}
}
}
+27
View File
@@ -0,0 +1,27 @@
# All open windows as a table (class, title, workspace, floating, pid, pos, size)
def wins [] {
^hyprctl clients -j | from json
| each {|w| {
class: $w.class
title: $w.title
workspace: $w.workspace.name
floating: $w.floating
pid: $w.pid
pos: $"($w.at.0),($w.at.1)"
size: $"($w.size.0)x($w.size.1)"
}}
}
# Workspaces with window count, sorted by id
def wsps [] {
^hyprctl workspaces -j | from json
| select id name monitor windows hasfullscreen
| sort-by id
}
# Currently focused window as a record
def hypr-active [] {
^hyprctl activewindow -j | from json
| select class title workspace floating pid
| upsert workspace { get workspace | get name }
}
+87
View File
@@ -0,0 +1,87 @@
# Pods as a table — optional namespace (-n) or all namespaces (-A)
def kgp [namespace?: string, --all (-A)] {
let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] }
^kubectl get pods ...$flags -o json | from json | get items
| each {|p|
let cs = $p.status.containerStatuses? | default []
{
name: $p.metadata.name
namespace: $p.metadata.namespace
phase: $p.status.phase
ready: $"(($cs | where ready == true | length))/(($cs | length))"
restarts: ($cs | each { $in.restartCount } | math sum)
age: ((date now) - ($p.metadata.creationTimestamp | into datetime))
}
}
}
# Deployments as a table — optional namespace (-n) or all namespaces (-A)
def kgd [namespace?: string, --all (-A)] {
let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] }
^kubectl get deployments ...$flags -o json | from json | get items
| each {|d| {
name: $d.metadata.name
namespace: $d.metadata.namespace
ready: $"($d.status.readyReplicas? | default 0)/($d.spec.replicas)"
up-to-date: ($d.status.updatedReplicas? | default 0)
available: ($d.status.availableReplicas? | default 0)
age: ((date now) - ($d.metadata.creationTimestamp | into datetime))
}}
}
# Services as a table — optional namespace (-n) or all namespaces (-A)
def kgs [namespace?: string, --all (-A)] {
let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] }
^kubectl get services ...$flags -o json | from json | get items
| each {|s| {
name: $s.metadata.name
namespace: $s.metadata.namespace
type: $s.spec.type
cluster-ip: $s.spec.clusterIP
ports: ($s.spec.ports | each { $"($in.port):($in.targetPort)/($in.protocol)" } | str join ",")
age: ((date now) - ($s.metadata.creationTimestamp | into datetime))
}}
}
# Nodes as a table with roles and kubelet version
def kgn [] {
^kubectl get nodes -o json | from json | get items
| each {|n|
let ready = ($n.status.conditions | where type == "Ready" | first | get status)
let roles = ($n.metadata.labels | transpose key value
| where ($in.key | str starts-with "node-role.kubernetes.io/")
| each { $in.key | str replace "node-role.kubernetes.io/" "" }
| str join ",")
{
name: $n.metadata.name
status: (if $ready == "True" { "Ready" } else { "NotReady" })
roles: (if ($roles | is-empty) { "<none>" } else { $roles })
version: $n.status.nodeInfo.kubeletVersion
age: ((date now) - ($n.metadata.creationTimestamp | into datetime))
}
}
}
# Kubectl contexts as a table, current marked with *
def ctx [] {
let current = (^kubectl config current-context | str trim)
^kubectl config view -o json | from json | get contexts
| each {|c| {
current: (if $c.name == $current { "*" } else { "" })
name: $c.name
cluster: $c.context.cluster
user: $c.context.user
namespace: ($c.context.namespace? | default "default")
}}
}
# Switch kubectl context
def ctx-use [name: string] {
^kubectl config use-context $name
}
# Helm releases as a table — optional namespace (-n) or all namespaces (-A)
def helm-ls [namespace?: string, --all (-A)] {
let flags = if $all { [--all-namespaces] } else if ($namespace != null) { [-n $namespace] } else { [] }
^helm list ...$flags -o json | from json
}
+57
View File
@@ -0,0 +1,57 @@
# Installed packages as a table, optionally filtered by name
def pkgl [query?: string] {
^paru -Q --color never
| lines
| each {|l| let p = $l | split row ' '; {name: $p.0, version: $p.1}}
| if $query != null { where name =~ $query } else { $in }
}
# Packages with available upgrades (name, current, new)
def pkgu [] {
^paru -Qu --color never
| lines
| each {|l|
let p = $l | split row ' '
{name: $p.0, current: $p.1, new: $p.3}
}
}
# Search repos and AUR (repo, name, version, desc)
def pkgs [query: string] {
^paru -Ss --color never $query
| lines
| chunks 2
| each {|pair|
let header = $pair.0
let desc = ($pair.1? | default "" | str trim)
let parts = $header | split row '/'
let name_ver = ($parts.1 | split row ' ')
{
repo: $parts.0
name: $name_ver.0
version: $name_ver.1
desc: $desc
}
}
}
# Package info as a structured record
def pkgi [pkg: string] {
^paru -Qi --color never $pkg
| lines
| each {|l|
let parts = $l | split row ':'
if ($parts | length) >= 2 {
{key: ($parts.0 | str trim), value: ($parts | skip 1 | str join ':' | str trim)}
}
}
| compact
| transpose -r -d
}
# Which package owns a file
def pkgo [file: string] {
^paru -Qo --color never $file
| parse '{file} is owned by {name} {version}'
| first
}
+18
View File
@@ -0,0 +1,18 @@
$env.PROMPT_COMMAND = {||
let dir = ($env.PWD | str replace $env.HOME "~")
let branch = (try { ^git rev-parse --abbrev-ref HEAD err> /dev/null | str trim } catch { "" })
let dirty = (try {
if (^git status --porcelain err> /dev/null | str trim | is-empty) { "" } else { "*" }
} catch { "" })
let git_seg = if ($branch | is-empty) { "" } else {
$" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00ff99{{- else }}#00b377{{- end }}' })($branch)(ansi { fg: '#ff0044' })($dirty)(ansi reset)"
}
$"(ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00eaff{{- else }}#007a88{{- end }}' })($dir)(ansi reset)($git_seg)"
}
$env.PROMPT_INDICATOR_VI_INSERT = $" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#00ff99{{- else }}#00b377{{- end }}' })(ansi reset) "
$env.PROMPT_INDICATOR_VI_NORMAL = $" (ansi { fg: '{{- if eq .chezmoi.config.data.theme "apex-neon" }}#ffb700{{- else }}#d18f00{{- end }}' })(ansi reset) "
$env.TRANSIENT_PROMPT_COMMAND = $"(ansi { fg: '#737373' })(ansi reset) "
$env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = ""
$env.TRANSIENT_PROMPT_INDICATOR_VI_NORMAL = ""
+22
View File
@@ -0,0 +1,22 @@
# Processes sorted by CPU, optionally filtered by name
def psk [query?: string] {
ps | sort-by cpu -r
| if $query != null { where name =~ $query } else { $in }
}
# Listening TCP ports with process info
def ports [] {
^ss -tlnp
| detect columns
| rename state recv-q send-q local peer process
| each {|r| $r | upsert port (try { $r.local | split row ":" | last | into int } catch { null })}
| select state local port process
| sort-by port
}
# Environment variables as a filterable sorted table
def envs [query?: string] {
$env | transpose key value
| if $query != null { where key =~ $query } else { $in }
| sort-by key
}
+68
View File
@@ -0,0 +1,68 @@
# Apex Aeon — Nushell color config
# Usage: add `use /path/to/apex-aeon.nu` to config.nu
export-env {
$env.config.color_config = {
# --- Chrome ---
separator: "#737373"
leading_trailing_space_bg: { bg: "#737373" }
header: { fg: "#ff0044" attr: b }
empty: "#737373"
row_index: { fg: "#737373" attr: b }
hints: "#a0a0a0"
# --- Primitive types ---
string: { fg: "#00b377" }
int: { fg: "#007a88" }
float: { fg: "#007a88" }
bool: { fg: "#d18f00" }
filesize: { fg: "#007a88" }
duration: { fg: "#007a88" }
datetime: { fg: "#007a88" }
range: { fg: "#0a0a0a" }
record: { fg: "#0a0a0a" }
list: { fg: "#0a0a0a" }
block: { fg: "#7a3cff" }
nothing: { fg: "#737373" }
binary: { fg: "#007a88" }
# --- Shapes (REPL syntax highlighting) ---
shape_string: { fg: "#00b377" }
shape_string_interpolation: { fg: "#00b377" attr: b }
shape_int: { fg: "#007a88" }
shape_float: { fg: "#007a88" }
shape_bool: { fg: "#d18f00" }
shape_datetime: { fg: "#007a88" }
shape_binary: { fg: "#007a88" }
shape_filepath: { fg: "#007a88" }
shape_directory: { fg: "#007a88" }
shape_globpattern: { fg: "#007a88" }
shape_glob_interpolation: { fg: "#007a88" }
shape_nothing: { fg: "#737373" }
shape_variable: { fg: "#0a0a0a" }
shape_vardecl: { fg: "#0a0a0a" }
shape_operator: { fg: "#0a0a0a" }
shape_external: { fg: "#005577" }
shape_externalarg: { fg: "#0a0a0a" }
shape_internalcall: { fg: "#005577" attr: b }
shape_signature: { fg: "#005577" }
shape_flag: { fg: "#d18f00" }
shape_pipe: { fg: "#d18f00" attr: b }
shape_redirection: { fg: "#d18f00" }
shape_and: { fg: "#7a3cff" attr: b }
shape_or: { fg: "#7a3cff" attr: b }
shape_keyword: { fg: "#7a3cff" attr: b }
shape_block: { fg: "#7a3cff" }
shape_closure: { fg: "#7a3cff" }
shape_list: { fg: "#0a0a0a" }
shape_record: { fg: "#0a0a0a" }
shape_table: { fg: "#0a0a0a" }
shape_range: { fg: "#0a0a0a" }
shape_literal: { fg: "#0a0a0a" }
shape_custom: { fg: "#0a0a0a" }
shape_match_pattern: { fg: "#d18f00" }
shape_matching_brackets: { attr: u }
shape_garbage: { fg: "#ff0044" attr: b }
}
}
+68
View File
@@ -0,0 +1,68 @@
# Apex Neon — Nushell color config
# Usage: add `use /path/to/apex-neon.nu` to config.nu
export-env {
$env.config.color_config = {
# --- Chrome ---
separator: "#737373"
leading_trailing_space_bg: { bg: "#262626" }
header: { fg: "#ff0044" attr: b }
empty: "#737373"
row_index: { fg: "#737373" attr: b }
hints: "#404040"
# --- Primitive types ---
string: { fg: "#00ff99" }
int: { fg: "#00eaff" }
float: { fg: "#00eaff" }
bool: { fg: "#ffb700" }
filesize: { fg: "#00eaff" }
duration: { fg: "#00eaff" }
datetime: { fg: "#00eaff" }
range: { fg: "#ededed" }
record: { fg: "#ededed" }
list: { fg: "#ededed" }
block: { fg: "#9d00ff" }
nothing: { fg: "#737373" }
binary: { fg: "#00eaff" }
# --- Shapes (REPL syntax highlighting) ---
shape_string: { fg: "#00ff99" }
shape_string_interpolation: { fg: "#00ff99" attr: b }
shape_int: { fg: "#00eaff" }
shape_float: { fg: "#00eaff" }
shape_bool: { fg: "#ffb700" }
shape_datetime: { fg: "#00eaff" }
shape_binary: { fg: "#00eaff" }
shape_filepath: { fg: "#00eaff" }
shape_directory: { fg: "#00eaff" }
shape_globpattern: { fg: "#00eaff" }
shape_glob_interpolation: { fg: "#00eaff" }
shape_nothing: { fg: "#737373" }
shape_variable: { fg: "#ededed" }
shape_vardecl: { fg: "#ededed" }
shape_operator: { fg: "#ededed" }
shape_external: { fg: "#0088cc" }
shape_externalarg: { fg: "#ededed" }
shape_internalcall: { fg: "#0088cc" attr: b }
shape_signature: { fg: "#0088cc" }
shape_flag: { fg: "#ffb700" }
shape_pipe: { fg: "#ffb700" attr: b }
shape_redirection: { fg: "#ffb700" }
shape_and: { fg: "#9d00ff" attr: b }
shape_or: { fg: "#9d00ff" attr: b }
shape_keyword: { fg: "#9d00ff" attr: b }
shape_block: { fg: "#9d00ff" }
shape_closure: { fg: "#9d00ff" }
shape_list: { fg: "#ededed" }
shape_record: { fg: "#ededed" }
shape_table: { fg: "#ededed" }
shape_range: { fg: "#ededed" }
shape_literal: { fg: "#ededed" }
shape_custom: { fg: "#ededed" }
shape_match_pattern: { fg: "#ffb700" }
shape_matching_brackets: { attr: u }
shape_garbage: { fg: "#ff0044" attr: b }
}
}
+70
View File
@@ -0,0 +1,70 @@
# Code generated by zoxide. DO NOT EDIT.
# =============================================================================
#
# Hook configuration for zoxide.
#
# Initialize hook to add new entries to the database.
export-env {
$env.config = (
$env.config?
| default {}
| upsert hooks { default {} }
| upsert hooks.env_change { default {} }
| upsert hooks.env_change.PWD { default [] }
)
let __zoxide_hooked = (
$env.config.hooks.env_change.PWD | any { try { get __zoxide_hook } catch { false } }
)
if not $__zoxide_hooked {
$env.config.hooks.env_change.PWD = ($env.config.hooks.env_change.PWD | append {
__zoxide_hook: true,
code: {|_, dir| ^zoxide add -- $dir}
})
}
}
# =============================================================================
#
# When using zoxide with --no-cmd, alias these internal functions as desired.
#
# Jump to a directory using only keywords.
def --env --wrapped __zoxide_z [...rest: string] {
let path = match $rest {
[] => {'~'},
[ '-' ] => {'-'},
[ $arg ] if ($arg | path expand | path type) == 'dir' => {$arg}
_ => {
^zoxide query --exclude $env.PWD -- ...$rest | str trim -r -c "\n"
}
}
cd $path
}
# Jump to a directory using interactive search.
def --env --wrapped __zoxide_zi [...rest:string] {
cd $'(^zoxide query --interactive -- ...$rest | str trim -r -c "\n")'
}
# =============================================================================
#
# Commands for zoxide. Disable these using --no-cmd.
#
alias z = __zoxide_z
alias zi = __zoxide_zi
# =============================================================================
#
# Add this to your env file (find it by running `$nu.env-path` in Nushell):
#
# zoxide init nushell | save -f ~/.zoxide.nu
#
# Now, add this to the end of your config file (find it by running
# `$nu.config-path` in Nushell):
#
# source ~/.zoxide.nu
#
# Note: zoxide only supports Nushell v0.89.0+.