initialise: dotfiles

This commit is contained in:
Osaigbovo Omere 2026-08-15 19:39:06 +01:00
commit 66a4a699b0
No known key found for this signature in database
106 changed files with 6092 additions and 0 deletions

35
.config/README.md Normal file
View file

@ -0,0 +1,35 @@
# .config
This directory contains user-specific configuration files conforming to the XDG Base Directory Specification. The configurations map out a modular, Wayland-centric desktop environment alongside core terminal and system utilities.
## COMPOSITORS AND SESSION MANAGEMENT
* **niri:** Scrollable-tiling Wayland compositor.
* **sway:** Tiling Wayland compositor and X11/i3 replacement.
* **swaylock:** Screen locking utility for Wayland sessions.
* **nwg-displays:** Output and display management.
## TERMINALS AND SHELL ENVIRONMENT
* **kitty:** GPU-accelerated terminal emulator.
* **foot:** Lightweight Wayland terminal emulator.
* **fish:** Interactive shell configuration.
* **tmux:** Terminal multiplexer for persistent workspace sessions.
* **starship.toml:** Cross-shell prompt configuration.
## SYSTEM UI AND THEMING
* **waybar:** Customisable Wayland status bar.
* **fuzzel:** Application launcher for wlroots-based compositors.
* **mako:** Lightweight notification daemon.
* **nwg-look:** GTK settings editor for maintaining visual consistency.
* **qt5ct / qt6ct:** Qt configuration utilities for uniform theming across toolkits.
## MEDIA AND DOCUMENTS
* **mpd:** Music Player Daemon for backend audio management.
* **ncmpcpp:** Feature-rich ncurses client for MPD.
* **mpv:** Hardware-accelerated command-line media player.
* **zathura:** Keyboard-driven document and PDF viewer.
## SYSTEM UTILITIES AND DEVELOPMENT
* **nvim:** Neovim configuration for development and text editing.
* **git:** Global version control parameters.
* **btop:** Resource monitor for system usage and hardware polling.
* **incus:** Local configuration for the container and virtual machine daemon.

View file

@ -0,0 +1,63 @@
# --- CORE CAPABILITIES ---
[env]
TERM = "alacritty"
[window]
padding = { x = 8, y = 8 }
dynamic_padding = true
# sway manages window borders and aesthetics
decorations = "none"
opacity = 1.0
[scrolling]
history = 10000
multiplier = 3
# --- TYPOGRAPHY ---
[font]
size = 9.0
[font.normal]
family = "JetBrainsMono Nerd Font"
style = "Regular"
[font.bold]
family = "JetBrainsMono Nerd Font"
style = "Bold"
[font.italic]
family = "JetBrainsMono Nerd Font"
style = "Italic"
# --- blackK ---
[colors.primary]
background = "#030303"
foreground = "#eeeeee"
[colors.cursor]
text = "#100f0f"
cursor = "#cecdc3"
[colors.normal]
black = "#100f0f"
red = "#af3029"
green = "#66800b"
yellow = "#ad8301"
blue = "#205ea6"
magenta = "#5e409d"
cyan = "#24837b"
white = "#cecdc3"
[colors.bright]
black = "#282726"
red = "#d14d41"
green = "#879a39"
yellow = "#d0a215"
blue = "#4385be"
magenta = "#8b7ec8"
cyan = "#3aa99f"
white = "#e6e4d9"
# --- KEYBOARD AND MOUSE ---
[mouse]
hide_when_typing = true

286
.config/btop/btop.conf Normal file
View file

@ -0,0 +1,286 @@
#? Config file for btop v.1.4.7
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes.
#* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
color_theme = "/home/dil/.config/btop/themes/flexoki-dark.theme"
#* If the theme set background should be shown, set to False if you want terminal background transparency.
theme_background = true
#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false.
truecolor = true
#* Set to true to force tty mode regardless if a real tty has been detected or not.
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols.
force_tty = false
#* Option to disable presets. Either the default preset, custom presets, or all presets.
#* "Off" All presets are enabled.
#* "Default" preset is disabled.#* "Custom" presets are disabled.#* "All" presets are disabled.
disable_presets = "Off"
#* Define presets for the layout of the boxes. Preset 0 is always all boxes shown with default settings. Max 9 presets.
#* Format: "box_name:P:G,box_name:P:G" P=(0 or 1) for alternate positions, G=graph symbol to use for box.
#* Use whitespace " " as separator between different presets.
#* Example: "cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty"
presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:default cpu:0:block,net:0:tty"
#* Set to True to enable "h,j,k,l,g,G" keys for directional control in lists.
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = false
#* Disable all mouse events.
disable_mouse = false
#* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = true
#* Use terminal synchronized output sequences to reduce flickering on supported terminals.
terminal_sync = true
#* Default symbols to use for graph creation, "braille", "block" or "tty".
#* "braille" offers the highest resolution but might not be included in all fonts.
#* "block" has half the resolution of braille but uses more common characters.
#* "tty" uses only 3 different symbols but will work with most fonts and should work in a real TTY.
#* Note that "tty" only has half the horizontal resolution of the other two, so will show a shorter historical view.
graph_symbol = "braille"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_cpu = "default"
# Graph symbol to use for graphs in gpu box, "default", "braille", "block" or "tty".
graph_symbol_gpu = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_mem = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_net = "default"
# Graph symbol to use for graphs in cpu box, "default", "braille", "block" or "tty".
graph_symbol_proc = "default"
#* Manually set which boxes to show. Available values are "cpu mem net proc" and "gpu0" through "gpu5", separate values with whitespace.
shown_boxes = "cpu proc"
#* Update time in milliseconds, recommended 2000 ms or above for better sample times for graphs.
update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct",
#* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False.
proc_reversed = false
#* Show processes as a tree.
proc_tree = false
#* Use the cpu graph colors in the process list.
proc_colors = true
#* Use a darkening gradient in the process list.
proc_gradient = true
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core = false
#* Show process memory as bytes instead of percent.
proc_mem_bytes = true
#* Show cpu graph for each process.
proc_cpu_graphs = true
#* Use /proc/[pid]/smaps for memory information in the process info box (very slow but more accurate)
proc_info_smaps = false
#* Show proc box on left side of screen instead of right.
proc_left = false
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = false
#* Should the process list follow the selected process when detailed view is open.
proc_follow_detailed = true
#* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = false
#* Should cpu and memory usage display be preserved for dead processes when paused.
keep_dead_proc_usage = false
#* Sets the CPU stat shown in upper half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_upper = "Auto"
#* Sets the CPU stat shown in lower half of the CPU graph, "total" is always available.
#* Select from a list of detected attributes from the options menu.
cpu_graph_lower = "Auto"
#* If gpu info should be shown in the cpu box. Available values = "Auto", "On" and "Off".
show_gpu_info = "Auto"
#* Toggles if the lower CPU graph should be inverted.
cpu_invert_lower = true
#* Set to True to completely disable the lower CPU graph.
cpu_single_graph = false
#* Show cpu box at bottom of screen instead of top.
cpu_bottom = false
#* Shows the system uptime in the CPU box.
show_uptime = true
#* Shows the CPU package current power consumption in watts. Requires running `make setcap` or `make setuid` or running with sudo.
show_cpu_watts = true
#* Show cpu temperature.
check_temp = true
#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors.
cpu_sensor = "Auto"
#* Show temperatures for cpu cores also if check_temp is True and sensors has been found.
show_coretemp = true
#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.
#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.
#* Format "x:y" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.
#* Example: "4:0 5:1 6:3"
cpu_core_map = ""
#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine".
temp_scale = "celsius"
#* Use base 10 for bits/bytes sizes, KB = 1000 instead of KiB = 1024.
base_10_sizes = false
#* Show CPU frequency.
show_cpu_freq = true
#* How to calculate CPU frequency, available values: "first", "range", "lowest", "highest" and "average".
freq_mode = "first"
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
#* Special formatting: /host = hostname | /user = username | /uptime = system uptime
clock_format = "%X"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update = true
#* Custom cpu model name, empty string to disable.
custom_cpu_name = ""
#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with whitespace " ".
#* Only disks matching the filter will be shown. Prepend exclude= to only show disks not matching the filter. Examples: disk_filter="/boot /home/user", disks_filter="exclude=/boot /home/user"
disks_filter = ""
#* Show graphs instead of meters for memory values.
mem_graphs = true
#* Show mem box below net box instead of above.
mem_below_net = false
#* Count ZFS ARC in cached and available memory.
zfs_arc_cached = true
#* If swap memory should be shown in memory box.
show_swap = true
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = true
#* If mem box should be split to also show disks info.
show_disks = true
#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar.
only_physical = true
#* Read disks list from /etc/fstab. This also disables only_physical.
use_fstab = true
#* Setting this to True will hide all datasets, and only show ZFS pools. (IO stats will be calculated per-pool)
zfs_hide_datasets = false
#* Set to true to show available disk space for privileged users.
disk_free_priv = false
#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view.
show_io_stat = true
#* Toggles io mode for disks, showing big graphs for disk read/write speeds.
io_mode = false
#* Set to True to show combined read/write io graphs in io mode.
io_graph_combined = false
#* Set the top speed for the io graphs in MiB/s (100 by default), use format "mountpoint:speed" separate disks with whitespace " ".
#* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = ""
#* Swap the positions of the upload and download speed graphs. When true, upload will be on top.
swap_upload_download = false
#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100
net_upload = 100
#* Use network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = true
#* Sync the auto scaling for download and upload to whichever currently has the highest scale.
net_sync = true
#* Starts with the Network Interface specified here.
net_iface = ""
#* "True" shows bitrates in base 10 (Kbps, Mbps). "False" shows bitrates in binary sizes (Kibps, Mibps, etc.). "Auto" uses base_10_sizes.
base_10_bitrate = "Auto"
#* Show battery stats in top right if battery is present.
show_battery = true
#* Which battery to use if multiple are present. "Auto" for auto detection.
selected_battery = "Auto"
#* Show power stats of battery next to charge indicator.
show_battery_watts = true
#* Set loglevel for "~/.local/state/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level = "WARNING"
#* Automatically save current settings to config file on exit.
save_config_on_exit = true
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = true
#* Measure PCIe throughput on AMD cards, may impact performance on certain cards.
rsmi_measure_pcie_speeds = true
#* Horizontally mirror the GPU graph.
gpu_mirror_graph = true
#* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
shown_gpus = "nvidia amd intel"
#* Custom gpu0 model name, empty string to disable.
custom_gpu_name0 = ""
#* Custom gpu1 model name, empty string to disable.
custom_gpu_name1 = ""
#* Custom gpu2 model name, empty string to disable.
custom_gpu_name2 = ""
#* Custom gpu3 model name, empty string to disable.
custom_gpu_name3 = ""
#* Custom gpu4 model name, empty string to disable.
custom_gpu_name4 = ""
#* Custom gpu5 model name, empty string to disable.
custom_gpu_name5 = ""

27
.config/fuzzel/fuzzel.ini Normal file
View file

@ -0,0 +1,27 @@
# THE NOIR THEME
[main]
# fuzzel requires wrapper scripts for dynamic per-mode prompts
prompt="APP: "
font=JetBrainsMono Nerd Font:size=8,Noto Color Emoji:size=8
# character width dictates the horizontal size
width=50
lines=8
icons-enabled=yes
terminal=footclient
# disable the generic exit button to maintain the sharp aesthetic
exit-on-keyboard-focus-loss=yes
[colors]
# flexoki ink translations
background=100f0fff
text=cecdc3ff
match=8b7ec8ff
selection=282726ff
selection-text=cecdc3ff
selection-match=8b7ec8ff
border=343331ff
[border]
width=1
radius=0

View file

@ -0,0 +1,60 @@
[gpg]
format = ssh
[core]
editor = nvim
autocrlf = input
pager = delta
[init]
defaultBranch = main
[color]
ui = auto
[pull]
rebase = true
[push]
autoSetupRemote = true
[merge]
tool = nvimdiff
[mergetool]
prompt = false
keepBackup = false
[interactive]
diffFilter = delta --color-only
[delta]
navigate = true
line-numbers = true
side-by-side = true
[commit]
gpgsign = true
[tag]
gpgsign = true
[alias]
s = status
st = status -sb
lg = log --oneline --graph --decorate --all
aa = add -A
cm = commit -m
am = commit -am
ps = push
pl = pull
lo = log --oneline --graph --decorate
unstage = reset HEAD --
last = log -1 HEAD
oops = commit --amend --no-edit
dot = "!git --git-dir=$HOME/.dotfiles --work-tree=$HOME"

41
.config/kitty/kitty.conf Normal file
View file

@ -0,0 +1,41 @@
# WINDOW
window_padding_width 8
hide_window_decorations yes
background_opacity 1.0
# SCROLLBACK
scrollback_lines 10000
mouse_hide_wait -1
# FONT
font_size 9
font_family JetBrainsMono Nerd Font
bold_font JetBrainsMono Nerd Font Bold
italic_font JetBrainsMono Nerd Font Italic
bold_italic_font JetBrainsMono Nerd Font Bold Italic
# -COLOURS
foreground #cecdc3
background #000000
cursor_text_color #100f0f
color0 #100f0f
color1 #af3029
color2 #66800b
color3 #ad8301
color4 #205ea6
color5 #5e409d
color6 #24837b
color7 #cecdc3
color8 #282726
color9 #d14d41
color10 #879a39
color11 #d0a215
color12 #4385be
color13 #8b7ec8
color14 #3aa99f
color15 #e6e4d9
# SHELL
#shell /home/dil/.local/bin/tmux-wrapper

View file

41
.config/mako/config Normal file
View file

@ -0,0 +1,41 @@
# --- THE VOID: MAKO EDITION ---
# Font & Text
font=JetBrainsMono Nerd Font 12
text-color=#ffffff
width=350
height=150
margin=10
padding=15
# The Box (Hard edges, glass background)
background-color=#000000cc
border-size=1
border-color=#333333
border-radius=0
# Icons
icons=1
max-icon-size=48
icon-location=left
# Behavior
default-timeout=5000
ignore-timeout=1
layer=overlay
anchor=top-right
# --- URGENCY OVERRIDES ---
# Critical alerts (Inverted: White bg, Black text, just like your Critical Battery)
[urgency=critical]
background-color=#ffffff
text-color=#000000
border-color=#ffffff
# Low priority (Dimmer)
[urgency=low]
default-timeout=1500
background-color=#1a1a1a
text-color=#888888
border-color=#333333

BIN
.config/mpd/database Normal file

Binary file not shown.

30
.config/mpd/mpd.conf Normal file
View file

@ -0,0 +1,30 @@
# FILES
music_directory "~/music"
playlist_directory "~/.config/mpd/playlists"
db_file "~/.config/mpd/database"
log_file "syslog"
pid_file "/tmp/mpd.pid"
state_file "~/.config/mpd/state"
# NETWORK
bind_to_address "127.0.0.1"
port "6600"
# AUDIO OUTPUT (PipeWire/Pulse)
audio_output {
type "pulse"
name "PulseAudio/PipeWire"
}
# VISUALISER FEED
audio_output {
type "fifo"
name "visualiser"
path "/tmp/mpd.fifo"
format "44100:16:2"
enabled "yes"
}
# TWEAKS
auto_update "yes"
restore_paused "yes"

43
.config/mpv/input.conf Normal file
View file

@ -0,0 +1,43 @@
# --- VIM NAVIGATION (Time) ---
h seek -5 # Left: Back 5s
l seek 5 # Right: Forward 5s
Shift+h seek -60 # H: Back 1 min
Shift+l seek 60 # L: Forward 1 min
# --- VIM AUDIO (Volume) ---
k add volume 5 # Up: Volume Up
j add volume -5 # Down: Volume Down
m cycle mute # Mute
# --- TRACK SELECTION (The Missing Part) ---
a cycle audio # Next Audio Track
Shift+a cycle audio down # Previous Audio Track
c cycle sub # Next Subtitle (Captions)
Shift+c cycle sub down # Previous Subtitle
v cycle sub-visibility # Hide/Show Subtitles
# --- SYNC CORRECTION (Because rips aren't perfect) ---
z add sub-delay -0.1 # Shift subs earlier
Shift+z add sub-delay +0.1 # Shift subs later
- add audio-delay -0.1 # Shift audio earlier
= add audio-delay +0.1 # Shift audio later
# --- PLAYBACK CONTROL ---
SPACE cycle pause # Play/Pause
. frame-step # Next Frame
, frame-back-step # Previous Frame
> add speed 0.1 # Speed Up
< add speed -0.1 # Slow Down
BS set speed 1.0 # Reset Speed (Backspace)
# --- POSITION & VIEW ---
0 seek 0 absolute # Go to Start
$ seek 100 absolute-percent # Go to End
f cycle fullscreen # Toggle Fullscreen
s screenshot # Take Screenshot
S screenshot video # Screenshot (No Subs)
# --- UTILS ---
q quit-watch-later # Quit (Save position)
Shift+q quit # Quit (Discard position)
i script-binding stats/display-stats-toggle # Show Info

25
.config/mpv/mpv.conf Normal file
View file

@ -0,0 +1,25 @@
# VIDEO AND PERFORMANCE
# use gpu for video output
vo=gpu
# enable safe hardware decoding to reduce cpu usage
hwdec=auto-safe
# keep the player window open after the file finishes
keep-open=yes
# AUDIO BEHAVIOUR
# default startup volume
volume=100
# maximum volume boost
volume-max=150
# maintain original audio pitch when changing playback speed
audio-pitch-correction=yes
# SUBTITLES AND LANGUAGES
# prefer english audio
alang=en,eng
# prefer english subtitles
slang=en,eng
# set subtitle text colour to white
sub-color="#FFFFFFFF"
# scale subtitles relative to window size
sub-scale-with-window=yes

30
.config/ncmpcpp/config Normal file
View file

@ -0,0 +1,30 @@
# --- CONNECTION ---
mpd_host = "127.0.0.1"
mpd_port = "6600"
# --- VISUALIZER (The Cool Part) ---
visualizer_data_source = "/tmp/mpd.fifo"
visualizer_output_name = "my_fifo"
visualizer_in_stereo = "yes"
visualizer_type = "spectrum" # Options: spectrum, wave, ellipse
visualizer_look = "●●" # The characters for the graph
# --- UI TWEAKS ---
playlist_display_mode = "columns" # Classic table view
browser_display_mode = "columns"
search_engine_display_mode = "columns"
playlist_editor_display_mode = "columns"
autocenter_mode = "yes"
centered_cursor = "yes"
user_interface = "alternative" # Cleaner look
# --- COLORS (Arch-Droid Style) ---
colors_enabled = "yes"
main_window_color = "default"
header_window_color = "cyan"
volume_color = "green"
progressbar_color = "black"
progressbar_elapsed_color = "green"
# --- SEEKING ---
seek_time = "5"

View file

@ -0,0 +1,22 @@
// STARTUP DAEMONS
//spawn-at-startup "/usr/lib/polkit-kde-authentication-agent-1"
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
spawn-at-startup "swaybg" "-i" "/home/sgbv/Pictures/turtle.jpg" "-m" "fill"
spawn-at-startup "waybar"
spawn-at-startup "mako"
spawn-at-startup "nm-applet"
spawn-at-startup "blueman-applet"
spawn-at-startup "wireplumber"
spawn-at-startup "pipewire"
spawn-at-startup "pipewire-pulse"
spawn-at-startup "sh" "-c" "mkdir -p /run/user/1000/keyring && gnome-keyring-daemon --start --components=secrets"
// CLIPBOARD TRACKING
spawn-at-startup "wl-paste" "--type" "text" "--watch" "cliphist" "store"
spawn-at-startup "wl-paste" "--type" "image" "--watch" "cliphist" "store"
// IDLE MANAGEMENT
spawn-at-startup "bash" "-c" "swayidle -w timeout 150 'brightnessctl -s set 10' resume 'brightnessctl -r' timeout 200 'swaylock -f -c 000000' timeout 250 'niri msg action power-off-monitors' resume 'niri msg action power-on-monitors' timeout 1800 'systemctl suspend' before-sleep 'swaylock -f -c 000000'"
// BRIGHTNESS
spawn-at-startup "~/.local/bin/restore-brightness"

95
.config/niri/binds.kdl Normal file
View file

@ -0,0 +1,95 @@
// KEYBINDINGS
binds {
Mod+Return cooldown-ms=500 { spawn "alacritty"; }
Mod+Shift+Return cooldown-ms=500 { spawn "code"; }
Alt+Return cooldown-ms=500 {spawn "virt-manager"; }
Mod+Space { spawn "fuzzel"; }
Mod+Shift+Space { spawn "bash" "-c" "$HOME/.local/bin/fuzzel-run"; }
// APPLICATION LAUNCHERS
Mod+B cooldown-ms=500 { spawn "librewolf"; }
Mod+Shift+B cooldown-ms=500 { spawn "firefox"; }
Mod+Alt+B cooldown-ms=500 { spawn "tor-browser"; }
Mod+O cooldown-ms=500 { spawn "obsidian"; }
Mod+A cooldown-ms=500 { spawn "enteauth"; }
Mod+Shift+A cooldown-ms=500 { spawn "keepassxc"; }
Mod+E cooldown-ms=500 { spawn "alacritty" "-e" "yazi"; }
Mod+Shift+E cooldown-ms=500 { spawn "nautilus"; }
Mod+G cooldown-ms=500 { spawn "azahar"; }
Mod+T cooldown-ms=500 { spawn "telegram-desktop"; }
// TUI SYSTEM WORKPLACES
Mod+D cooldown-ms=500 { spawn "alacritty" "-e" "btop"; }
Mod+Shift+Y cooldown-ms=500 { spawn "alacritty" "-e" "lazygit"; }
Mod+M cooldown-ms=500 { spawn "alacritty" "-e" "ncmpcpp"; }
Mod+Shift+T cooldown-ms=500 { spawn "alacritty" "-e" "tut"; }
// HARDWARE AND OVERLAYS
Mod+Shift+C { spawn "/home/sgbv/.local/bin/pick"; }
Mod+W { spawn-sh "killall -SIGUSR1 waybar"; }
// WINDOW MECHANICS
Mod+Q repeat=false { close-window; }
Mod+Shift+Q { quit skip-confirmation=true; }
// SPATIAL NAVIGATION
Mod+H { focus-column-left; }
Mod+L { focus-column-right; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+Shift+H { move-column-left; }
Mod+Shift+L { move-column-right; }
Mod+Shift+J { move-window-down; }
Mod+Shift+K { move-window-up; }
Mod+U { focus-workspace-down; }
Mod+I { focus-workspace-up; }
Mod+Shift+U { move-column-to-workspace-down; }
Mod+Shift+I { move-column-to-workspace-up; }
// INTERFACE GEOMETRY
Mod+Minus { set-column-width "-10%"; }
Mod+Equal { set-column-width "+10%"; }
Mod+Shift+Minus { set-window-height "-10%"; }
Mod+Shift+Equal { set-window-height "+10%"; }
Mod+Control+L { consume-window-into-column; }
Mod+Control+H { expel-window-from-column; }
Mod+F { maximize-column; }
Mod+Shift+F { fullscreen-window; }
Mod+V { toggle-window-floating; }
// NEWLY BOUND ACTIONS
Mod+Slash { show-hotkey-overlay; }
Mod+Tab { switch-preset-column-width; }
Mod+Shift+V { switch-focus-between-floating-and-tiling; }
Mod+R { open-overview; }
// HARDWARE MEDIA ROUTING
Mod+Alt+K { spawn "~/.local/bin/volume-notify" "+10%"; }
Mod+Alt+J { spawn "~/.local/bin/volume-notify" "-10%"; }
XF86AudioRaiseVolume { spawn "~/.local/bin/volume-notify" "+5%"; }
XF86AudioLowerVolume { spawn "~/.local/bin/volume-notify" "-5%"; }
XF86AudioMute { spawn "~/.local/bin/volume-notify" "toggle"; }
XF86AudioMicMute { spawn "pamixer" "--default-source" "-t"; }
XF86MonBrightnessUp { spawn "~/.local/bin/brightness-notify" "+5%"; }
XF86MonBrightnessDown { spawn "~/.local/bin/brightness-notify" "5%-"; }
Mod+Alt+I { spawn "~/.local/bin/brightness-notify" "+10%"; }
Mod+Alt+U { spawn "~/.local/bin/brightness-notify" "10%-"; }
// MPC BINDS
Mod+Alt+Space { spawn "mpc" "toggle"; }
Mod+Alt+L { spawn "mpc" "next"; }
Mod+Alt+H { spawn "mpc" "prev"; }
Mod+Alt+Shift+K { spawn "mpc" "stop"; }
Mod+Print { spawn "bash" "-c" "grim -g \"$(slurp)\" - | wl-copy"; }
Print { spawn "bash" "-c" "grim - | wl-copy"; }
Alt+Print { screenshot-window; }
Mod+C { spawn "bash" "-c" "cliphist list | fuzzel -d | cliphist decode | wl-copy"; }
Mod+Period { spawn "bash" "-c" "rofimoji --selector fuzzel --action type --clipboarder wl-copy"; }
Mod+Escape { spawn "~/.local/bin/power"; }
}

52
.config/niri/config.kdl Normal file
View file

@ -0,0 +1,52 @@
// HARDWARE OUTPUT
output "eDP-1" {
scale 1.0
}
// INPUT CONFIGURATION
input {
keyboard {
xkb {
//layout "de"
layout "gb"
}
}
touchpad {
tap
natural-scroll
}
focus-follows-mouse max-scroll-amount="0%"
}
// APPEARANCE AND SPATIAL LAYOUT
layout {
gaps 2
center-focused-column "never"
always-center-single-column
default-column-width { proportion 0.5; }
border {
width 0.5
active-color "rgb(61, 76, 7)"
inactive-color "rgb(26, 30, 12)"
}
shadow {
off
}
focus-ring {
off
}
}
// MODULE INCLUSIONS
// merge external logical blocks
include "autostart.kdl"
include "rules.kdl"
include "binds.kdl"
// MISCELLANEOUS CONFIGURATION
// disable saving screenshots to disk
screenshot-path null

154
.config/niri/rules.kdl Normal file
View file

@ -0,0 +1,154 @@
// Floating windows
window-rule {
match app-id="blueman-manager"
match app-id="nm-connection-editor"
open-floating true
}
window-rule {
match app-id="firefox" title="Picture-in-Picture"
match app-id="librewolf" title="Picture-in-Picture"
open-floating true
default-floating-position x=1466 y=44
min-width 439
min-height 248
max-width 439
max-height 248
}
window-rule {
match app-id="io.ente.auth"
open-floating true
default-floating-position x=1533 y=332
min-width 352
min-height 447
max-width 352
max-height 447
}
window-rule {
match app-id="org.keepassxc.KeePassXC"
open-floating true
default-floating-position x=1092 y=43
min-width 808
min-height 535
max-width 808
max-height 535
}
window-rule {
match app-id="blueman-manager"
open-on-workspace "main"
}
window-rule {
match app-id="nm-connection-editor"
open-on-workspace "main"
}
// Maximized windows
window-rule {
match app-id="org.azahar_emu.Azahar"
open-maximized-to-edges true
}
window-rule {
match app-id="Tor Browser"
open-maximized-to-edges true
}
window-rule {
match app-id="Tor Browser"
open-maximized-to-edges true
}
// Column widths
window-rule {
match app-id="Alacritty"
default-column-width { proportion 0.60; }
}
window-rule {
match app-id="org.gnome.Nautilus"
default-column-width { proportion 0.60; }
}
window-rule {
match app-id="code"
default-column-width { proportion 0.60; }
}
window-rule {
match app-id="firefox"
default-column-width { proportion 0.40; }
}
window-rule {
match app-id="librewolf"
default-column-width { proportion 0.40; }
}
// Privacy
window-rule {
match app-id="Tor Browser"
block-out-from "screencast"
}
// Workspace placement
window-rule {
match app-id="Alacritty"
open-on-workspace "main"
}
window-rule {
match app-id="org.gnome.Nautilus"
open-on-workspace "main"
}
window-rule {
match app-id="code"
open-on-workspace "main"
}
window-rule {
match app-id="firefox"
open-on-workspace "main"
}
window-rule {
match app-id="librewolf"
open-on-workspace "main"
}
window-rule {
match app-id="md.Obsidian"
open-on-workspace "gaming"
}
window-rule {
match app-id="org.azahar_emu.Azahar"
open-on-workspace "gaming"
}
window-rule {
match app-id="org.telegram.desktop"
open-on-workspace "private"
}
window-rule {
match app-id="Tor Browser"
open-on-workspace "private"
}
// Workspaces
workspace "main"
workspace "gaming"
workspace "private"

157
.config/nvim/init.lua Normal file
View file

@ -0,0 +1,157 @@
-- CORE SETTINGS
-- define leader key
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- editor behaviour
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.mouse = "a"
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.updatetime = 250
vim.opt.termguicolors = true
-- wayland clipboard integration
vim.opt.clipboard = "unnamedplus"
-- regional standardisation
vim.opt.spelllang = "en_gb"
-- PACKAGE MANAGER
-- bootstrap lazy nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable",
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- PLUGINS
-- load flexoki and core utilities
require("lazy").setup({
{
"kepano/flexoki-neovim",
name = "flexoki",
priority = 1000,
config = function()
vim.cmd("colorscheme flexoki-dark")
-- override specific highlights to match your sway environment
vim.api.nvim_set_hl(0, "Normal", { bg = "#100F0F" })
vim.api.nvim_set_hl(0, "NormalFloat", { bg = "#100F0F" })
end,
},
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
-- INSTALLATION
-- trigger parser downloads
require("nvim-treesitter").install({ "lua", "bash", "markdown", "markdown_inline" })
-- HIGHLIGHTING
-- activate native treesitter engine per filetype
vim.api.nvim_create_autocmd("FileType", {
pattern = { "lua", "sh", "markdown" },
callback = function()
vim.treesitter.start()
end,
})
end,
},
{
"hrsh7th/nvim-cmp",
dependencies = {
"hrsh7th/cmp-path",
"hrsh7th/cmp-buffer",
},
config = function()
local cmp = require("cmp")
-- COMPLETION ENGINE SETUP
-- initialise strictly local sources for paths and buffer text
cmp.setup({
mapping = cmp.mapping.preset.insert({
["<C-b>"] = cmp.mapping.scroll_docs(-4),
["<C-f>"] = cmp.mapping.scroll_docs(4),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.abort(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
}),
sources = cmp.config.sources({
{ name = "path" },
{ name = "buffer" },
})
})
end,
},
{
"akinsho/bufferline.nvim",
dependencies = { "nvim-tree/nvim-web-devicons" },
config = function()
-- BUFFER VISUALISATION
-- render background buffers as a top navigational bar
require("bufferline").setup({
options = {
mode = "buffers",
numbers = "ordinal",
show_buffer_close_icons = false,
show_close_icon = false,
separator_style = "thin",
enforce_regular_tabs = true,
always_show_bufferline = true,
}
})
-- KEY MAPS
-- bind shift-h and shift-l to cycle through the open buffers
vim.keymap.set("n", "<S-h>", "<cmd>BufferLineCyclePrev<CR>", { desc = "previous buffer" })
vim.keymap.set("n", "<S-l>", "<cmd>BufferLineCycleNext<CR>", { desc = "next buffer" })
-- buffer termination
-- bind leader-x to safely unload the current buffer from memory
vim.keymap.set("n", "<leader>x", "<cmd>bdelete<CR>", { desc = "close current buffer" })
end,
},
{
"MeanderingProgrammer/render-markdown.nvim",
dependencies = {
"nvim-treesitter/nvim-treesitter",
"nvim-tree/nvim-web-devicons"
},
opts = {
heading = {
sign = false,
icons = { '󰲡 ', '󰲣 ', '󰲥 ', '󰲧 ', '󰲩 ', '󰲫 ' },
},
code = {
sign = false,
width = "block",
right_pad = 1,
},
},
},
{
"folke/zen-mode.nvim",
config = function()
require("zen-mode").setup({
window = {
width = 80,
options = {
number = false,
relativenumber = false,
}
},
})
-- map leader-z to toggle the writing mode
vim.keymap.set("n", "<leader>z", "<cmd>ZenMode<CR>", { desc = "toggle zen mode" })
end
}
})

View file

@ -0,0 +1,12 @@
{
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
"flexoki": { "branch": "main", "commit": "c3e2251e813d29d885a7cbbe9808a7af234d845d" },
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
"nvim-web-devicons": { "branch": "master", "commit": "7d8dad2c837fcdf2e65997e68657c1a21c568074" },
"render-markdown.nvim": { "branch": "main", "commit": "629eb9533ec989d9d5c6cab8f3ad5372422c24e0" },
"zen-mode.nvim": { "branch": "main", "commit": "8564ce6d29ec7554eb9df578efa882d33b3c23a7" }
}

View file

@ -0,0 +1,9 @@
{
"view-scale": 0.15,
"snap-threshold": 10,
"indicator-timeout": 500,
"custom-mode": [],
"use-desc": false,
"confirm-timeout": 10,
"profile-bound-wallpapers": true
}

10
.config/nwg-look/config Normal file
View file

@ -0,0 +1,10 @@
{
"export-settings-ini": true,
"export-gtkrc-20": true,
"export-index-theme": true,
"export-xsettingsd": true,
"export-gtk4-symlinks": true,
"flatpak-export-gtk-theme-override": false,
"flatpak-export-icon-theme-override": false,
"flatpak-install-current-gtk-theme": false
}

2
.config/qt5ct/qt5ct.conf Normal file
View file

@ -0,0 +1,2 @@
[SettingsWindow]
geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\xff\xff\xff\xfd\xff\xff\xff\xe2\0\0\x3\xb2\0\0\x4\r\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n\0\0\0\0\0\0\0\0\a\x80\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n)

2
.config/qt6ct/qt6ct.conf Normal file
View file

@ -0,0 +1,2 @@
[SettingsWindow]
geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\xff\xff\xff\xfd\xff\xff\xff\xe2\0\0\x3\xb2\0\0\x4\r\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n\0\0\0\0\0\0\0\0\a\x80\0\0\0\0\0\0\0\0\0\0\x3\xaf\0\0\x4\n)

55
.config/starship.toml Normal file
View file

@ -0,0 +1,55 @@
# PROMPT LAYOUT
# define the left and right structural order
format = "$python$directory$git_branch$git_status$hostname$character"
right_format = """$jobs$time"""
# PYTHON ENVIRONMENT
# display the active virtual environment name without the python version
[python]
style = "green"
format = "[$virtualenv]($style) "
# DIRECTORY PATH
# display the truncated current working path
[directory]
style = "cyan"
truncate_to_repo = true
truncation_length = 3
format = "[$path]($style) "
# GIT BRANCH
# display the current repository branch
[git_branch]
format = "[$branch]($style) "
style = "yellow"
# GIT STATUS
# display modification indicators
[git_status]
style = "red"
format = "[$all_status]($style) "
# HOSTNAME
# display the machine name only during ssh sessions
[hostname]
ssh_only = true
format = "[$hostname](blue) "
# PROMPT CHARACTER
# display stateful execution symbols
[character]
success_symbol = "[>](bold green)"
error_symbol = "[x](bold red)"
vicmd_symbol = "[v](bold green)"
# BACKGROUND JOBS
# indicate processes suspended in the background
[jobs]
symbol = "✦ "
# SYSTEM TIME
# display the current time on the right axis
[time]
disabled = false
time_format = "%T"
format = "[$time]($style)"

43
.config/swaylock/config Normal file
View file

@ -0,0 +1,43 @@
# CORE AESTHETICS
# base background colour (flexoki ink dark)
color=100f0f
font=JetBrainsMono Nerd Font
font-size=24
# INDICATOR GEOMETRY
indicator-radius=120
indicator-thickness=12
indicator-idle-visible
# INTERNAL RING COLOURS
inside-color=100f0f
inside-clear-color=100f0f
inside-ver-color=100f0f
inside-wrong-color=100f0f
# EXTERNAL RING COLOURS
# normal state (grey)
ring-color=cecdc3
# clearing state (yellow)
ring-clear-color=d0a215
# verifying state (purple)
ring-ver-color=8b7ec8
# incorrect state (red)
ring-wrong-color=af3029
# KEYPRESS HIGHLIGHTS
# highlight colour when typing (green)
key-hl-color=879a39
# highlight colour when backspacing (yellow)
bshl-color=d0a215
# TEXT COLOURS
text-color=cecdc3
text-clear-color=100f0f
text-ver-color=cecdc3
text-wrong-color=cecdc3
# BEHAVIOUR
# disable the caps lock warning to maintain minimalism
ignore-empty-password
show-failed-attempts

28
.config/tmux/tmux.conf Normal file
View file

@ -0,0 +1,28 @@
# CORE ENVIRONMENT
set -g prefix C-b
bind C-b send-prefix
set -g base-index 1
set -g pane-base-index 1
set -g renumber-windows on
set -g default-terminal "tmux-256color"
set -g mouse on
set -g mode-keys vi
# STATUS BAR
set -g status-position top
set -g status-justify right
set -g status-style bg="#000000",fg="#cecdc3"
set -g window-status-format " #I:#W "
set -g window-status-current-format " #I:#W "
set -g window-status-current-style bg="#282726",fg="#8b7ec8",bold
set -g status-left " [#S] "
set -g status-left-length 40
set -g status-right ""
set -g status-right-length 80

68
.config/waybar/config Normal file
View file

@ -0,0 +1,68 @@
{
"layer": "top",
"position": "top",
"height": 28,
"spacing": 8,
"modules-left": ["niri/workspaces", "niri/window", "mpd"],
"modules-center": ["clock"],
"modules-right": ["custom/bandwidth", "battery", "tray"],
"niri/window": {
"format": "{}",
"rewrite": {
"(.*) — Mozilla Firefox": "Firefox",
"(.*) — LibreWolf": "Librewolf",
"(.*) - Obsidian v(.*)": "Obsidian"
},
"max-length": 50
},
"niri/workspaces": {
"format": "{icon}",
"format-icons": {
"focused": "●",
"default": "○"
}
},
"clock": {
"format": "{:%H:%M | %d}",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>"
},
"mpd": {
"format": "{stateIcon} {artist} - {title}",
"format-disconnected": " offline",
"format-stopped": " stopped",
"state-icons": {
"paused": "",
"playing": ""
},
"max-length": 40,
"tooltip-format": "MPD (connected)",
"tooltip-format-disconnected": "MPD (disconnected)",
"on-click": "mpc toggle",
"on-scroll-up": "mpc next",
"on-scroll-down": "mpc prev",
"on-click-right": "alacritty -e ncmpcpp"
},
"custom/bandwidth": {
"exec": "~/.local/bin/waybar-net",
"return-type": "json",
"interval": 60,
"format": "{}"
},
"battery": {
"states": { "warning": 30, "critical": 15 },
"format": "{icon} {capacity}%",
"format-icons": ["󰂎", "󰁺", "󰁼", "󰁾", "󰂀", "󰂂", "󰁹"]
},
"tray": {
"icon-size": 14,
"spacing": 10
}
}

104
.config/waybar/style.css Normal file
View file

@ -0,0 +1,104 @@
* {
font-family: "JetBrainsMono Nerd Font", "Roboto", sans-serif;
font-size: 13px;
min-height: 0;
border: none;
border-radius: 0;
}
/* BASE WINDOW */
/* edge-to-edge dark translucent bar */
window#waybar {
background-color: rgba(15, 15, 15, 0.85);
color: #cdd6f4;
}
/* WORKSPACES */
#workspaces {
margin-left: 8px;
}
#workspaces button {
padding: 0 8px;
color: #555555;
background: transparent;
transition: all 0.2s ease;
}
/* flexoki yellow for active workspaces */
#workspaces button.focused,
#workspaces button.active {
color: #d0a215;
}
#workspaces button:hover {
box-shadow: inherit;
text-shadow: inherit;
color: #aaaaaa;
}
/* MODULE SPACING */
#clock,
#custom,
#network,
#bluetooth,
#pulseaudio,
#backlight,
#battery,
#tray,
#window {
padding: 0 12px;
opacity: 0.9;
}
/* HARDWARE COLOURS */
/* assigning distinct muted hues to logical blocks */
#clock {
color: #babbf1;
}
#network {
color: #8caaee;
}
#bluetooth {
color: #ca9ee6;
}
#pulseaudio {
color: #81c8be;
}
#backlight {
color: #e5c890;
}
#battery {
color: #a6d189;
}
#battery.charging {
color: #a6e3a1;
}
#battery.critical:not(.charging) {
color: #e78284;
}
/* MPD CONTROLS */
#mpd {
padding: 0 10px;
font-family: monospace;
color: #cdd6f4;
}
/* dim the text when inactive */
#mpd.disconnected,
#mpd.stopped {
color: #6c7086;
}
/* green hue when active */
#mpd.playing {
color: #a6e3a1;
}

20
.config/zathura/zathurarc Normal file
View file

@ -0,0 +1,20 @@
# ZATHURA CONFIGURATION
# disable automatic recolouring on startup
set recolor "false"
# COLOUR MAPPINGS
# preserve image hues when recolouring is activated
set recolor-keephue "true"
# recolour variables applied only when toggled
set recolor-lightcolor "#1e1e2e"
set recolor-darkcolor "#cdd6f4"
# USER INTERFACE
# static dark theme for the application chrome
set default-bg "#1e1e2e"
set default-fg "#cdd6f4"
set statusbar-bg "#1e1e2e"
set statusbar-fg "#cdd6f4"
unmap d

29
.local/bin/README.md Executable file
View file

@ -0,0 +1,29 @@
# UTILS
a collection of personal utility scripts for arch linux system management, automation, and infrastructure reconnaissance.
## DEPENDENCIES
most scripts rely on the following packages available in the arch repositories or aur:
`bash`, `python3`, `jq`, `curl`, `bc`, `awk`, `bind`, `whois`, `openssl`, `aria2`, `yt-dlp`, `ffmpeg`, `fzf`, `lm_sensors`, `pacman-contrib`, `rsync`, `python-watchdog`, `waybar`
## UTILITIES ARCHITECTURE
the repository is structured around singular, verb-oriented tools designed for autonomous infrastructure management:
* `archive`: handles universal file compression and extraction routing.
* `fetch`: executes autonomous media liberation via aria2c and yt-dlp.
* `rate`: provides terminal-native fiat and crypto currency evaluation.
* `recon`: executes domain reconnaissance and infrastructure security auditing.
* `status`: audits arch linux system telemetry and environment health.
* `pick`: provides modular element selection functionality.
* `power`: handles system state and operational power management.
* `scratch`: controls the window manager scratchpad buffer.
* `audit-pkg`: scans local package environments for vulnerabilities.
* `audit-users`: maps system permissions and active user accounts.
* `backup-obsd`: automates synchronisation for local knowledge vaults.
* `obsd`: launches the local knowledge base environment.
* `adbx`: wraps android debug bridge operations.
* `fuzzel-run`: configures the autonomous application launcher.
* `waybar-net`: feeds network state data to the status bar.

120
.local/bin/adbx Executable file
View file

@ -0,0 +1,120 @@
#!/usr/bin/env bash
# COLOURS
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[1;33m'
blue='\033[0;34m'
cyan='\033[0;36m'
nc='\033[0m'
# LOGGING
log_info() { printf "${blue}[info]${nc} %s\n" "$1"; }
log_success() { printf "${green}[ok]${nc} %s\n" "$1"; }
log_warn() { printf "${yellow}[warn]${nc} %s\n" "$1"; }
log_err() { printf "${red}[err]${nc} %s\n" "$1"; }
# ENVIRONMENT
check_deps() {
local tools=(adb fastboot apktool uber-apk-signer fzf)
for t in "${tools[@]}"; do
if ! command -v "$t" &>/dev/null; then
log_warn "$t missing"
fi
done
}
# DEVICE MANAGEMENT
cmd_info() {
printf "${cyan}DEVICE INFORMATION${nc}\n"
printf "model: %s\n" "$(adb shell getprop ro.product.model)"
printf "android: %s\n" "$(adb shell getprop ro.build.version.release)"
local bat=$(adb shell dumpsys battery | grep level | cut -d: -f2 | tr -d ' ')
local stat=$(adb shell dumpsys battery | grep 'AC powered' | cut -d: -f2 | tr -d ' ')
[[ "$stat" == "true" ]] && charge="charging" || charge="discharging"
printf "battery: %s%% (%s)\n" "$bat" "$charge"
if adb shell su -c "echo 'rooted'" &>/dev/null; then
printf "root: ${green}yes${nc}\n"
else
printf "root: ${red}no${nc}\n"
fi
}
cmd_wifi() {
local port=${1:-5555}
local ip=$(adb shell ip addr show wlan0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
if [ -z "$ip" ]; then
log_err "wifi ip not detected"
return 1
fi
adb tcpip $port
sleep 2
adb connect "$ip:$port"
log_success "connected to $ip:$port"
}
# PACKAGE MANAGEMENT
cmd_nuke() {
local pkg=$(adb shell pm list packages | cut -d: -f2 | fzf --prompt="nuke > " --height=40%)
if [ -n "$pkg" ]; then
read -p "confirm nuke $pkg? (y/n): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
adb shell pm uninstall -k --user 0 "$pkg"
log_success "nuked $pkg"
fi
fi
}
# APK ENGINEERING
cmd_pull() {
local pkg=${1:-$(adb shell pm list packages | cut -d: -f2 | fzf --prompt="pull > ")}
[ -z "$pkg" ] && return
local path=$(adb shell pm path "$pkg" | head -n 1 | cut -d: -f2 | tr -d '\r')
log_info "pulling $pkg..."
adb pull "$path" "./$pkg.apk"
}
cmd_scrot() {
local file="scrot_$(date +%Y%m%d_%H%M%S).png"
adb shell screencap -p /sdcard/temp.png
adb pull /sdcard/temp.png "./$file" >/dev/null
adb shell rm /sdcard/temp.png
log_success "saved: $file"
}
# MAIN ROUTER
usage() {
printf "${cyan}ARCH-DROID TOOL (ADBX)${nc}\n"
printf "usage: adbx <command> [args]\n\n"
printf "DEVICE\n"
printf " check verify tools\n"
printf " list list devices\n"
printf " info show battery/root\n"
printf " wifi wireless adb\n\n"
printf "PACKAGES\n"
printf " nuke interactive uninstall\n"
printf " freeze disable app\n"
printf " melt enable app\n\n"
printf "FILES\n"
printf " pull extract apk\n"
printf " sign sign apk\n"
printf " scrot screenshot\n"
exit 1
}
case "$1" in
check) check_deps ;;
list)
adb devices -l
fastboot devices
;;
info) cmd_info ;;
wifi) cmd_wifi "$2" ;;
nuke) cmd_nuke ;;
pull) cmd_pull "$2" ;;
scrot) cmd_scrot ;;
*) usage ;;
esac

117
.local/bin/archive Executable file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env bash
set -e
# COLOURS
red='\033[1;31m'
green='\033[1;32m'
blue='\033[1;34m'
yellow='\033[1;33m'
nc='\033[0m'
# USAGE GUIDE
# displays the help menu if arguments are missing or incorrect
show_help() {
printf "${blue}usage:${nc} archivectl <command> <arguments>\n\n"
printf "commands:\n"
printf " compress <source> [output] compresses a file or directory\n"
printf " extract <file> extracts a supported archive\n\n"
printf "examples:\n"
printf " archivectl compress myfolder\n"
printf " archivectl compress myfolder data.zip\n"
printf " archivectl extract data.tar.gz\n"
exit 1
}
# COMPRESSION ROUTINE
# handles the creation of new archives
do_compress() {
if [ -z "$1" ]; then
printf "${red}error: target missing for compression.${nc}\n"
exit 1
fi
local source_path="${1%/}"
if [ ! -e "$source_path" ]; then
printf "${red}error: source '%s' not found.${nc}\n" "$source_path"
exit 1
fi
local output
if [ -n "$2" ]; then
output="$2"
else
output="${source_path}.tar.gz"
fi
printf "${blue}compressing ${yellow}%s${nc} into ${yellow}%s${nc}...\n" "$source_path" "$output"
case "$output" in
*.tar.gz | *.tgz) tar -czf "$output" "$source_path" ;;
*.tar.xz) tar -cJf "$output" "$source_path" ;;
*.tar.bz2) tar -cjf "$output" "$source_path" ;;
*.tar) tar -cf "$output" "$source_path" ;;
*.zip) zip -r "$output" "$source_path" ;;
*.7z) 7z a "$output" "$source_path" ;;
*.rar) rar a "$output" "$source_path" ;;
*)
printf "${red}error: format not recognised.${nc}\n"
exit 1
;;
esac
printf "${green}compression complete.${nc}\n"
du -h "$output" | awk '{print $1 " " $2}'
}
# EXTRACTION ROUTINE
# handles the unpacking of existing archives
do_extract() {
if [ -z "$1" ]; then
printf "${red}error: target missing for extraction.${nc}\n"
exit 1
fi
local file="$1"
if [ ! -f "$file" ]; then
printf "${red}error: file '%s' not found.${nc}\n" "$file"
exit 1
fi
printf "${blue}extracting: ${yellow}%s${nc}...\n" "$file"
case "$file" in
*.tar.bz2 | *.tbz2) tar xjf "$file" ;;
*.tar.gz | *.tgz) tar xzf "$file" ;;
*.tar.xz) tar xJf "$file" ;;
*.tar) tar xf "$file" ;;
*.lzma) unlzma "$file" ;;
*.bz2) bunzip2 "$file" ;;
*.rar) unrar x "$file" ;;
*.gz) gunzip "$file" ;;
*.zip) unzip "$file" ;;
*.Z) uncompress "$file" ;;
*.7z) 7z x "$file" ;;
*.xz) unxz "$file" ;;
*.exe) cabextract "$file" ;;
*)
printf "${red}error: format not recognised.${nc}\n"
exit 1
;;
esac
printf "${green}extraction complete.${nc}\n"
}
# COMMAND ROUTER
# parses the first argument to determine execution path
case "$1" in
compress)
do_compress "$2" "$3"
;;
extract)
do_extract "$2"
;;
*)
show_help
;;
esac

144
.local/bin/audit-pkg Executable file
View file

@ -0,0 +1,144 @@
#!/usr/bin/env bash
# GUARD
if ! command -v pacman &>/dev/null; then
echo "pacman not found. this script is for arch linux." >&2
exit 1
fi
# COLOURS
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[1;33m'
blue='\033[0;34m'
cyan='\033[0;36m'
bold='\033[1m'
dim='\033[2m'
nc='\033[0m'
# COUNTERS
_covered=0
_partial=0
_missing=0
# HELPERS
is_installed() {
pacman -Qq "$1" &>/dev/null
}
print_section() {
printf "\n${blue}${bold}%s${nc}\n" "$1"
printf "${dim}--------------------------------------------------${nc}\n"
}
check_any() {
local category="$1"
local description="$2"
shift 2
local -a found=()
for pkg in "$@"; do
is_installed "$pkg" && found+=("$pkg")
done
if [[ ${#found[@]} -gt 0 ]]; then
printf " ${green}[ ok ]${nc} ${bold}%-35s${nc} ${dim}%s${nc}\n" "$category" "${found[0]}"
_covered=$((_covered + 1))
else
printf " ${red}[ !! ]${nc} ${bold}%-35s${nc}\n" "$category"
printf " ${dim}%s${nc}\n" "$description"
_missing=$((_missing + 1))
fi
}
check_all() {
local category="$1"
local description="$2"
shift 2
local -a found=()
local -a absent=()
for pkg in "$@"; do
if is_installed "$pkg"; then found+=("$pkg"); else absent+=("$pkg"); fi
done
if [[ ${#absent[@]} -eq 0 ]]; then
printf " ${green}[ ok ]${nc} ${bold}%-35s${nc}\n" "$category"
_covered=$((_covered + 1))
elif [[ ${#found[@]} -gt 0 ]]; then
printf " ${yellow}[ / ]${nc} ${bold}%-35s${nc}\n" "$category"
printf " ${dim}missing: %s${nc}\n" "${absent[*]}"
_partial=$((_partial + 1))
else
printf " ${red}[ !! ]${nc} ${bold}%-35s${nc}\n" "$category"
printf " ${dim}need all of: %s${nc}\n" "$*"
_missing=$((_missing + 1))
fi
}
# HEADER
clear
printf "${cyan}${bold}ARCH LINUX + HYPRLAND AUDIT${nc}\n"
printf "${dim}checking package categories in local database${nc}\n\n"
# CORE SYSTEM
print_section "CORE SYSTEM"
check_all "base system" "essential os foundation" base base-devel
check_any "kernel" "linux kernel flavour" linux linux-lts linux-zen linux-hardened
check_any "ucode" "processor microcode patches" intel-ucode amd-ucode
check_any "bootloader" "system startup manager" grub refind
check_any "filesystems" "fat/ntfs/ext4 utilities" dosfstools ntfs-3g e2fsprogs
# DISPLAY AND COMPOSITOR
print_section "DISPLAY AND COMPOSITOR"
check_any "compositor" "hyprland or alternatives" hyprland sway niri
check_any "session manager" "uwsm or greetd" uwsm ly greetd
check_all "xdg portals" "screen sharing and file pickers" xdg-desktop-portal-hyprland xdg-desktop-portal-gtk
check_any "gpu drivers" "mesa or nvidia proprietary" mesa nvidia nvidia-open
# DESKTOP COMPONENTS
print_section "DESKTOP COMPONENTS"
check_any "status bar" "waybar or eww" waybar eww ags
check_any "launcher" "app and command fuzzy search" rofi-wayland wofi fuzzel
check_any "notifications" "notification daemon" mako swaync dunst
check_any "locker" "screen locking utility" hyprlock swaylock
check_any "wallpaper" "wayland native wallpaper setter" hyprpaper swww
check_all "clipboard" "manager and history stack" wl-clipboard cliphist
# AUDIO AND NETWORK
print_section "AUDIO AND NETWORK"
check_all "pipewire" "full audio stack" pipewire pipewire-pulse pipewire-alsa wireplumber
check_any "network" "networkmanager or iwd" networkmanager iwd
check_any "firewall" "traffic control" ufw nftables
# SHELL AND EDITORS
print_section "SHELL AND EDITORS"
check_any "terminal" "wayland native terminal" kitty alacritty foot ghostty
check_any "shell" "bash or zsh" bash zsh fish
check_any "prompt" "shell prompt customiser" starship oh-my-posh
check_any "editor" "modal or simple text editor" nvim vim micro nano
# INPUT AND POWER
print_section "INPUT AND POWER"
check_any "brightness" "backlight control" brightnessctl light
check_any "power" "power management daemon" tlp auto-cpufreq
check_all "bluetooth" "bluetooth stack" bluez bluez-utils
check_any "bt-manager" "bluetooth frontend" blueman bluetui
# THEMING AND FONTS
print_section "THEMING AND FONTS"
check_any "nerd-font" "glyphs for terminal icons" ttf-jetbrains-mono-nerd ttf-hack-nerd
check_any "emoji" "emoji rendering support" noto-fonts-emoji
check_any "gtk-theme" "gtk appearance manager" nwg-look lxappearance
# SECURITY AND SECRETS
print_section "SECURITY AND SECRETS"
check_any "polkit" "auth agent for elevated tasks" polkit-kde-agent lxqt-policykit
check_any "keyring" "password and secret storage" gnome-keyring
# SUMMARY
_total=$((_covered + _partial + _missing))
printf "\n${blue}${bold}SUMMARY${nc}\n"
printf "${dim}--------------------------------------------------${nc}\n"
printf " ${green}ok: %d${nc}\n" "$_covered"
printf " ${yellow}partial: %d${nc}\n" "$_partial"
printf " ${red}missing: %d${nc}\n" "$_missing"
printf " total: %d\n\n" "$_total"

22
.local/bin/audit-users Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
# COLOURS
cyan='\033[0;36m'
nc='\033[0m'
# USER AUDIT
printf "${cyan}AUTHORISED HUMAN USERS${nc}\n"
printf "%-12s %-6s %-15s %-30s %s\n" "user" "uid" "shell" "groups" "last login"
# filter for uids between 1000 and 60000
awk -F: '$3 >= 1000 && $3 < 60000 {print $1}' /etc/passwd | while read -r user; do
uid=$(id -u "$user")
groups=$(groups "$user" | cut -d: -f2 | xargs)
shell=$(getent passwd "$user" | cut -d: -f7)
# parse lastlog for the second line and strip the username to get the timestamp
last_raw=$(lastlog -u "$user" | awk 'NR==2 { $1=""; print $0 }' | xargs)
printf "%-12s %-6s %-15s %-30s %s\n" \
"$user" "$uid" "$shell" "$groups" "${last_raw:-never logged in}"
done

20
.local/bin/backup-obsd Executable file
View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-07-28 16:26 +0100
set -euo pipefail
echo "creating tarball"
echo ""
tar --zstd -cf - -C ~ obsidian | age -r age1y5fnkaugdmqgegurgq6jdqrm5lz3ehyp9ve7rhf69pmr9xy9veyqtnappe -o ~/vault-$(date "+%FT%T").tar.zst.age
echo ""
echo "pushing tarball to filen"
echo ""
filen upload ~/vault-*.tar.zst.age /obsidian
echo ""
echo "deleting tarbal"
rm -rf ~/vault-*.tar.zst.age
echo ""
echo "obsidian vault backup complete"
echo ""
filen ls -l /obsidian

13
.local/bin/brightness-notify Executable file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-04 05:02 +0100
set -euo pipefail
# ADJUST BACKLIGHT
brightnessctl set "$@" > /dev/null
# FETCH CURRENT PERCENTAGE
brightness=$(brightnessctl -m | awk -F, '{print $4}' | tr -d '%')
notify-send -h string:x-canonical-private-synchronous:brightness -u low "Brightness: ${brightness}%"

10
.local/bin/bw Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
SESSION=$(secret-tool lookup service bitwarden 2>/dev/null)
if [[ -z "$SESSION" ]]; then
SESSION=$(bw unlock --raw)
secret-tool store --label="bw-session" service bitwarden <<< "$SESSION"
fi
exec /usr/bin/bw "$@" --session "$SESSION"

250
.local/bin/fetch Executable file
View file

@ -0,0 +1,250 @@
#!/usr/bin/env bash
# COLOURS
red='\033[1;31m'
green='\033[1;32m'
blue='\033[1;34m'
cyan='\033[1;36m'
yellow='\033[1;33m'
nc='\033[0m'
# CONFIGURATION
session_file="$HOME/.aria2_session"
xdg_videos="${XDG_VIDEOS_DIR:-$HOME/Videos}"
xdg_music="${XDG_MUSIC_DIR:-$HOME/Music}"
hist_video="$HOME/.config/archive_history_video.txt"
hist_music="$HOME/.config/archive_history_music.txt"
# establish configuration directory
mkdir -p "$HOME/.config"
touch "$session_file"
# UTILITIES
log() {
local col="$1"
local msg="$2"
printf "${col}%s${nc}\n" "$msg"
}
# verify core binaries and clipboard providers
check_dependencies() {
local deps=("aria2c" "yt-dlp" "spotdl")
for dep in "${deps[@]}"; do
if ! command -v "$dep" >/dev/null 2>&1; then
log "$red" "missing core dependency: $dep"
exit 1
fi
done
if ! command -v wl-paste >/dev/null 2>&1 && ! command -v xclip >/dev/null 2>&1; then
log "$yellow" "warning: missing wl-clipboard or xclip. clipboard polling disabled."
fi
}
# extract link from clipboard
get_clipboard_url() {
local content=""
if command -v wl-paste >/dev/null 2>&1; then
content=$(wl-paste)
elif command -v xclip >/dev/null 2>&1; then
content=$(xclip -o -selection clipboard)
fi
content="$(echo "$content" | xargs)"
[[ "$content" == http* ]] && echo "$content"
}
# ARIA2C CORE
run_aria2c() {
aria2c \
--continue=true \
--max-connection-per-server=16 \
--split=16 \
--min-split-size=1M \
--user-agent="Mozilla/5.0" \
--save-session="$session_file" \
--save-session-interval=60 \
--seed-time=0 \
"$@" || log "$red" "aria2c failed"
}
# SPOTIFY CORE
run_spotdl() {
local url="$1"
local target_dir="$2"
log "$cyan" "spotify routing to $target_dir"
mkdir -p "$target_dir"
cd "$target_dir" || return 1
local cmd=(
spotdl download "$url"
--format opus
--output "{artists} - {title}.{ext}"
)
local attempt
for attempt in 1 2 3; do
log "$cyan" "spotdl execution attempt $attempt"
if timeout 300 "${cmd[@]}"; then
return 0
fi
log "$yellow" "spotdl network or api failure on attempt $attempt"
sleep 2
done
log "$red" "spotdl failed permanently due to api restrictions"
return 1
}
# YT-DLP CORE
run_ytdlp() {
local url="$1"
local target_dir="$2"
local hist_file="$3"
local out_tmpl="$4"
shift 4
local extra_args=("$@")
log "$cyan" "media routing to $target_dir"
mkdir -p "$target_dir"
yt-dlp \
--progress \
--ignore-errors \
--add-metadata \
--embed-thumbnail \
--embed-subs \
--embed-chapters \
--downloader aria2c \
--downloader-args "aria2c:-x 4 -k 1M" \
--download-archive "$hist_file" \
-o "${target_dir}/${out_tmpl}" \
"${extra_args[@]}" \
"$url" \
|| log "$yellow" "yt-dlp finished with errors"
}
# MODES
mode_media() {
local url="$1"
local media_type="$2"
local format_arg="$3"
local playlist_flag="$4"
local audio_tmpl="%(playlist_index|)s%(playlist_index& - |)s%(artist,uploader|Unknown)s - %(title)s [%(id)s].%(ext)s"
local video_tmpl="%(playlist_index|)s%(playlist_index& - |)s%(uploader,channel|Unknown)s - %(title)s [%(id)s].%(ext)s"
local extra_args=()
[[ -n "$playlist_flag" ]] && extra_args+=("$playlist_flag")
if [[ "$media_type" == "audio" ]]; then
run_ytdlp "$url" "$xdg_music" "$hist_music" "$audio_tmpl" \
"${extra_args[@]}" \
--extract-audio --audio-format opus --audio-quality 0
else
if [[ "$format_arg" == "best" ]]; then
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
"${extra_args[@]}" -f "bv*+ba/b" --merge-output-format mkv
elif [[ "$format_arg" == "ask" ]]; then
log "$cyan" "querying upstream for format manifests"
yt-dlp -F "$url"
printf "\n${cyan}enter desired format code:${nc} "
read -r selected_format
if [[ -n "$selected_format" ]]; then
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
"${extra_args[@]}" -f "$selected_format" --merge-output-format mkv
else
log "$red" "aborted: no format code provided"
exit 1
fi
else
run_ytdlp "$url" "$xdg_videos" "$hist_video" "$video_tmpl" \
"${extra_args[@]}" -f "$format_arg" --merge-output-format mkv
fi
fi
}
# BATCH MODE
mode_batch() {
local filepath="$1"
[[ ! -f "$filepath" ]] && {
log "$red" "batch file missing: $filepath"
exit 1
}
log "$blue" "batch mode execution for $filepath"
local tmp
tmp=$(mktemp)
# strip comments and empty lines
grep -vE '^\s*#|^\s*$' "$filepath" > "$tmp"
run_aria2c --input-file="$tmp" --max-concurrent-downloads=4
rm -f "$tmp"
}
# ARG PARSING
target=""
media_type="video"
format_arg="best"
playlist_flag=""
resume=0
while [[ $# -gt 0 ]]; do
case "$1" in
-t|--type)
media_type="$2"; shift 2 ;;
-f|--format)
format_arg="$2"; shift 2 ;;
-p|--playlist)
playlist_flag="--yes-playlist"; shift ;;
-r|--resume)
resume=1; shift ;;
*)
[[ -z "$target" ]] && target="$1"
shift ;;
esac
done
# ROUTER
check_dependencies
[[ -z "$target" ]] && target=$(get_clipboard_url)
if [[ -z "$target" && $resume -eq 0 ]]; then
log "$yellow" "usage: fetch [url|file] [-t audio|video] [-f best|ask|code] [-p]"
exit 1
fi
if [[ $resume -eq 1 ]]; then
log "$yellow" "resuming global aria2 session"
run_aria2c --input-file="$session_file"
elif [[ -f "$target" ]]; then
mode_batch "$target"
elif [[ "$target" =~ \.(iso|zip|rar|tar|gz|bz2|7z|torrent|pdf|png|jpg|mp4|mkv)$ ]]; then
log "$blue" "direct file payload detected"
run_aria2c "$target"
elif [[ "$target" =~ spotify\.com ]]; then
run_spotdl "$target" "$xdg_music" || {
log "$red" "spotdl failed. yt-dlp cannot natively parse walled-garden spotify urls. manual extraction required."
exit 1
}
else
mode_media "$target" "$media_type" "$format_arg" "$playlist_flag"
fi

7
.local/bin/fuzzel-run Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Generate a list of all commands, remove duplicates, and pipe to Fuzzel
selected=$(compgen -c | sort -u | fuzzel -d --prompt="RUN: ")
# Execute the selected binary through the compositor
[ -n "$selected" ] && swaymsg exec -- "$selected"

669
.local/bin/hledger-charts Executable file
View file

@ -0,0 +1,669 @@
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: 2026-07-12 09:40 +0100
# UPDATED: 2026-07-27 10:53 +0100
"""
hledger-charts
terminal and image charts for hledger csv output. reads csv from standard
input, piped directly from hledger, and renders an ascii chart to the
terminal, a matplotlib png, or both.
ARCH DIFFERENCE
matplotlib is only required for --png output, ascii charts use the standard
library alone. install matplotlib via pacman rather than pip where possible:
pacman -S python-matplotlib
pip installs into an externally managed environment fail under PEP 668
unless a virtualenv is used or --break-system-packages is passed.
INPUT
pipe hledger csv output in directly, or pass --journal so the script
calls hledger itself. pass the -O csv flag when piping directly from
hledger. the --format flag specifies a custom line format string and
produces incompatible text:
hledger balance -O csv | hledger-charts pie --png pie.png
hledger-charts pie --journal .journal/2026.journal --png pie.png
histogram and pie read a single period balance report. trend, stacked, and
grouped read a multi period balance report (hledger balance -M), which the
script requests automatically when --journal is given. pass an account pattern
or date range as trailing query arguments to focus the chart. all trailing
arguments and flags after the chart options are passed straight through to
hledger balance:
hledger-charts stacked --journal .journal/2026.journal expenses
hledger-charts trend --journal .journal/2026.journal expenses -b 2026-01 -e 2026-04
"""
import argparse
import csv
import io
import os
import re
import subprocess
import sys
SPARK_CHARS = "▁▂▃▄▅▆▇█"
ANSI_COLOURS = [31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
# AMOUNT PARSING
# shared by both single period and multi period csv shapes
def clean_amount(raw, invert=False):
"""EXTRACT THE FIRST NUMERIC VALUE FROM CURRENCY AND FORMATTED STRINGS
accepts formats such as "1,200.00 NGN", "NGN -1,200.00", "- 450.00"
"""
cleaned = raw.replace(",", "")
cleaned = re.sub(r"([-+])\s+", r"\1", cleaned)
match = re.search(r"[-+]?\d+(?:\.\d+)?", cleaned)
if not match:
return 0.0
val = float(match.group(0))
return -val if invert else val
# ACCOUNT HIERARCHY
def rollup_account(account, depth):
"""TRUNCATE ACCOUNT NAME TO A SPECIFIED HIERARCHY DEPTH"""
if not depth or depth <= 0:
return account
parts = account.split(":")
return ":".join(parts[:depth])
def aggregate_single_period(accounts, amounts, depth):
"""GROUP SINGLE PERIOD AMOUNTS BY TRUNCATED ACCOUNT NAME"""
if not depth or depth <= 0:
return accounts, amounts
grouped = {}
for acc, amt in zip(accounts, amounts):
parent = rollup_account(acc, depth)
grouped[parent] = grouped.get(parent, 0.0) + amt
return list(grouped.keys()), list(grouped.values())
def aggregate_multi_period(accounts, periods, matrix, depth):
"""GROUP MULTI PERIOD MATRICES BY TRUNCATED ACCOUNT NAME"""
if not depth or depth <= 0:
return accounts, periods, matrix
grouped = {}
n_periods = len(periods)
for i, acc in enumerate(accounts):
parent = rollup_account(acc, depth)
if parent not in grouped:
grouped[parent] = [0.0] * n_periods
for j in range(n_periods):
grouped[parent][j] += matrix[i][j]
return list(grouped.keys()), periods, list(grouped.values())
# CSV PARSING
def parse_balance_csv(rows, invert=False):
"""PARSE A SINGLE PERIOD BALANCE REPORT
expects rows shaped like [account, amount], skips headers and totals
"""
accounts = []
amounts = []
for row in rows:
if len(row) < 2:
continue
account = row[0].strip()
if account.lower().rstrip(" :") in ("account", "total", ""):
continue
try:
amount = clean_amount(row[1], invert=invert)
except ValueError:
continue
accounts.append(account)
amounts.append(amount)
return accounts, amounts
def parse_period_csv(rows, invert=False):
"""PARSE A MULTI PERIOD BALANCE REPORT
expects a header of account,period1,... and drops any trailing total column
"""
if not rows:
return [], [], []
header = [c.strip() for c in rows[0]]
if len(header) < 2:
raise ValueError(
"period csv needs an account column and at least one value column"
)
periods = header[1:]
if len(periods) > 1 and periods[-1].lower().rstrip(" :") == "total":
periods = periods[:-1]
accounts = []
matrix = []
for row in rows[1:]:
if len(row) < 2:
continue
account = row[0].strip()
if account.lower().rstrip(" :") in ("account", "total", ""):
continue
values = row[1:1 + len(periods)]
try:
parsed = [clean_amount(v, invert=invert) for v in values]
except ValueError:
continue
accounts.append(account)
matrix.append(parsed)
return accounts, periods, matrix
# DATA LOADING
def load_csv_rows(args, period):
"""LOAD BALANCE REPORT ROWS WITH TTY AND PIPELINE VALIDATION
calls hledger directly when --journal is given, otherwise reads csv from stdin
"""
if not args.journal:
if sys.stdin.isatty():
print("error: no input piped to stdin and --journal not specified", file=sys.stderr)
sys.exit(1)
if args.query:
print("warning: query arguments are ignored when reading pre-computed csv from stdin", file=sys.stderr)
return list(csv.reader(sys.stdin))
cmd = ["hledger", "-f", args.journal, "balance", "-O", "csv"]
if period:
cmd.append("-M")
cmd.extend(args.query)
try:
result = subprocess.run(cmd, capture_output=True, text=True)
except FileNotFoundError:
print("error: hledger not found on PATH", file=sys.stderr)
sys.exit(1)
if result.returncode != 0:
print(f"error: hledger exited with status {result.returncode}", file=sys.stderr)
if result.stderr:
print(result.stderr.strip(), file=sys.stderr)
sys.exit(1)
return list(csv.reader(io.StringIO(result.stdout)))
# RANKING HELPERS
# keep charts readable when the account list is long
def top_n_with_other(labels, values, n):
"""COLLAPSE LOW RANKED ENTRIES INTO A SINGLE OTHER CATEGORY"""
paired = sorted(zip(labels, values), key=lambda p: abs(p[1]), reverse=True)
if n <= 0 or len(paired) <= n:
return [p[0] for p in paired], [p[1] for p in paired]
top = paired[:n]
rest = paired[n:]
other_total = sum(v for _, v in rest)
out_labels = [p[0] for p in top]
out_values = [p[1] for p in top]
if other_total != 0:
out_labels.append("other")
out_values.append(other_total)
return out_labels, out_values
def top_n_matrix_with_other(accounts, matrix, n):
"""COLLAPSE LOW RANKED ACCOUNTS INTO AN OTHER ROW ACROSS ALL PERIODS"""
totals = [sum(abs(v) for v in row) for row in matrix]
order = sorted(range(len(accounts)), key=lambda i: totals[i], reverse=True)
if n <= 0 or len(order) <= n:
idx = order
return [accounts[i] for i in idx], [matrix[i] for i in idx]
top_idx = order[:n]
rest_idx = order[n:]
top_accounts = [accounts[i] for i in top_idx]
top_matrix = [matrix[i] for i in top_idx]
n_periods = len(matrix[0]) if matrix else 0
other_row = [sum(matrix[i][j] for i in rest_idx) for j in range(n_periods)]
if any(other_row):
top_accounts.append("other")
top_matrix.append(other_row)
return top_accounts, top_matrix
# ASCII RENDERING PRIMITIVES
def use_colour(args):
if args.no_colour:
return False
if os.environ.get("NO_COLOR"):
return False
return sys.stdout.isatty()
def sparkline(values):
"""RENDER A COMPACT UNICODE SPARKLINE FOR ONE SERIES"""
if not values:
return ""
lo = min(values)
hi = max(values)
span = hi - lo
if span == 0:
return SPARK_CHARS[0] * len(values)
out = []
for v in values:
idx = int(((v - lo) / span) * (len(SPARK_CHARS) - 1))
out.append(SPARK_CHARS[idx])
return "".join(out)
def multiline_sparkline(values, rows=2):
"""RENDER A MULTI-LINE ASCII SPARKLINE CANVAS"""
if not values or rows <= 1:
return [sparkline(values)]
lo = min(values)
hi = max(values)
span = hi - lo if hi != lo else 1.0
canvas = [[" " for _ in values] for _ in range(rows)]
for col, v in enumerate(values):
norm = (v - lo) / span
scaled = norm * rows
full_blocks = int(scaled)
remainder = scaled - full_blocks
for r in range(full_blocks):
if r < rows:
canvas[rows - 1 - r][col] = "█"
if full_blocks < rows:
idx = int(remainder * len(SPARK_CHARS))
if idx > 0 or full_blocks == 0:
canvas[rows - 1 - full_blocks][col] = SPARK_CHARS[min(idx, len(SPARK_CHARS) - 1)]
return ["".join(row) for row in canvas]
def segment_glyph(i, colour):
"""PICK A GLYPH FOR SEGMENT I
coloured terminals reuse a plain block since colour carries the
distinction, plain terminals fall back to a letter per segment so
adjacent segments of the same rendered width stay distinguishable
"""
if colour:
return "█"
return chr(ord("A") + (i % 26))
def render_stacked_bar(labels, values, width, colour):
"""RENDER ONE PROPORTIONAL STACKED BAR ACROSS THE GIVEN WIDTH"""
abs_values = [abs(v) for v in values]
total = sum(abs_values)
if total <= 0:
return " " * width
segments = []
allocated = 0
for i, v in enumerate(abs_values):
seg_width = int(round((v / total) * width))
if i == len(abs_values) - 1:
seg_width = max(width - allocated, 0)
allocated += seg_width
glyph = segment_glyph(i, colour)
char = glyph * seg_width
if colour:
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
char = f"\033[{code}m{char}\033[0m"
segments.append(char)
return "".join(segments)
def render_legend(labels, values, colour):
"""RENDER A LABEL AND PERCENTAGE LEGEND"""
abs_values = [abs(v) for v in values]
total = sum(abs_values)
max_label = max((len(l) for l in labels), default=0)
lines = []
for i, (label, value) in enumerate(zip(labels, values)):
pct = (abs(value) / total * 100) if total else 0.0
glyph = segment_glyph(i, colour)
swatch = glyph
if colour:
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
swatch = f"\033[{code}m{glyph}\033[0m"
lines.append(f"{swatch} {label:<{max_label}} {value:>14.2f} {pct:>5.1f}%")
return "\n".join(lines)
# MATPLOTLIB HELPERS
# imported lazily, ascii output never needs this dependency
def _matplotlib_pyplot(theme="default"):
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
if theme and theme != "default":
try:
plt.style.use(theme)
except OSError:
print(f"warning: matplotlib theme '{theme}' not found, falling back to default", file=sys.stderr)
return plt
except ImportError:
print(
"error: matplotlib not installed, install with "
"'pacman -S python-matplotlib' or "
"'pip install matplotlib --break-system-packages'",
file=sys.stderr,
)
sys.exit(1)
def render_histogram_png(accounts, amounts, path, currency, theme):
plt = _matplotlib_pyplot(theme)
data = sorted(zip(accounts, amounts), key=lambda p: abs(p[1]), reverse=True)
labels = [d[0] for d in data]
values = [d[1] for d in data]
fig, ax = plt.subplots(figsize=(10, max(4, len(labels) * 0.4)))
ax.barh(labels, values, color="#4C72B0")
ax.invert_yaxis()
ax.set_xlabel(f"amount ({currency})" if currency else "amount")
ax.set_title("account balances")
fig.tight_layout()
fig.savefig(path, dpi=150)
fig.clf()
plt.close(fig)
print(f"saved histogram to {path}", file=sys.stderr)
# MATPLOTLIB HELPERS
def render_pie_png(labels, values, path, currency, theme):
plt = _matplotlib_pyplot(theme)
fig, ax = plt.subplots(figsize=(10, 7))
abs_values = [abs(v) for v in values]
total = sum(abs_values)
# format legend labels with percentages
legend_labels = [
f"{label} ({(abs(val) / total * 100):.1f}%)" if total else label
for label, val in zip(labels, values)
]
# suppress autopct on small slices to prevent overlap
def autopct_fmt(pct):
return f"{pct:.1f}%" if pct >= 3.0 else ""
wedges, _, autotexts = ax.pie(
abs_values,
autopct=autopct_fmt,
startangle=90,
pctdistance=0.75,
)
# adjust text contrast based on slice luminance
for wedge, autotext in zip(wedges, autotexts):
if autotext.get_text():
r, g, b = wedge.get_facecolor()[:3]
luminance = 0.299 * r + 0.587 * g + 0.114 * b
autotext.set_color("black" if luminance > 0.6 else "white")
autotext.set_fontsize(9)
autotext.set_weight("bold")
ax.legend(
wedges,
legend_labels,
title="accounts",
loc="center left",
bbox_to_anchor=(1, 0, 0.5, 1),
frameon=False,
)
ax.set_title(
f"account distribution ({currency})"
if currency
else "account distribution"
)
ax.axis("equal")
fig.tight_layout()
fig.savefig(path, dpi=150, bbox_inches="tight")
fig.clf()
plt.close(fig)
print(f"saved pie chart to {path}", file=sys.stderr)
def render_trend_png(accounts, periods, matrix, path, currency, theme):
plt = _matplotlib_pyplot(theme)
fig, ax = plt.subplots(figsize=(10, 6))
for i, account in enumerate(accounts):
ax.plot(periods, matrix[i], marker="o", label=account)
ax.set_xlabel("period")
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
ax.set_title("balance trend")
ax.legend()
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
fig.tight_layout()
fig.savefig(path, dpi=150)
fig.clf()
plt.close(fig)
print(f"saved trend chart to {path}", file=sys.stderr)
def render_stacked_png(accounts, periods, matrix, path, currency, theme):
plt = _matplotlib_pyplot(theme)
fig, ax = plt.subplots(figsize=(10, 6))
bottom = [0.0] * len(periods)
for i, account in enumerate(accounts):
values = [abs(v) for v in matrix[i]]
ax.bar(periods, values, bottom=bottom, label=account)
bottom = [b + v for b, v in zip(bottom, values)]
ax.set_xlabel("period")
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
ax.set_title("spending by category per period")
ax.legend()
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
fig.tight_layout()
fig.savefig(path, dpi=150)
fig.clf()
plt.close(fig)
print(f"saved stacked chart to {path}", file=sys.stderr)
def render_grouped_png(accounts, periods, matrix, path, currency, theme):
plt = _matplotlib_pyplot(theme)
x = list(range(len(periods)))
n = len(accounts)
width = 0.8 / max(n, 1)
fig, ax = plt.subplots(figsize=(10, 6))
for i, account in enumerate(accounts):
values = [abs(v) for v in matrix[i]]
offset = (i - (n - 1) / 2) * width
positions = [xi + offset for xi in x]
ax.bar(positions, values, width=width, label=account)
ax.set_xticks(x)
ax.set_xticklabels(periods, rotation=45, ha="right")
ax.set_xlabel("period")
ax.set_ylabel(f"amount ({currency})" if currency else "amount")
ax.set_title("monthly comparison by account")
ax.legend()
fig.tight_layout()
fig.savefig(path, dpi=150)
fig.clf()
plt.close(fig)
print(f"saved grouped chart to {path}", file=sys.stderr)
# SUBCOMMANDS
def cmd_histogram(args):
rows = load_csv_rows(args, period=False)
accounts, amounts = parse_balance_csv(rows, invert=args.invert)
if not accounts:
print("error: no account data found in input", file=sys.stderr)
sys.exit(1)
accounts, amounts = aggregate_single_period(accounts, amounts, args.depth)
total = sum(amounts)
abs_total = sum(abs(a) for a in amounts)
data = sorted(zip(accounts, amounts), key=lambda p: abs(p[1]), reverse=True)
max_label = max(len(a) for a, _ in data)
bar_width = 35
print(f"\n{'ACCOUNT':<{max_label}} | {'AMOUNT':>14} | {'%':>5} | HISTOGRAM")
print("-" * (max_label + 62))
for account, amount in data:
pct = (abs(amount) / abs_total * 100) if abs_total else 0.0
filled = int((pct / 100) * bar_width)
print(f"{account:<{max_label}} | {amount:>14.2f} | {pct:>4.1f}% | {'█' * filled}")
print("-" * (max_label + 62))
print(f"{'TOTAL':<{max_label}} | {total:>14.2f} | 100.0% |\n")
if args.png:
render_histogram_png(accounts, amounts, args.png, args.currency, args.theme)
def cmd_pie(args):
rows = load_csv_rows(args, period=False)
accounts, amounts = parse_balance_csv(rows, invert=args.invert)
if not accounts:
print("error: no account data found in input", file=sys.stderr)
sys.exit(1)
accounts, amounts = aggregate_single_period(accounts, amounts, args.depth)
labels, values = top_n_with_other(accounts, amounts, args.top)
colour = use_colour(args)
print()
print(render_stacked_bar(labels, values, args.width, colour))
print()
print(render_legend(labels, values, colour))
print()
if args.png:
render_pie_png(labels, values, args.png, args.currency, args.theme)
def cmd_trend(args):
rows = load_csv_rows(args, period=True)
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
if not accounts:
print("error: no account data found in input", file=sys.stderr)
sys.exit(1)
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
if args.top > 0:
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
else:
# DEFAULT TO A SINGLE AGGREGATE SERIES
# sums every account per period unless --top requests individual lines
agg = [sum(row[j] for row in matrix) for j in range(len(periods))]
accounts, matrix = ["total"], [agg]
colour = use_colour(args)
max_label = max(len(a) for a in accounts)
print()
for i, account in enumerate(accounts):
series = matrix[i]
spark_lines = multiline_sparkline(series, rows=args.rows)
if colour:
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
spark_lines = [f"\033[{code}m{sl}\033[0m" for sl in spark_lines]
if args.rows > 1:
print(f"{account}: {series[0]:>12.2f} -> {series[-1]:>12.2f}")
for sl in spark_lines:
print(f" {sl}")
print()
else:
print(f"{account:<{max_label}} {spark_lines[0]} {series[0]:>12.2f} -> {series[-1]:>12.2f}")
if args.rows <= 1:
print()
print("periods: " + ", ".join(periods))
print()
if args.png:
render_trend_png(accounts, periods, matrix, args.png, args.currency, args.theme)
def cmd_stacked(args):
rows = load_csv_rows(args, period=True)
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
if not accounts:
print("error: no account data found in input", file=sys.stderr)
sys.exit(1)
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
colour = use_colour(args)
max_period = max(len(p) for p in periods)
print()
for j, period in enumerate(periods):
values = [abs(matrix[i][j]) for i in range(len(accounts))]
bar = render_stacked_bar(accounts, values, args.width, colour)
print(f"{period:<{max_period}} {bar}")
print()
totals = [sum(abs(matrix[i][j]) for j in range(len(periods))) for i in range(len(accounts))]
print(render_legend(accounts, totals, colour))
print()
if args.png:
render_stacked_png(accounts, periods, matrix, args.png, args.currency, args.theme)
def cmd_grouped(args):
rows = load_csv_rows(args, period=True)
accounts, periods, matrix = parse_period_csv(rows, invert=args.invert)
if not accounts:
print("error: no account data found in input", file=sys.stderr)
sys.exit(1)
accounts, periods, matrix = aggregate_multi_period(accounts, periods, matrix, args.depth)
accounts, matrix = top_n_matrix_with_other(accounts, matrix, args.top)
colour = use_colour(args)
bar_width = 20
print()
for i, account in enumerate(accounts):
row = [abs(v) for v in matrix[i]]
row_max = max(row) if row else 0
print(account)
for j, period in enumerate(periods):
filled = int((row[j] / row_max) * bar_width) if row_max else 0
char = "█" * filled
if colour:
code = ANSI_COLOURS[i % len(ANSI_COLOURS)]
char = f"\033[{code}m{char}\033[0m"
padded = char + " " * (bar_width - filled)
print(f" {period:<10} {padded} {row[j]:>12.2f}")
print()
if args.png:
render_grouped_png(accounts, periods, matrix, args.png, args.currency, args.theme)
# ARGUMENT PARSING
def build_parser():
parser = argparse.ArgumentParser(
description="terminal and image charts for hledger csv output, reads csv from standard input"
)
sub = parser.add_subparsers(dest="command", required=True)
common = argparse.ArgumentParser(add_help=False)
common.add_argument("--png", metavar="PATH", help="also render a matplotlib png to this path")
common.add_argument("-t", "--theme", default="default", help="matplotlib stylesheet theme (e.g., dark_background, ggplot)")
common.add_argument("--currency", default="", help="currency label for chart titles")
common.add_argument("--no-colour", action="store_true", help="disable ansi colour output")
common.add_argument("-i", "--invert", action="store_true", help="invert mathematical signs (multiply all amounts by -1)")
common.add_argument("-d", "--depth", type=int, default=0, help="summarise accounts to this hierarchy depth (0 for full names)")
common.add_argument("--journal", metavar="PATH", help="hledger journal file, calls hledger directly instead of reading csv from stdin")
common.add_argument("query", nargs=argparse.REMAINDER, help="optional hledger query passed through to hledger balance, e.g. an account pattern or -b/-e dates, only used with --journal")
p_hist = sub.add_parser("histogram", parents=[common], help="horizontal bar histogram of account balances")
p_hist.set_defaults(func=cmd_histogram)
p_pie = sub.add_parser("pie", parents=[common], help="proportional bar and pie chart of account distribution")
p_pie.add_argument("--top", type=int, default=8, help="accounts to show before collapsing into other, 0 for no limit")
p_pie.add_argument("--width", type=int, default=60, help="width of the ascii bar in characters")
p_pie.set_defaults(func=cmd_pie)
p_trend = sub.add_parser("trend", parents=[common], help="balance trend over time as sparklines and a line chart")
p_trend.add_argument("--top", type=int, default=0, help="individual account lines to show, 0 shows only the total")
p_trend.add_argument("-r", "--rows", type=int, default=1, help="height of the ascii sparkline canvas in rows (default 1)")
p_trend.set_defaults(func=cmd_trend)
p_stack = sub.add_parser("stacked", parents=[common], help="stacked bar of spending by category per period")
p_stack.add_argument("--top", type=int, default=8, help="accounts to show before collapsing into other, 0 for no limit")
p_stack.add_argument("--width", type=int, default=50, help="width of each ascii bar in characters")
p_stack.set_defaults(func=cmd_stacked)
p_group = sub.add_parser("grouped", parents=[common], help="grouped bar comparison of accounts across periods")
p_group.add_argument("--top", type=int, default=6, help="accounts to show before collapsing into other, 0 for no limit")
p_group.set_defaults(func=cmd_grouped)
return parser
def main():
parser = build_parser()
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

35
.local/bin/md2csv Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: 2026-07-28 11:14 +0100
import csv
import sys
# INPUT VALIDATION
# ensure an input file argument is provided
if len(sys.argv) < 2:
print("usage: md2csv <input.md>")
sys.exit(1)
input_file = sys.argv[1]
# TABLE CONVERSION
# open input markdown and initialize standard output csv writer
with open(input_file, "r", encoding="utf-8") as infile:
writer = csv.writer(sys.stdout)
for line in infile:
line = line.strip()
# ROW FILTERING
# skip empty lines and markdown layout separator lines
if not line or "---" in line:
continue
# CELL EXTRACTION
# check if line represents a markdown table row
if line.startswith("|") and line.endswith("|"):
# strip leading and trailing pipes then split by delimiter
raw_cells = line[1:-1].split("|")
cleaned_cells = [cell.strip() for cell in raw_cells]
writer.writerow(cleaned_cells)

198
.local/bin/md2hledger Executable file
View file

@ -0,0 +1,198 @@
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: 2026-07-27
import sys
from pathlib import Path
from decimal import Decimal, InvalidOperation
# CONFIGURATION
# set default accounting buckets, fallback currency, and journal vault path
DEFAULT_EXPENSE_ACCOUNT = "Expenses:Uncategorised"
DEFAULT_INCOME_ACCOUNT = "Income:Uncategorised"
DEFAULT_PAYMENT_ACCOUNT = "Assets:Banks:OPay"
DEFAULT_CURRENCY = "NGN"
NOTES_DIR = Path("~/obsidian/finances").expanduser()
# ACCOUNT NORMALISATION
# intercept legacy account strings and fix misrouted categories at compile time
def normalize_account(account_str, currency, name):
clean_account = account_str.strip()
clean_name = name.lower()
# auto resolve generic raenest root postings to multi currency leaf accounts
if clean_account == "Assets:Banks:Raenest":
if currency == "USD":
return "Assets:Banks:Raenest:USD"
if currency == "GBP":
return "Assets:Banks:Raenest:GBP"
if currency == "EUR":
return "Assets:Banks:Raenest:EUR"
return "Assets:Banks:Raenest:NGN"
# intercept fee and charge keywords before portfolio redirects
if any(w in clean_name for w in ("charge", "fee", "loss", "tax", "commission")):
if "uncategorised" in clean_account.lower():
return "Expenses:Financial:BankLoss"
return clean_account
# redirect uncategorised investment transfers to asset portfolios
if "uncategorised" in clean_account.lower():
if "cowrywise" in clean_name:
return "Assets:Investments:Cowrywise"
if "bybit" in clean_name:
return "Assets:Investments:Bybit"
if "octafx" in clean_name:
return "Assets:Investments:OctaFX"
return clean_account
# FRONTMATTER PARSER
# extract yaml between the opening and closing dashes without external libraries
def parse_frontmatter(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except OSError as e:
print(f"warning: unable to read {file_path} ({e})", file=sys.stderr)
return None
if not content.startswith("---"):
return None
parts = content.split("---", 2)
if len(parts) < 3:
return None
yaml_text = parts[1]
data = {}
for line in yaml_text.strip().split("\n"):
# ignore empty lines and full line comments
line = line.strip()
if not line or line.startswith("#"):
continue
if ":" in line:
key, val = line.split(":", 1)
# strip inline yaml comments and surrounding quotation marks
clean_val = val.split("#", 1)[0].strip().strip('\'"')
data[key.strip().lower()] = clean_val
return data
# JOURNAL GENERATOR
# convert parsed dictionary into an hledger double entry transaction string
def generate_entry(data, file_path):
try:
date = data["date"]
name = data.get("name", "untitled transaction")
tx_type = data.get("type", "expense").lower()
# read dynamic accounts or fallback to defaults
currency = data.get("currency", DEFAULT_CURRENCY).strip().upper()
raw_payment = data.get("payment_account", DEFAULT_PAYMENT_ACCOUNT)
raw_target = data.get("account", DEFAULT_EXPENSE_ACCOUNT if tx_type not in {"income", "liability", "borrowing", "inflow"} else DEFAULT_INCOME_ACCOUNT)
# normalise accounts against currency and description keywords
payment_account = normalize_account(raw_payment, currency, name)
target_account = normalize_account(raw_target, currency, name)
# use decimal for precise financial arithmetic
unit = Decimal(data.get("unit", "1"))
amount = Decimal(data["amount"])
total = unit * amount
# CHARGE RESOLUTION
# extract bank transfer fees and pos agent charges without multiplying by unit
transfer_charges = Decimal(data.get("transfer_charges", "0"))
pos_charges = Decimal(data.get("pos_charges", "0"))
# FOREX CONVERSIONS
# handle dual currency exchanges and correct backwards conversion frontmatter
if tx_type == "conversion":
source_currency = data.get("source_currency", "USD").strip().upper()
source_amount = Decimal(data.get("source_amount", "0")) * unit
total_source_outflow = source_amount
charge_postings = ""
if pos_charges > 0:
charge_postings += f" Expenses:Financial:POSFees {pos_charges:.2f} {source_currency}\n"
total_source_outflow += pos_charges
if transfer_charges > 0:
charge_postings += f" Expenses:Financial:BankLoss {transfer_charges:.2f} {source_currency}\n"
total_source_outflow += transfer_charges
# fix reversed raenest conversion where foreign currency was logged as spent instead of local currency
if ("raenest" in name.lower() or "raenest" in payment_account.lower()) and source_currency != DEFAULT_CURRENCY:
entry = (
f"{date} * {name}\n"
f" {payment_account} {source_amount:.2f} {source_currency}\n"
f" {target_account} -{total:.2f} {currency} @@ {source_amount:.2f} {source_currency}\n"
f"{charge_postings}\n"
)
return entry
entry = (
f"{date} * {name}\n"
f" {target_account} {total:.2f} {currency}\n"
f"{charge_postings}"
f" {payment_account} -{total_source_outflow:.2f} {source_currency} @@ {total:.2f} {currency}\n\n"
)
return entry
# CASH FLOW ROUTING
# route transactions based on whether value enters or leaves the payment account
inflow_types = {"income", "liability", "borrowing"}
# handle loans collected and standard inflows unless routed from equity windfalls
if tx_type == "inflow" and not payment_account.lower().startswith("equity"):
inflow_types.add("inflow")
if tx_type in inflow_types:
entry = (
f"{date} * {name}\n"
f" {payment_account} {currency} {total:.2f}\n"
f" {target_account} -{currency} {total:.2f}\n\n"
)
else:
charge_postings = ""
total_outflow = total
if pos_charges > 0:
charge_postings += f" Expenses:Financial:POSFees {currency} {pos_charges:.2f}\n"
total_outflow += pos_charges
if transfer_charges > 0:
charge_postings += f" Expenses:Financial:BankLoss {currency} {transfer_charges:.2f}\n"
total_outflow += transfer_charges
entry = (
f"{date} * {name}\n"
f" {target_account} {currency} {total:.2f}\n"
f"{charge_postings}"
f" {payment_account} -{currency} {total_outflow:.2f}\n\n"
)
return entry
except (KeyError, InvalidOperation, TypeError) as e:
# log malformed notes to standard error to prevent silent data loss
print(f"warning: skipping malformed note '{file_path}' ({type(e).__name__})", file=sys.stderr)
return ""
# MAIN EXECUTION
# scan the journal directory recursively and print compiled journal entries
if __name__ == "__main__":
if not NOTES_DIR.is_dir():
print(f"error: directory '{NOTES_DIR}' does not exist", file=sys.stderr)
sys.exit(1)
entries = []
# rglob enables recursive scanning through nested folder structures
for file_path in sorted(NOTES_DIR.rglob("*.md")):
data = parse_frontmatter(file_path)
if data and "date" in data and "amount" in data:
entry = generate_entry(data, file_path)
if entry:
entries.append(entry)
print("".join(entries), end="")

158
.local/bin/obsd Executable file
View file

@ -0,0 +1,158 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-08 16:59 +0100
set -Eeuo pipefail
# CONFIGURATION
LOCAL_VAULT="${HOME}/obsidian"
REMOTE_HOST="ish"
REMOTE_VAULT="~/obsidian"
RSYNC_FLAGS=(
-az
--delete
--human-readable
--itemize-changes
)
EXCLUDES=(
--exclude=".DS_Store"
--exclude=".Trash-*"
# Obsidian cache
--exclude=".obsidian/cache/"
--exclude=".obsidian/workspace*.json"
--exclude=".obsidian/workspace-mobile.json"
# Vim
--exclude="*.swp"
--exclude="*.swo"
# Temporary files
--exclude="*~"
)
# HELP
usage() {
cat <<EOF
obsd - Obsidian Vault Synchroniser
Usage:
obsd push
obsd pull
obsd status
obsd diff
obsd backup
obsd help
Commands
push Local -> iSH
pull iSH -> Local
status Dry-run using timestamps
diff Dry-run using checksums
backup Create compressed archive
EOF
}
# CHECKS
check_requirements() {
command -v rsync >/dev/null \
|| { echo "rsync not installed."; exit 1; }
command -v ssh >/dev/null \
|| { echo "ssh not installed."; exit 1; }
[[ -d "$LOCAL_VAULT" ]] \
|| { echo "Vault not found: $LOCAL_VAULT"; exit 1; }
}
# BACKUP
backup() {
local backup_dir="${HOME}/obsidian-backups"
mkdir -p "$backup_dir"
local stamp
stamp="$(date +%Y%m%d-%H%M%S)"
local archive="${backup_dir}/obsidian-${stamp}.tar.zst"
echo "Creating backup..."
tar \
--zstd \
-cf "$archive" \
-C "$HOME" \
obsidian
echo
echo "Backup created:"
echo " $archive"
}
# PUSH
push() {
rsync \
"${RSYNC_FLAGS[@]}" \
"${EXCLUDES[@]}" \
"${LOCAL_VAULT}/" \
"${REMOTE_HOST}:${REMOTE_VAULT}/"
}
# PULL
pull() {
rsync \
"${RSYNC_FLAGS[@]}" \
"${EXCLUDES[@]}" \
"${REMOTE_HOST}:${REMOTE_VAULT}/" \
"${LOCAL_VAULT}/"
}
# STATUS
status() {
rsync \
-azin \
"${EXCLUDES[@]}" \
"${LOCAL_VAULT}/" \
"${REMOTE_HOST}:${REMOTE_VAULT}/"
}
# DIFF
diff() {
rsync \
-azcin \
"${EXCLUDES[@]}" \
"${LOCAL_VAULT}/" \
"${REMOTE_HOST}:${REMOTE_VAULT}/"
}
# MAIN
check_requirements
case "${1:-help}" in
push)
push
;;
pull)
pull
;;
status)
status
;;
diff)
diff
;;
backup)
backup
;;
help|-h|--help)
usage
;;
*)
echo "Unknown command: ${1}"
echo
usage
exit 1
;;
esac

77
.local/bin/obsd-bash-2026-06-03 Executable file
View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
import argparse
import time
import subprocess
from threading import Timer
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path
# PRODUCTION CONFIGURATION
local_dir = "//home/sgbv/obsidian/"
remote_dest = "ish:/root/obsidian/"
debounce_seconds = 2.0
# ignore workspace ui states to prevent infinite sync loops
excludes = [
"--exclude=.obsidian/workspace*",
"--exclude=.trash/",
"--exclude=.git/"
]
def build_rsync_cmd(src, dest):
return ["rsync", "-avz", "--delete"] + excludes + [src, dest]
def pull_sync():
print(f"pulling changes from ish to arch...")
subprocess.run(build_rsync_cmd(remote_dest, local_dir))
print("pull complete.")
def push_sync():
print(f"pushing changes from arch to ish...")
subprocess.run(build_rsync_cmd(local_dir, remote_dest))
print("push complete.")
class watchdog_push_handler(FileSystemEventHandler):
def __init__(self):
self.sync_timer = None
def on_any_event(self, event):
# ignore directory changes and workspace/hidden cache updates
if event.is_directory or any(x in event.src_path for x in [".obsidian", ".trash"]):
return
print(f"local change detected: {event.event_type}")
if self.sync_timer:
self.sync_timer.cancel()
self.sync_timer = Timer(debounce_seconds, push_sync)
self.sync_timer.start()
def watch_mode():
print(f"arch watchdog listening on {local_dir}")
handler = watchdog_push_handler()
observer = Observer()
observer.schedule(handler, local_dir, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nstopping watchdog...")
observer.stop()
observer.join()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="arch sync node")
parser.add_argument("mode", nargs="?", default="watch", choices=["watch", "push", "pull"])
args = parser.parse_args()
if args.mode == "pull":
pull_sync()
elif args.mode == "push":
push_sync()
else:
watch_mode()

114
.local/bin/obsd-python-2026-07-26 Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: 2026-06-03 20:12 +0100
# UPDATED: 2026-07-24 09:40 +0100
"""
The script was modified to replace the archive flag with explicit metadata-stripping and checksum evaluation flags (`-rltz`, `--checksum`, `--no-perms`, `--omit-dir-times`) to prevent inode timestamp updates during rsync pulls, while introducing a native post-pull sanitisation function that removes iOS-injected carriage returns without altering file modification times.
"""
import argparse
import os
import subprocess
import time
from pathlib import Path
from threading import Timer
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
# PRODUCTION CONFIGURATION
local_dir = "//home/sgbv/obsidian/"
remote_dest = "ish:/root/obsidian/"
debounce_seconds = 2.0
# ignore workspace ui states to prevent infinite sync loops
excludes = [
"--exclude=.obsidian/workspace*",
"--exclude=.trash/",
"--exclude=.git/"
]
def build_rsync_cmd(src, dest):
# use checksum and strip posix metadata mapping to prevent inode ctime destruction
flags = [
"rsync",
"-rltz",
"--checksum",
"--delete",
"--no-perms",
"--no-owner",
"--no-group",
"--omit-dir-times"
]
return flags + excludes + [src, dest]
def sanitize_line_endings():
# strip dos carriage returns natively while preserving file modification timestamps
print("sanitising line endings...")
vault_path = Path(local_dir)
for file_path in vault_path.rglob("*.md"):
try:
content = file_path.read_bytes()
if b"\r\n" in content:
clean_content = content.replace(b"\r\n", b"\n")
# record original timestamps before writing
stat_data = file_path.stat()
file_path.write_bytes(clean_content)
# restore original access and modification timestamps
os.utime(file_path, (stat_data.st_atime, stat_data.st_mtime))
print(f"cleaned carriage returns: {file_path.name}")
except Exception as error:
print(f"sanitisation error on {file_path.name}: {error}")
def pull_sync():
print("pulling changes from ish to arch...")
subprocess.run(build_rsync_cmd(remote_dest, local_dir))
sanitize_line_endings()
print("pull complete.")
def push_sync():
print("pushing changes from arch to ish...")
subprocess.run(build_rsync_cmd(local_dir, remote_dest))
print("push complete.")
class watchdog_push_handler(FileSystemEventHandler):
def __init__(self):
self.sync_timer = None
def on_any_event(self, event):
# ignore directory changes and workspace or hidden cache updates
if event.is_directory or any(x in event.src_path for x in [".obsidian", ".trash"]):
return
print(f"local change detected: {event.event_type}")
if self.sync_timer:
self.sync_timer.cancel()
self.sync_timer = Timer(debounce_seconds, push_sync)
self.sync_timer.start()
def watch_mode():
print(f"arch watchdog listening on {local_dir}")
handler = watchdog_push_handler()
observer = Observer()
observer.schedule(handler, local_dir, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nstopping watchdog...")
observer.stop()
observer.join()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="arch sync node")
parser.add_argument("mode", nargs="?", default="watch", choices=["watch", "push", "pull"])
args = parser.parse_args()
if args.mode == "pull":
pull_sync()
elif args.mode == "push":
push_sync()
else:
watch_mode()

11
.local/bin/pac-size Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
pacman -Qi $(pactree -u "$1") | awk '/^Installed Size/ {
size=$4
if ($5 == "KiB") size /= 1024
if ($5 == "GiB") size *= 1024
if ($5 == "B") size /= 1048576
total += size
} END {
printf "Total ecosystem size: %.2f MiB\n", total
}'

14
.local/bin/pick Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -e
# DEPENDENCY CHECK
# ensures required wayland utilities are present before execution
if ! command -v hyprpicker >/dev/null 2>&1; then
notify-send "error" "hyprpicker is not installed."
exit 1
fi
# EXECUTION AND NOTIFICATION
# captures the hex value and triggers a system notification
HEX_CODE=$(hyprpicker -a)
notify-send "colour selected" "hex code $HEX_CODE copied to clipboard."

40
.local/bin/power Executable file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env bash
# MENU ENTRIES
# define the string values for the power options
lock="lock"
suspend="suspend"
hibernate="hibernate"
logout="log out"
reboot="reboot"
shutdown="shutdown"
# USER INPUT
# pipe options into fuzzel and capture the string output
selected=$(printf "%s\n%s\n%s\n%s\n%s\n%s" "$lock" "$suspend" "$hibernate" "$logout" "$reboot" "$shutdown" | fuzzel --dmenu --prompt "power: ")
# EXECUTION ROUTING
# match the captured string to the system daemon command
case "$selected" in
"$lock")
swaylock -f -c 000000
;;
"$suspend")
loginctl suspend
;;
"$hibernate")
loginctl hibernate
;;
"$logout")
loginctl terminate-session "$XDG_SESSION_ID"
;;
"$reboot")
loginctl reboot
;;
"$shutdown")
loginctl shutdown
;;
*)
exit 0
;;
esac

468
.local/bin/rate Executable file
View file

@ -0,0 +1,468 @@
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: 2026-05-26 12:45 +0100
# UPDATED: 2026-07-12 15:13 +0100
"""
rate v0.6
terminal multi-asset valuation engine. runs entirely on the python standard
library with zero external dependencies. supports specialized workflows for
forex, precious metals, energy futures, market indices, crypto, equities, and ETFs.
"""
import json
import os
import re
import socket
import sys
import urllib.error
import urllib.parse
import urllib.request
# FORCE IPV4 RESOLUTION
# prevent unreachable network errors on broken ipv6 internet gateways
_old_getaddrinfo = socket.getaddrinfo
def _ipv4_getaddrinfo(*args, **kwargs):
responses = _old_getaddrinfo(*args, **kwargs)
return [res for res in responses if res[0] == socket.AF_INET]
socket.getaddrinfo = _ipv4_getaddrinfo
# TERMINAL COLOURS
def get_colours():
if not sys.stdout.isatty() or os.environ.get("NO_COLOR"):
return {
"red": "",
"green": "",
"yellow": "",
"blue": "",
"cyan": "",
"nc": "",
}
return {
"red": "\033[1;31m",
"green": "\033[1;32m",
"yellow": "\033[1;33m",
"blue": "\033[1;34m",
"cyan": "\033[1;36m",
"nc": "\033[0m",
}
COL = get_colours()
# ASSET MAPS
CRYPTO_MAP = {
"btc": "bitcoin",
"eth": "ethereum",
"usdt": "tether",
"bnb": "binancecoin",
"sol": "solana",
"xrp": "ripple",
"usdc": "usd-coin",
"ada": "cardano",
"doge": "dogecoin",
"ton": "the-open-network",
"trx": "tron",
"avax": "avalanche-2",
"shib": "shiba-inu",
"dot": "polkadot",
"link": "chainlink",
}
METAL_MAP = {
"gold": "GC=F",
"silver": "SI=F",
"platinum": "PL=F",
"palladium": "PA=F",
"copper": "HG=F",
}
ENERGY_MAP = {
"oil": "CL=F",
"wti": "CL=F",
"brent": "BZ=F",
"gas": "NG=F",
"natgas": "NG=F",
"heating": "HO=F",
}
INDEX_MAP = {
"sp500": "^GSPC",
"s&p500": "^GSPC",
"nasdaq": "^IXIC",
"dow": "^DJI",
"dji": "^DJI",
"ftse": "^FTSE",
"nikkei": "^N225",
"dax": "^GDAXI",
}
# FORMATTING UTILITIES
def fmt_num(val, decimals=2):
return f"{val:,.{decimals}f}"
def fmt_rate(val):
if val == 0:
return "0.00"
if abs(val) < 0.0001:
return f"{val:,.8f}".rstrip("0").rstrip(".")
if abs(val) < 1.0:
return f"{val:,.4f}"
return f"{val:,.2f}"
# CORE HTTP ENGINE
def fetch_json(url, params=None):
if params:
url = f"{url}?{urllib.parse.urlencode(params)}"
req = urllib.request.Request(
url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(req, timeout=10) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as e:
print(
f"{COL['red']}error: api request failed with status {e.code}.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
except urllib.error.URLError:
print(
f"{COL['red']}error: network connection timed out or unreachable.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
except json.JSONDecodeError:
print(
f"{COL['red']}error: failed to parse remote json payload.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
def get_forex_rate(base, target):
base, target = base.upper(), target.upper()
if base == target:
return 1.0
data = fetch_json(f"https://open.er-api.com/v6/latest/{base}")
rates = data.get("rates", {})
if target not in rates:
print(
f"{COL['red']}error: currency target '{target}' unsupported.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
return float(rates[target])
def fetch_yahoo_market_data(ticker):
encoded = urllib.parse.quote(ticker)
url = f"https://query2.finance.yahoo.com/v8/finance/chart/{encoded}"
data = fetch_json(url, {"interval": "1d", "range": "1d"})
result = data.get("chart", {}).get("result")
if not result:
print(
f"{COL['red']}error: market asset '{ticker}' not found on yahoo finance.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
meta = result[0].get("meta", {})
price = float(meta.get("regularMarketPrice", 0.0))
prev_close = float(
meta.get("chartPreviousClose")
or meta.get("previousClose")
or price
)
return {
"symbol": meta.get("symbol", ticker.upper()),
"name": (
meta.get("shortName") or meta.get("longName") or ticker
).strip(),
"price": price,
"currency": meta.get("currency", "USD").upper(),
"change_pct": ((price - prev_close) / prev_close * 100.0) if prev_close else 0.0,
}
# UTILITY PARSER FOR MARKET INPUTS
def parse_market_args(args, default_target="NGN"):
amount = 1.0
target = default_target
if len(args) >= 2:
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[1]):
amount = float(args[1])
if len(args) >= 3:
target = args[2]
else:
target = args[1]
if len(args) >= 3 and re.match(r"^[0-9]+(\.[0-9]+)?$", args[2]):
amount = float(args[2])
return amount, target.upper()
# SUBCOMMAND EXECUTORS
def cmd_forex(args):
amount, base, target = 1.0, "USD", "NGN"
if len(args) == 1:
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
amount = float(args[0])
else:
base = args[0]
elif len(args) == 2:
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
amount, base = float(args[0]), args[1]
else:
base, target = args[0], args[1]
elif len(args) >= 3:
if re.match(r"^[0-9]+(\.[0-9]+)?$", args[0]):
amount, base, target = float(args[0]), args[1], args[2]
else:
base, target, amount = args[0], args[1], float(args[2])
base, target = base.upper(), target.upper()
print(
f"{COL['blue']}fetching exchange rate for {base}/{target}...{COL['nc']}",
file=sys.stderr,
)
rate = get_forex_rate(base, target)
val = amount * rate
print("-----------------------------------")
print(f"pair: {base} / {target}")
print(f"rate: {COL['yellow']}1 {base} = {fmt_rate(rate)} {target}{COL['nc']}")
print(
f"value: {COL['green']}{target} {fmt_num(val, 2)}{COL['nc']} ({base} {fmt_num(amount, 2)})"
)
print("-----------------------------------")
def cmd_yahoo_generic(ticker, amount, target_curr, class_label):
print(
f"{COL['blue']}fetching evaluation for {class_label} [{ticker}]...{COL['nc']}",
file=sys.stderr,
)
market = fetch_yahoo_market_data(ticker)
native_curr = market["currency"]
native_price = market["price"]
total_native = amount * native_price
if native_curr != target_curr:
fx_rate = get_forex_rate(native_curr, target_curr)
final_val = total_native * fx_rate
rate_disp = f"{fmt_rate(native_price)} {native_curr} (~{fmt_num(native_price * fx_rate, 2)} {target_curr})"
val_disp = f"{COL['green']}{fmt_num(final_val, 2)} {target_curr}{COL['nc']} ({fmt_num(total_native, 2)} {native_curr})"
else:
rate_disp = f"{fmt_rate(native_price)} {native_curr}"
val_disp = f"{COL['green']}{fmt_num(total_native, 2)} {native_curr}{COL['nc']}"
chg = market["change_pct"]
chg_col = COL["green"] if chg >= 0 else COL["red"]
chg_sign = "+" if chg >= 0 else ""
print("-----------------------------------")
print(f"asset: {market['name']} ({market['symbol']})")
print(f"valuation: {COL['yellow']}{rate_disp}{COL['nc']}")
print(f"24h move: {chg_col}{chg_sign}{chg:.2f}%{COL['nc']}")
if amount != 1.0 or native_curr != target_curr:
print(f"total sum: {val_disp}")
print("-----------------------------------")
def cmd_metal(args):
if not args:
print(
f"{COL['red']}error: missing metal identifier.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
query = args[0].lower().strip()
ticker = METAL_MAP.get(query, args[0].upper())
amount, target = parse_market_args(args)
cmd_yahoo_generic(ticker, amount, target, "Precious Metal")
def cmd_energy(args):
if not args:
print(
f"{COL['red']}error: missing energy identifier.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
query = args[0].lower().strip()
ticker = ENERGY_MAP.get(query, args[0].upper())
amount, target = parse_market_args(args)
cmd_yahoo_generic(ticker, amount, target, "Energy Future")
def cmd_indices(args):
if not args:
print(
f"{COL['red']}error: missing index symbol.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
query = args[0].lower().strip()
ticker = INDEX_MAP.get(query, args[0].upper())
amount, target = parse_market_args(args)
cmd_yahoo_generic(ticker, amount, target, "Market Index")
def cmd_stock(args):
if not args:
print(
f"{COL['red']}error: missing stock ticker.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
ticker = args[0].upper()
amount, target = parse_market_args(args)
cmd_yahoo_generic(ticker, amount, target, "Equity Asset")
def cmd_etf(args):
if not args:
print(
f"{COL['red']}error: missing ETF symbol.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
ticker = args[0].upper()
amount, target = parse_market_args(args)
cmd_yahoo_generic(ticker, amount, target, "Exchange Traded Fund")
def cmd_crypto(args):
if not args:
print(
f"{COL['red']}error: missing crypto token name or symbol.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
query = args[0].lower().strip()
amount, vs_curr = parse_market_args(args, default_target="ngn")
vs_target = vs_curr.lower()
if query in CRYPTO_MAP:
coin_id, symbol = CRYPTO_MAP[query], query.upper()
else:
print(
f"{COL['blue']}searching coingecko for '{query}'...{COL['nc']}",
file=sys.stderr,
)
search = fetch_json(
"https://api.coingecko.com/api/v3/search", {"query": query}
)
coins = search.get("coins", [])
if not coins:
print(
f"{COL['red']}error: token '{query}' not found.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
coin_id, symbol = coins[0]["id"], coins[0]["symbol"].upper()
print(
f"{COL['blue']}fetching {symbol} spot data in {vs_curr.upper()}...{COL['nc']}",
file=sys.stderr,
)
data = fetch_json(
"https://api.coingecko.com/api/v3/simple/price",
{
"ids": coin_id,
"vs_currencies": vs_target,
"include_24hr_change": "true",
},
)
coin_data = data.get(coin_id, {})
if vs_target not in coin_data:
print(
f"{COL['red']}error: target market currency '{vs_curr}' unsupported.{COL['nc']}",
file=sys.stderr,
)
sys.exit(1)
rate = float(coin_data[vs_target])
change = float(coin_data.get(f"{vs_target}_24h_change", 0.0))
val = amount * rate
chg_col = COL["green"] if change >= 0 else COL["red"]
chg_sign = "+" if change >= 0 else ""
print("-----------------------------------")
print(f"crypto: {symbol} ({coin_id})")
print(f"spot rate: {COL['yellow']}{fmt_rate(rate)} {vs_curr.upper()}{COL['nc']}")
print(f"24h move: {chg_col}{chg_sign}{change:.2f}%{COL['nc']}")
if amount != 1.0:
print(
f"valuation: {COL['green']}{fmt_num(val, 2)} {vs_curr.upper()}{COL['nc']} ({symbol} {fmt_num(amount, 4)})"
)
print("-----------------------------------")
# ROUTING MATRIX
def show_help():
print("rate - multi-asset evaluation utility")
print("usage:")
print(" rate forex [amt] <base> [target] fiat configurations (alias: fx)")
print(" rate metal <gold|silver|ticker> precious spots contracts (alias: met)")
print(" rate energy <oil|brent|gas|ticker> fuel commodity options (alias: nrg)")
print(" rate indices <sp500|nasdaq|ticker> global market indices (alias: ind)")
print(" rate crypto <coin> [amt] [curr] digital tokens valuations (alias: c)")
print(" rate stock <ticker> [amt] [curr] corporate equities (alias: s)")
print(" rate etf <ticker> [amt] [curr] exchange traded funds (alias: e)")
sys.exit(0)
def main():
args = sys.argv[1:]
if not args or args[0] in ("-h", "--help", "help"):
show_help()
cmd = args[0].lower()
sub_args = args[1:]
if cmd in ("forex", "fx", "to", "from"):
if cmd == "to":
cmd_forex(["1", "NGN", sub_args[0]] if sub_args else ["NGN"])
elif cmd == "from":
cmd_forex(["1", sub_args[0], "NGN"] if sub_args else ["USD", "NGN"])
else:
cmd_forex(sub_args)
elif cmd in ("metal", "met"):
cmd_metal(sub_args)
elif cmd in ("energy", "nrg", "eng"):
cmd_energy(sub_args)
elif cmd in ("indices", "ind", "idx"):
cmd_indices(sub_args)
elif cmd in ("crypto", "c"):
cmd_crypto(sub_args)
elif cmd in ("stock", "stocks", "s"):
cmd_stock(sub_args)
elif cmd in ("etf", "e"):
cmd_etf(sub_args)
else:
# direct default fallback to legacy raw strings evaluation
cmd_forex(args)
if __name__ == "__main__":
main()

189
.local/bin/recon Executable file
View file

@ -0,0 +1,189 @@
#!/usr/bin/env bash
set -o pipefail
export LC_ALL=C
# COLOURS
# standard terminal output hues
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[1;33m'
cyan='\033[0;36m'
nc='\033[0m'
# CONFIGURATION
# defaults to mullvad dns for privacy-respecting resolution
resolver="@194.242.2.2"
timeout_val=2
ua="mozilla/5.0 (x11; linux x86_64) applewebkit/537.36"
# USAGE GUIDE
# displays instructions if arguments are missing
usage() {
printf "${cyan}usage:${nc} reconctl [-t timeout] <domain>\n"
printf " -t set network timeout in seconds (default: 2)\n"
printf " -h show help menu\n\n"
exit 1
}
while getopts "t:h" opt; do
case ${opt} in
t) timeout_val=$OPTARG ;;
h) usage ;;
*) usage ;;
esac
done
shift $((OPTIND - 1))
domain="$1"
[[ -z "$domain" ]] && usage
# INPUT VALIDATION
# rejects malformed hostnames before they reach dig/curl/openssl/whois
if [[ ! "$domain" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; then
printf "${red}error: '%s' is not a valid domain${nc}\n" "$domain"
exit 1
fi
# HELPER FUNCTIONS
# standardises section headers for clean output
section_header() {
printf "\n${cyan}%s${nc}\n" "$1"
}
# DEPENDENCY AUDIT
# validates required packages before execution
for cmd in dig curl jq openssl whois awk; do
if ! command -v "$cmd" >/dev/null 2>&1; then
printf "${red}error: missing dependency '%s'${nc}\n" "$cmd"
exit 1
fi
done
# DNS ANALYSIS
# extracts primary records and security configurations
section_header "DNS RECORDS"
ipv4=$(dig $resolver +short A "$domain")
ipv6=$(dig $resolver +short AAAA "$domain")
cname=$(dig $resolver +short CNAME "$domain")
mx=$(dig $resolver +short MX "$domain")
caa=$(dig $resolver +short CAA "$domain")
txt=$(dig $resolver +short TXT "$domain" | grep -Ei "spf|dmarc" || true)
dnskey=$(dig $resolver +short DNSKEY "$domain")
printf " ipv4:\n%s\n" "$([[ -n "$ipv4" ]] && echo "$ipv4" | sed 's/^/ /' || echo " n/a")"
printf " ipv6:\n%s\n" "$([[ -n "$ipv6" ]] && echo "$ipv6" | sed 's/^/ /' || echo " n/a")"
printf " cname:\n %s\n" "${cname:-none detected}"
printf " nameservers:\n%s\n" "$(dig $resolver +short NS "$domain" | sed 's/^/ /')"
printf " mail exchangers:\n%s\n" "$([[ -n "$mx" ]] && echo "$mx" | sed 's/^/ /' || echo " none detected")"
printf " caa records:\n%s\n" "$([[ -n "$caa" ]] && echo "$caa" | sed 's/^/ /' || echo " none detected")"
printf " security txt:\n%s\n" "$([[ -n "$txt" ]] && echo "$txt" | sed 's/^/ /' || echo " none detected")"
printf " dnssec: %s\n" "$([[ -n "$dnskey" ]] && echo "enabled" || echo "not detected")"
# REVERSE LOOKUP
# checks ptr records for resolved v4 and v6 addresses
section_header "REVERSE DNS"
if [[ -n "$ipv4" || -n "$ipv6" ]]; then
for ip in $ipv4 $ipv6; do
ptr=$(dig $resolver +short -x "$ip")
printf " %s -> %s\n" "$ip" "${ptr:-n/a}"
done
else
printf " n/a\n"
fi
# HTTP PERFORMANCE
# tests routing responses and redirection chains
section_header "HTTP ROUTING"
http_status=$(timeout "$timeout_val" curl -A "$ua" -sL -o /dev/null -w "%{http_code}" "http://$domain" || echo "timeout")
https_status=$(timeout "$timeout_val" curl -A "$ua" -sL -o /dev/null -w "%{http_code}" "https://$domain" 2>/dev/null || echo "timeout")
redirects=$(timeout "$timeout_val" curl -A "$ua" -sI -L "http://$domain" 2>/dev/null | grep -i "^location:" | sed 's/[Ll]ocation: //g' | tr -d '\r')
printf " http status: %s\n" "$http_status"
printf " https status: %s\n" "$https_status"
printf " redirects:\n%s\n" "$([[ -n "$redirects" ]] && echo "$redirects" | sed 's/^/ -> /' || echo " none")"
# ENCRYPTION AUDIT
# pulls subject, issuer, and expiration from the tls certificate
section_header "TLS CERTIFICATE"
tls=$(timeout "$timeout_val" openssl s_client -connect "$domain:443" -servername "$domain" -showcerts </dev/null 2>/dev/null)
expiry=$(echo "$tls" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
subject_issuer=$(echo "$tls" | grep -E "subject=|issuer=" | sed 's/^[ \t]*//')
printf " subject/issuer:\n%s\n" "$([[ -n "$subject_issuer" ]] && echo "$subject_issuer" | sed 's/^/ /' || echo " unknown")"
printf " expiry date: %s\n" "${expiry:-unknown}"
# SECURITY HEADERS
# evaluates presence of critical web security directives
section_header "SECURITY POSTURE"
headers=$(timeout "$timeout_val" curl -A "$ua" -sI "https://$domain" 2>/dev/null | tr -d '\r')
for h in "strict-transport-security" "content-security-policy" "x-frame-options" "x-content-type-options"; do
val=$(echo "$headers" | grep -i "^$h:" | cut -d: -f2- | xargs)
if [[ -n "$val" ]]; then
printf " %-25s ${green}present${nc}\n" "$h"
else
printf " %-25s ${yellow}missing${nc}\n" "$h"
fi
done
# INFRASTRUCTURE PROBE
# checks common critical ports on the primary resolved ip, results sorted after concurrent scan
section_header "PORT SCAN"
ports=(21 22 25 53 80 443 3306 8080 8443)
target_ip=$(echo "$ipv4" | head -n1)
if [[ -n "$target_ip" ]]; then
scan_tmp=$(mktemp)
exec 3>&2
exec 2>/dev/null
for port in "${ports[@]}"; do
(timeout 1 bash -c "</dev/tcp/$target_ip/$port" &>/dev/null && echo "$port" >> "$scan_tmp") &
done
wait
exec 2>&3
if [[ -s "$scan_tmp" ]]; then
sort -n "$scan_tmp" | while read -r port; do
printf " port %-5s ${green}open${nc}\n" "$port"
done
else
printf " no open ports detected\n"
fi
rm -f "$scan_tmp"
else
printf " ${red}no ipv4 address for scanning.${nc}\n"
fi
# ASSET ENUMERATION
# queries certificate transparency logs for child infrastructure via crt.sh json endpoint
section_header "SUBDOMAINS"
subdomains=$(timeout 15 curl -s "https://crt.sh/?q=%25.$domain&output=json" | jq -r '.[].name_value' 2>/dev/null | tr ',' '\n' | sed 's/\*\.//' | sort -u | head -n 15)
[[ -n "$subdomains" ]] && echo "$subdomains" | sed 's/^/ /' || echo " enumeration failed or none found"
# REGISTRY AUDIT
# extracts baseline domain registration metadata
section_header "WHOIS DATA"
whois_data=$(timeout "$timeout_val" whois "$domain" 2>/dev/null | grep -Ei "^registrar:|^creation date:|^updated date:" | tr -s ' ' | head -n 5)
[[ -n "$whois_data" ]] && echo "$whois_data" | sed 's/^/ /' || echo " extraction limited"
# RISK SCORE
# calculates a basic hazard rating based on missing protections and open ports
section_header "RISK SUMMARY"
risk=0
[[ ! "$https_status" =~ ^(2|3) ]] && ((risk += 2))
echo "$headers" | grep -qi "^strict-transport-security:" || ((risk += 1))
echo "$headers" | grep -qi "^content-security-policy:" || ((risk += 1))
[[ -z "$dnskey" ]] && ((risk += 1))
timeout 1 bash -c "</dev/tcp/$target_ip/22" &>/dev/null && ((risk += 1))
timeout 1 bash -c "</dev/tcp/$target_ip/21" &>/dev/null && ((risk += 2))
if [ $risk -le 1 ]; then
posture_color=$green
posture_text="low risk"
else
posture_color=$red
posture_text="risk identified"
fi
printf " domain: %s\n" "$domain"
printf " ip: %s\n" "${target_ip:-n/a}"
printf " posture: ${posture_color}%s${nc} (score: %d)\n\n" "$posture_text" "$risk"

11
.local/bin/restore-brightness Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-08 13:04 +0100
set -euo pipefail
main() {
sleep 1
brightnessctl set 10%}
}
main "$@"

30
.local/bin/scratch Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
# ~/.local/bin/scratchpadctl
# MANAGE SPATIAL SCRATCHPAD STATE
APP_ID="$1"
SPAWN_CMD="$2"
HIDDEN_WORKSPACE="scratchpad_vault"
WINDOW_JSON=$(niri msg -j windows | jq -c ".[] | select(.app_id == \"$APP_ID\")" | head -n 1)
if [ -z "$WINDOW_JSON" ]; then
eval "$SPAWN_CMD" &
exit 0
fi
# extract geometric identifiers
WINDOW_ID=$(echo "$WINDOW_JSON" | jq -r '.id')
WINDOW_WS=$(echo "$WINDOW_JSON" | jq -r '.workspace_id')
# query active operational space
CURRENT_WS=$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .id')
if [ "$WINDOW_WS" == "$CURRENT_WS" ]; then
niri msg action focus-window --id "$WINDOW_ID"
niri msg action move-window-to-workspace "$HIDDEN_WORKSPACE"
else
niri msg action focus-window --id "$WINDOW_ID"
niri msg action move-window-to-workspace "$CURRENT_WS"
niri msg action focus-window --id "$WINDOW_ID"
fi

27
.local/bin/start-audio Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-04 21:37 +0100
set -euo pipefail
# TERMINATE EXISTING PROCESSES
# ensure a clean state before starting
killall -q wireplumber pipewire-pulse pipewire mpd
sleep 1
# INITIALISE AUDIO DAEMONS
# start the core pipewire service
pipewire &
sleep 1
# start the session manager
wireplumber &
# start the pulseaudio compatibility layer
pipewire-pulse &
# START MUSIC PLAYER DAEMON
# launch the background daemon for local music
mpd &

365
.local/bin/status Executable file
View file

@ -0,0 +1,365 @@
#!/usr/bin/env bash
set -o pipefail
export LC_ALL=C
# COLOURS
# define standard terminal output hues
green='\033[1;32m'
cyan='\033[1;36m'
yellow='\033[1;33m'
red='\033[1;31m'
nc='\033[0m'
# HELPER FUNCTIONS
# standardises section headers and validates executable presence
section_header() {
printf "\n${green}%s${nc}\n" "$1"
}
cmd_exists() {
command -v "$1" >/dev/null 2>&1
}
# SYSTEM CORE
# prints basic kernel and hardware identity
print_system_core() {
local kernel cpu uptime_raw load_avg
kernel=$(uname -r)
cpu=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)
uptime_raw=$(uptime -p | sed 's/up //')
load_avg=$(awk '{print $1, $2, $3}' /proc/loadavg)
section_header "SYSTEM CORE"
printf " kernel: %s\n" "$kernel"
printf " cpu: %s\n" "$cpu"
printf " load: %s\n" "$load_avg"
printf " uptime: %s\n" "$uptime_raw"
}
# GRAPHICS
# detects the display protocol and active compositor
print_graphics() {
local wm_name session_type
session_type="${XDG_SESSION_TYPE:-unknown}"
wm_name="unknown"
if [ "$session_type" = "wayland" ]; then
wm_name="${XDG_CURRENT_DESKTOP:-Wayland}"
elif cmd_exists xprop; then
wm_name=$(xprop -root _NET_WM_NAME 2>/dev/null | cut -d'"' -f2)
[ -z "$wm_name" ] && wm_name=$(xprop -root WM_NAME 2>/dev/null | cut -d'"' -f2)
fi
section_header "GRAPHICS"
printf " session: %s\n" "$session_type"
printf " compositor: %s\n" "$wm_name"
if cmd_exists lspci; then
local gpus
gpus=$(lspci -mm | awk -F'"' -v OFS='' '
tolower($2) ~ /^(vga compatible controller|3d controller|display controller)$/ {print $4}' | paste -sd ', ' -)
printf " gpu: %s\n" "${gpus:-unknown}"
else
printf " gpu: (lspci missing)\n"
fi
}
# HARDWARE
# polls thermal sensors, power supply, and drive health
print_hardware() {
section_header "HARDWARE"
local temp=""
if cmd_exists sensors; then
if cmd_exists jq; then
temp=$(sensors -j 2>/dev/null | jq -r '.. | ."Package id 0"? // ."Tctl"? // empty | .temp1_input' 2>/dev/null)
if [ -n "$temp" ]; then
temp=$(printf "%.1f°C" "$temp")
fi
fi
if [ -z "$temp" ]; then
temp=$(sensors 2>/dev/null | grep -m1 -E 'Package id 0|Tctl|Tdie' | awk '{print $4}' | sed 's/+//;s/°C//')
[ -n "$temp" ] && temp="${temp}°C"
fi
[ -z "$temp" ] && temp=$(sensors 2>/dev/null | grep -m1 'temp1' | awk '{print $2}' | sed 's/+//')
else
for zone in /sys/class/thermal/thermal_zone*; do
local type temp_val
type=$(cat "$zone/type" 2>/dev/null)
temp_val=$(cat "$zone/temp" 2>/dev/null)
if [ -n "$temp_val" ]; then
temp="$((temp_val/1000))°C ($type)"
break
fi
done
fi
printf " temp: %s\n" "${temp:-unknown}"
local bat_info=""
for bat in /sys/class/power_supply/BAT*; do
if [ -d "$bat" ]; then
local level status name
name=$(basename "$bat")
level=$(cat "$bat/capacity" 2>/dev/null || echo "?")
status=$(cat "$bat/status" 2>/dev/null || echo "unknown")
bat_info="${bat_info}${name}: ${level}% (${status}) "
fi
done
if [ -n "$bat_info" ]; then
printf " battery: %s\n" "$(echo "$bat_info" | xargs)"
fi
local smart_health=""
if cmd_exists nvme && [ -e /dev/nvme0 ]; then
smart_health=$(nvme smart-log /dev/nvme0 2>/dev/null | awk '/percentage_used/ {print $3"%" used}')
elif cmd_exists smartctl; then
smart_health=$(smartctl -H /dev/sda 2>/dev/null | awk '/SMART overall-health|SMART Health Status/ {print $NF}')
fi
printf " disk health: %s\n" "${smart_health:-not checked}"
}
# RESOURCES
# calculates memory pressure and storage consumption
print_resources() {
local mem_data total used avail mem_pct swap_data s_total s_used
mem_data=$(free -m | awk '/^Mem:/ {print $2,$3,$7}')
read -r total used avail <<<"$mem_data"
mem_pct=$((100 * used / total))
swap_data=$(free -m | awk '/^Swap:/ {print $2,$3}')
read -r s_total s_used <<<"$swap_data"
section_header "RESOURCES"
printf " ram: %sMi / %sMi (%s%% used)\n" "$used" "$total" "$mem_pct"
printf " avail: %sMi reclaimable\n" "$avail"
printf " swap: %sMi / %sMi used\n" "$s_used" "$s_total"
printf " disk: %s\n" "$(df -h / | awk 'NR==2 {print $3 " / " $2 " (" $5 " used)"}')"
if [ -d /var/cache/pacman/pkg ]; then
local cache_size
cache_size=$(du -sh /var/cache/pacman/pkg 2>/dev/null | cut -f1)
printf " pkg cache: %s\n" "${cache_size:-?}"
else
printf " pkg cache: (missing)\n"
fi
}
# NETWORK
# outputs ipv4 addressing and routing states
print_network() {
section_header "NETWORK"
ip -4 addr show | grep "inet " | grep -v "127.0.0.1" | \
awk '{print " " $NF ": " $2}' | sed 's|/.*||'
local gateway
gateway=$(ip route | grep default | awk '{print $3}' | head -n1)
printf " gateway: %s\n" "$gateway"
local dns
dns=$(grep nameserver /etc/resolv.conf 2>/dev/null | awk '{print $2}' | head -n2 | xargs)
[ -z "$dns" ] && dns="(none)"
printf " dns: %s\n" "$dns"
for iface in /sys/class/net/*; do
if [ "$(cat "$iface/operstate" 2>/dev/null)" = "up" ]; then
local iface_name
iface_name=$(basename "$iface")
local speed
speed=$(cat "$iface/speed" 2>/dev/null)
[ -n "$speed" ] && printf " %s speed: %s Mbit/s\n" "$iface_name" "$speed"
if cmd_exists iw; then
local ssid
ssid=$(iw dev "$iface_name" link 2>/dev/null | awk '/SSID/ {print $2}')
[ -n "$ssid" ] && printf " %s ssid: %s\n" "$iface_name" "$ssid"
fi
fi
done
if cmd_exists curl; then
local ext_ip
ext_ip=$(curl -s --max-time 2 ifconfig.me 2>/dev/null)
[ -n "$ext_ip" ] && printf " external: %s\n" "$ext_ip"
fi
}
# PACKAGES
# queries the local pacman database for state and updates
print_packages() {
if ! cmd_exists pacman; then
return
fi
local pkg_total pkg_explicit pkg_aur pkg_updates pkg_orphan
pkg_total=$(pacman -Qq 2>/dev/null | wc -l)
pkg_explicit=$(pacman -Qe 2>/dev/null | wc -l)
pkg_aur=$(pacman -Qm 2>/dev/null | wc -l)
pkg_orphan=$(pacman -Qtdq 2>/dev/null | wc -l)
section_header "PACKAGES"
printf " total: %s\n" "$pkg_total"
printf " explicit: %s\n" "$pkg_explicit"
printf " aur: %s\n" "$pkg_aur"
printf " orphans: %s\n" "$pkg_orphan"
if cmd_exists checkupdates; then
pkg_updates=$(checkupdates 2>/dev/null | wc -l)
if [ "$pkg_updates" -gt 0 ]; then
printf " updates: ${yellow}%s pending${nc}\n" "$pkg_updates"
else
printf " updates: up to date\n"
fi
else
printf " updates: (checkupdates not installed)\n"
fi
local last_sync
last_sync=$(grep -i "full system upgrade" /var/log/pacman.log 2>/dev/null | tail -1 | awk '{print $1, $2}' | sed 's/\[//;s/\]//;s/ \[PACMAN\]//')
printf " last sync: %s\n" "${last_sync:-unknown}"
if grep -q '^\[testing\]' /etc/pacman.conf 2>/dev/null; then
printf " testing: ${yellow}enabled${nc}\n"
else
printf " testing: disabled\n"
fi
}
# SECURITY
# checks kernel hardening and audits package vulnerabilities
print_security() {
section_header "SECURITY"
local kernel_type
kernel_type=$(uname -r | grep -qi "hardened" && echo "hardened" || echo "standard")
printf " kernel: %s\n" "$kernel_type"
if cmd_exists arch-audit; then
local vulns
vulns=$(arch-audit -q 2>/dev/null | wc -l)
if [ "$vulns" -gt 0 ]; then
printf " vulns: ${red}%d packages${nc}\n" "$vulns"
else
printf " vulns: none\n"
fi
fi
}
# PERFORMANCE
# reports on cpu scaling and underlying disk scheduling
print_performance() {
section_header "PERFORMANCE"
if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ]; then
local gov freq
gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
printf " governor: %s\n" "$gov"
if [ -f /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq ]; then
freq=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq)
printf " cpu freq: %s MHz\n" $((freq / 1000))
fi
else
printf " governor: not available\n"
fi
local root_dev
root_dev=$(findmnt -n -o SOURCE / 2>/dev/null)
if [ -n "$root_dev" ]; then
local disk_name
disk_name=$(lsblk -ndo PKNAME "$root_dev" 2>/dev/null)
[ -z "$disk_name" ] && disk_name=$(lsblk -ndo NAME "$root_dev" 2>/dev/null)
if [ -n "$disk_name" ] && [ -f "/sys/block/$disk_name/queue/scheduler" ]; then
local scheduler
scheduler=$(grep -oP '\[\K[^\]]+' "/sys/block/$disk_name/queue/scheduler")
printf " scheduler: %s\n" "${scheduler:-unknown}"
else
printf " scheduler: (no sysfs entry)\n"
fi
else
printf " scheduler: (root device unknown)\n"
fi
}
# SYSTEMD HEALTH
# audits systemd for failed services and boot times
print_systemd_health() {
if ! cmd_exists systemctl; then
return
fi
section_header "SYSTEMD HEALTH"
local failed_units
failed_units=$(systemctl --failed --no-legend 2>/dev/null | wc -l)
if [ "$failed_units" -gt 0 ]; then
printf " failed units: ${red}%d${nc}\n" "$failed_units"
systemctl --failed --no-legend 2>/dev/null | awk '{print " " $1}'
else
printf " failed units: none\n"
fi
if cmd_exists journalctl; then
local boot_time
boot_time=$(journalctl --list-boots 2>/dev/null | tail -1 | awk '{print $4}')
[ -n "$boot_time" ] && printf " boot time: %s\n" "$boot_time"
fi
}
# CONTAINERS AND VIRTUALISATION
# detects running instances across incus, docker, and libvirt
print_containers_virt() {
section_header "CONTAINERS & VIRTUALISATION"
if cmd_exists incus; then
local incus_count
incus_count=$(incus list status=running -c n --format csv 2>/dev/null | wc -l)
printf " incus: %d active instances\n" "$incus_count"
fi
if cmd_exists docker && systemctl -q is-active docker 2>/dev/null; then
local containers
containers=$(docker ps -q 2>/dev/null | wc -l)
printf " docker: %d running containers\n" "$containers"
fi
if cmd_exists podman && systemctl -q is-active podman 2>/dev/null; then
local pods
pods=$(podman ps -q 2>/dev/null | wc -l)
printf " podman: %d running containers\n" "$pods"
fi
if cmd_exists virsh && systemctl -q is-active libvirtd 2>/dev/null; then
local vms
vms=$(virsh list --name 2>/dev/null | grep -v "^$" | wc -l)
printf " libvirt: %d active VMs\n" "$vms"
fi
}
# TIMERS
# counts active systemd chron tasks
print_timers() {
if ! cmd_exists systemctl; then
return
fi
local timer_count
timer_count=$(systemctl list-timers --no-legend 2>/dev/null | wc -l)
if [ "$timer_count" -gt 0 ]; then
section_header "TIMERS"
printf " active: %d systemd timers\n" "$timer_count"
fi
}
# EXECUTION
print_system_core
print_graphics
print_hardware
print_resources
print_network
print_packages
print_security
print_performance
print_systemd_health
print_containers_virt
print_timers
printf "\n"

14
.local/bin/strap-archlinux-vm Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-03 18:35 +0100
set -euo pipefail
main() {
incus init archlinux-vm --empty --vm -c security.secureboot=false
incus config device add archlinux-vm install-iso disk source=//home/sgbv/.iso/archlinux.iso boot.priority=10
incus start archlinux-vm
incus console archlinux-vm --type=vga
}
main "$@"

17
.local/bin/strap-artix-vm Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# AUTHOR
# daesorin
# CREATED
# 2026-08-03 18:35 +0100
set -euo pipefail
main() {
aria2c --dir=//home/sgbv/.iso --seed-time=0 //home/sgbv/Downloads/artix-base-openrc-20260729-x86_64.iso.torrent
incus init artix-vm --empty --vm -c security.secureboot=false
incus config device add artix-vm install-iso disk source=//home/sgbv/.iso/artix-base-openrc-20260729-x86_64.iso boot.priority=10
incus start artix-vm
incus console artix-vm --type=vga
}
main "$@"

24
.local/bin/volume-notify Executable file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-08-04 05:01 +0100
set -euo pipefail
if [ "$1" = "toggle" ]; then
pamixer -t
elif [[ "$1" == +* ]]; then
val="${1#+}"
pamixer -i "${val%\%}"
elif [[ "$1" == -* ]]; then
val="${1#-}"
pamixer -d "${val%\%}"
fi
volume=$(pamixer --get-volume)
muted=$(pamixer --get-mute)
if [ "$muted" = "true" ]; then
notify-send -h string:x-canonical-private-synchronous:volume -u low "Volume: Muted"
else
notify-send -h string:x-canonical-private-synchronous:volume -u low "Volume: ${volume}%"
fi

14
.local/bin/waybar-net Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
if ! vnstat -i wlan0 --oneline >/dev/null 2>&1; then
echo "{\"text\": \"Offline\", \"tooltip\": \"vnstat daemon unavailable\"}"
exit 0
fi
raw_data=$(vnstat -i wlan0 --oneline)
rx_today=$(echo "$raw_data" | cut -d';' -f4)
tx_today=$(echo "$raw_data" | cut -d';' -f5)
total_today=$(echo "$raw_data" | cut -d';' -f6)
echo "{\"text\": \"↓ $rx_today ↑ $tx_today\", \"tooltip\": \"today total: $total_today\"}"

18
.local/bin/weather Executable file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: 2026-07-18 11:26 +0100
set -euo pipefail
# DEFINE API ENDPOINT
# store the target url and authentication parameter in a shell variable
readonly API_URL="http://api.weatherapi.com/v1/current.json?key=d06994d0d97744ae89d172923261707&q=Idogbo&aqi=no"
# FETCH AND PARSE WEATHER PAYLOAD
# curl fetches the data silently and pipes the stream directly into jq for extraction
curl -s "$API_URL" | jq -r '
"Location: \(.location.name), \(.location.region), \(.location.country)",
"Local Time: \(.location.localtime)",
"Temperature: \(.current.temp_c)°C",
"Condition: \(.current.condition.text)"
'

1
.zsh/.functions/cdc Normal file
View file

@ -0,0 +1 @@
cd "$1" && clear;

47
.zsh/.functions/copy Normal file
View file

@ -0,0 +1,47 @@
# PIPED INPUT HANDLING
# read directly from standard input when no arguments are provided
if [ ! -t 0 ] && [ $# -eq 0 ]; then
wl-copy
return 0
fi
# ARGUMENT VALIDATION
# ensure at least one file argument is passed when not piping
if [ $# -eq 0 ]; then
echo "usage: copy <file...> or <command> | copy" >&2
return 1
fi
# FILE EXISTENCE CHECK
# verify the target argument is a readable file
if [ ! -r "$1" ]; then
echo "error: cannot read file '$1'" >&2
return 1
fi
# MIME TYPE DETECTION
# inspect the file header to determine the correct clipboard target
local mime_type
mime_type=$(file --mime-type -b "$1")
# CLIPBOARD DISPATCH
# route the file to wl-copy with the appropriate target flags
case "$mime_type" in
image/png|image/jpeg|image/webp|image/gif|image/bmp)
wl-copy -t "$mime_type" < "$1"
echo "copied $1 ($mime_type) to clipboard."
;;
image/svg+xml)
if command -v rsvg-convert >/dev/null 2>&1; then
rsvg-convert "$1" | wl-copy -t image/png
echo "rasterised $1 to png and copied to clipboard."
else
wl-copy -t image/svg+xml < "$1"
echo "copied $1 (raw svg) to clipboard. install librsvg for automatic png rasterisation."
fi
;;
*)
cat "$@" | wl-copy
echo "copied text content ($# file(s)) to clipboard."
;;
esac

View file

@ -0,0 +1,4 @@
local branch
branch=$(git branch --show-current)
git fetch origin &&
git reset --hard "origin/$branch"

5
.zsh/.functions/git.fire Normal file
View file

@ -0,0 +1,5 @@
local branch="fire-$(date +%Y%m%d-%H%M)"
git checkout -b "$branch" &&
git add -A &&
git commit -m "FIRE EMERGENCY: $branch" --no-verify &&
git push -u origin "$branch"

1
.zsh/.functions/git.grab Normal file
View file

@ -0,0 +1 @@
git checkout "$1" -- "$2"

8
.zsh/.functions/git.lazy Normal file
View file

@ -0,0 +1,8 @@
if [ -z "$1" ]; then
echo "Usage: git.lazy \"commit message\""
return 1
fi
git add -A &&
git commit -m "$1" &&
git push

7
.zsh/.functions/git.nuke Normal file
View file

@ -0,0 +1,7 @@
if [ -z "$1" ]; then
echo "Usage: git.nuke <branch>"
return 1
fi
git branch -D "$1"
git push origin --delete "$1"

11
.zsh/.functions/hl-bar Normal file
View file

@ -0,0 +1,11 @@
# HISTOGRAM RENDERING
# render horizontal bar chart of account balances
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" histogram --journal "$journal" "${@:-expenses}"
else
echo "warning: hledger-charts not found, defaulting to native percentage breakdown" >&2
hledger -f "$journal" balance "${@:-expenses}" --percent
fi

9
.zsh/.functions/hl-debt Normal file
View file

@ -0,0 +1,9 @@
# DEBT AND LIABILITY TRACKING
# view loan balances over time using inverted multi-line sparklines
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" trend --journal "$journal" -i -r 2 "${@:-liabilities}"
else
hledger -f "$journal" balance -M "${@:-liabilities}" --invert
fi

11
.zsh/.functions/hl-group Normal file
View file

@ -0,0 +1,11 @@
# GROUPED BAR RENDERING
# render monthly bar comparisons by account
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" grouped --journal "$journal" "${@:-expenses}"
else
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
hledger -f "$journal" balance -M "${@:-expenses}"
fi

10
.zsh/.functions/hl-inc Normal file
View file

@ -0,0 +1,10 @@
# INCOME DISTRIBUTION
# view earnings breakdown with inverted mathematical signs for positive display
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" pie --journal "$journal" -i "${@:-income}"
else
hledger -f "$journal" balance "${@:-income}" --invert
fi

11
.zsh/.functions/hl-pie Normal file
View file

@ -0,0 +1,11 @@
# PIE AND PROPORTIONAL RENDERING
# render proportional ascii bar and distribution chart
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" pie --journal "$journal" "${@:-expenses}"
else
echo "warning: hledger-charts not found, defaulting to native percentage breakdown" >&2
hledger -f "$journal" balance "${@:-expenses}" --percent
fi

11
.zsh/.functions/hl-stack Normal file
View file

@ -0,0 +1,11 @@
# STACKED BAR RENDERING
# render stacked proportional bars across time periods
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" stacked --journal "$journal" "${@:-expenses}"
else
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
hledger -f "$journal" balance -M "${@:-expenses}"
fi

11
.zsh/.functions/hl-trend Normal file
View file

@ -0,0 +1,11 @@
# TREND RENDERING
# render multi-period balance trends with unicode sparklines
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
if [[ -x "$chart_script" ]]; then
"$chart_script" trend --journal "$journal" "${@:-expenses}"
else
echo "warning: hledger-charts not found, defaulting to monthly tabular matrix" >&2
hledger -f "$journal" balance -M "${@:-expenses}"
fi

14
.zsh/.functions/hl-view Normal file
View file

@ -0,0 +1,14 @@
# GRAPHICAL IMAGE RENDERING
# generate matplotlib png image and open immediately with imv
local subcommand="${1:-pie}"
shift 2>/dev/null || true
local chart_script="$HOME/.local/bin/hledger-charts"
local journal="${HLEDGER_JOURNAL:-$HOME/obsidian/finances/journals/2026.journal}"
local tmp_image="/tmp/hledger_chart.png"
if [[ -x "$chart_script" ]]; then
"$chart_script" "$subcommand" --journal "$journal" --png "$tmp_image" --theme "${HL_THEME:-dark_background}" "${@:-expenses}" && imv "$tmp_image"
else
echo "error: hledger-charts binary not executable at $chart_script" >&2
return 1
fi

11
.zsh/.functions/lanscan Normal file
View file

@ -0,0 +1,11 @@
local iface_info=$(ip -o -f inet addr show | awk '/scope global/ {print $2, $4}' | head -1)
local iface=$(echo $iface_info | awk '{print $1}')
local subnet=$(echo $iface_info | awk '{print $2}')
if [ -z "$subnet" ]; then
echo "❌ No active network connection found."
return 1
fi
echo "🔍 Scanning subnet \033[33m$subnet\033[0m on \033[33m$iface\033[0m..."
doas nmap -sn -n "$subnet" | grep -E "Nmap scan|MAC Address" | sed -e 's/Nmap scan report for /IP: /g' -e 's/MAC Address: /MAC: /g'

1
.zsh/.functions/mkcd Normal file
View file

@ -0,0 +1 @@
mkdir -p "$1" && cd "$1"

46
.zsh/.functions/nbash Normal file
View file

@ -0,0 +1,46 @@
local target="${1:-}"
if [[ -z "$target" ]]; then
echo "usage: nbash <new_script_name>"
return 1
fi
# PREVENT OVERWRITES
# exit immediately if the target file already exists
if [[ -f "$target" ]]; then
echo "error: file '$target' already exists."
return 1
fi
# CREATE DIRECTORIES
# ensure the parent directory structure exists before creating the file
mkdir -p "$(dirname "$target")"
# TEMPLATE SELECTION
# use the external template if available, otherwise generate inline
local template="$HOME/.bash_templates/script.sh"
if [[ -f "$template" ]]; then
cp "$template" "$target"
else
cat > "$target" <<EOF
#!/usr/bin/env bash
# AUTHOR: Daesorin
# CREATED: $(date +"%F %R %z")
set -euo pipefail
main() {
# main execution block
echo "Hello, world!"
}
main "\$@"
EOF
fi
# FILE PERMISSIONS
# grant read, write, and execute to the owner, and read to others
chmod u=rwx,go=r "$target"
# LAUNCH EDITOR
# open the target in the user environment editor
${EDITOR:-vim} "$target"

5
.zsh/.functions/netspeed Normal file
View file

@ -0,0 +1,5 @@
echo "\033[36m📊 Network Interface Speeds:\033[0m"
for iface in $(ip link show | grep "^[0-9]" | awk '{print $2}' | tr -d ':' | grep -v "lo"); do
speed=$(cat /sys/class/net/$iface/speed 2>/dev/null)
[[ -n "$speed" && "$speed" != "-1" ]] && echo "├── $iface: ${speed} Mb/s"
done

47
.zsh/.functions/npython Normal file
View file

@ -0,0 +1,47 @@
local target="${1:-}"
if [[ -z "$target" ]]; then
echo "usage: npython <new_script_name.py>"
return 1
fi
# PREVENT OVERWRITES
# exit immediately if the target file already exists
if [[ -f "$target" ]]; then
echo "error: file '$target' already exists."
return 1
fi
# CREATE DIRECTORIES
# ensure the parent directory structure exists before creating the file
mkdir -p "$(dirname "$target")"
# TEMPLATE SELECTION
# use the external template if available, otherwise generate inline
local template="$HOME/.python_templates/script.py"
if [[ -f "$template" ]]; then
cp "$template" "$target"
else
cat > "$target" <<EOF
#!/usr/bin/env python3
# AUTHOR: Daesorin
# CREATED: $(date +"%F %R %z")
import sys
def main():
# main execution block
print("Hello, world!")
return 0
if __name__ == '__main__':
sys.exit(main())
EOF
fi
# FILE PERMISSIONS
# grant read, write, and execute to the owner, and read to others
chmod u=rwx,go=r "$target"
# LAUNCH EDITOR
# open the target in the user environment editor
${EDITOR:-vim} "$target"

11
.zsh/.functions/pingall Normal file
View file

@ -0,0 +1,11 @@
local hosts=("8.8.8.8" "1.1.1.1" "codeberg.org" "archlinux.org")
echo "\033[36m🌐 Testing connectivity...\033[0m"
for host in "${hosts[@]}"; do
if ping -c 1 -W 2 "$host" &>/dev/null; then
echo "✅ $host: reachable"
else
echo "❌ $host: unreachable"
fi
done

View file

@ -0,0 +1,6 @@
orphans=$(pacman -Qtdq)
if [[ -n "$orphans" ]]; then
sudo pacman -Rns $orphans
else
printf "no orphan packages found.\n"
fi

3
.zsh/.functions/portscan Normal file
View file

@ -0,0 +1,3 @@
[[ -z "$1" ]] && { echo "Usage: portscan <host>"; return 1; }
nmap "$1"

3
.zsh/.functions/serve Normal file
View file

@ -0,0 +1,3 @@
# LOCAL WEB SERVER
# host current directory on specified port or default to 8000
python3 -m http.server "${1:-8000}"

View file

@ -0,0 +1,5 @@
if command -v speedtest-cli &> /dev/null; then
speedtest-cli
else
echo "Install speedtest-cli: sudo pacman -S speedtest-cli"
fi

35
.zsh/.functions/sshkeygen Normal file
View file

@ -0,0 +1,35 @@
# SSH KEY GENERATOR
# creates an ed25519 keypair and copies the public key to the clipboard
local email=$1
local keytype="ed25519"
local keyname=${2:-id_ed25519}
if [[ -z "$email" ]]; then
echo "Usage: sshkeygen <email> [keyname]"
return 1
fi
local ssh_dir="$HOME/.ssh"
mkdir -p "$ssh_dir"
chmod 700 "$ssh_dir"
local keypath="$ssh_dir/$keyname"
if [[ -f "$keypath" ]]; then
echo "\033[31m❌ Key already exists at $keypath\033[0m"
return 1
fi
ssh-keygen -t "$keytype" -C "$email" -f "$keypath" -N ""
echo "\033[32m✅ SSH key created at $keypath\033[0m"
# CLIPBOARD INTEGRATION
# auto-copy the public key based on the active display server
if command -v wl-copy &> /dev/null; then
cat "$keypath.pub" | wl-copy
echo "\033[36m📋 Public key copied to clipboard (Wayland)!\033[0m"
elif command -v xclip &> /dev/null; then
cat "$keypath.pub" | xclip -selection clipboard
echo "\033[36m📋 Public key copied to clipboard (X11)!\033[0m"
else
cat "$keypath.pub"
fi

View file

@ -0,0 +1,4 @@
# SSH KEY LISTER
# displays all public keys currently stored in the local ssh directory
echo "\033[36m📂 Existing SSH keys in ~/.ssh:\033[0m"
ls -lh "$HOME/.ssh/"*.pub 2>/dev/null || echo "No SSH keys found."

18
.zsh/.functions/venv Normal file
View file

@ -0,0 +1,18 @@
# VIRTUAL ENVIRONMENT MANAGER
# creates a new environment if it does not exist, then activates it
local venv_dir="${1:-.venv}"
if [[ ! -d "$venv_dir" ]]; then
echo "initialising new virtual environment in ./$venv_dir..."
python3 -m venv "$venv_dir"
fi
# ACTIVATE ENVIRONMENT
# source the activation script into the current shell session
if [[ -f "$venv_dir/bin/activate" ]]; then
source "$venv_dir/bin/activate"
echo "activated $venv_dir"
else
echo "error: activation script not found in ./$venv_dir"
return 1
fi

7
.zsh/.zshrc Normal file
View file

@ -0,0 +1,7 @@
[[ -o interactive ]] || return
if [[ -d "$ZDOTDIR" ]]; then
for config in "$ZDOTDIR"/*.zsh(N); do
source "$config"
done
fi

3
.zsh/00-umask.zsh Normal file
View file

@ -0,0 +1,3 @@
# FILE CREATION MASK
# set default permissions for newly created files and directories
umask 0077

14
.zsh/10-path.zsh Normal file
View file

@ -0,0 +1,14 @@
typeset -U path PATH
path=(
"$HOME/.local/bin"
"$HOME/.filen-cli/bin"
"$HOME/.cargo/bin"
"$HOME/go/bin"
"$HOME/.npm-global/bin"
/usr/local/bin
/usr/lib
$path
)
export PATH

23
.zsh/20-options.zsh Normal file
View file

@ -0,0 +1,23 @@
# LINTING DIRECTIVES
# ignore file during static analysis due to lack of zsh support
# shellcheck disable=all
setopt AUTO_CD # enter dir without prepending `cd`
setopt AUTO_PUSHD
setopt PUSHD_IGNORE_DUPS
setopt PUSHD_SILENT
setopt CDABLE_VARS
setopt NO_CASE_GLOB
setopt COMPLETE_IN_WORD
setopt ALWAYS_TO_END
setopt AUTO_LIST
setopt GLOB_DOTS
setopt hist_ignore_space
# GLOBBING OPTIONS
# set expansion rules
setopt EXTENDED_GLOB
setopt NUMERIC_GLOB_SORT
# SAFETY OPTIONS
# prevent accidental file overwriting
setopt NO_CLOBBER

22
.zsh/30-history.zsh Normal file
View file

@ -0,0 +1,22 @@
# HISTORY FILE CONFIGURATION
# define history location and limits
HISTFILE="$ZDOTDIR/.history"
HISTSIZE=1000
SAVEHIST=2000
# HISTORY OPTIONS
# set recording and retrieval parameters
setopt APPEND_HISTORY
setopt INC_APPEND_HISTORY
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_ALL_DUPS
setopt HIST_SAVE_NO_DUPS
setopt HIST_REDUCE_BLANKS
setopt HIST_FIND_NO_DUPS
setopt HIST_VERIFY
setopt SHARE_HISTORY
setopt EXTENDED_HISTORY
# HISTORY ALIASES
# define output format for history review
alias hist='fc -l -t "%Y-%m-%d %H:%M:%S"'

29
.zsh/40-env.zsh Normal file
View file

@ -0,0 +1,29 @@
# shellcheck disable=all
# SYSTEM DEFAULTS
# define default editors and pagers
export EDITOR="nvim"
export VISUAL="nvim"
export PAGER="bat"
export MANPAGER="nvim +Man!"
export BAT_THEME="base16"
export NODE_OPTIONS=--no-deprecation
# HLEDGER CONFIGURATION
# point system commands to the primary journal file
export LEDGER_FILE="$HOME/obsidian/finances/journals/2026.journal"
# PAGER COLOURS
# define terminal capability codes for less
export LESS_TERMCAP_mb=$'\E[1;31m'
export LESS_TERMCAP_md=$'\E[1;36m'
export LESS_TERMCAP_me=$'\E[0m'
export LESS_TERMCAP_so=$'\E[01;44;33m'
export LESS_TERMCAP_se=$'\E[0m'
export LESS_TERMCAP_us=$'\E[1;32m'
export LESS_TERMCAP_ue=$'\E[0m'
# VIDEODRIVER
export SDL_VIDEODRIVER=wayland
export W3M_DIR="$HOME/.config/w3m"
export W3M_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/w3m"

19
.zsh/50-completion.zsh Normal file
View file

@ -0,0 +1,19 @@
# COMPLETION INITIALISATION
# load and start the completion system
autoload -Uz compinit
fpath=(
/usr/share/zsh/site-functions
$fpath
)
compinit
# STYLING OPTIONS
# apply cache and interface rules to completions
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
zstyle ':completion:*' menu select
zstyle ':completion:*' list-dirs-first true
zstyle ':completion:*' use-cache yes
zstyle ':completion:*' cache-path ~/.cache/zsh
zstyle ':completion:*' group-name ''
zstyle ':completion:*' smart-case yes
zstyle ':completion:*' completer _expand _complete _correct _approximate

30
.zsh/60-plugins.zsh Normal file
View file

@ -0,0 +1,30 @@
# STARSHIP INITIALISATION
# start the prompt
eval "$(starship init zsh)"
# ZOXIDE INITIALISATION
# start the smart directory jumper
if command -v zoxide >/dev/null 2>&1; then
eval "$(zoxide init zsh --cmd cd)"
fi
# TMUX CHECK
# verify active multiplexer sessions
if [ -n "$TMUX" ] && ! tmux info >/dev/null 2>&1; then
unset TMUX
unset TMUX_PANE
fi
# ZSH PLUGINS
# source external extension scripts
if [[ -f /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh ]]; then
source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
fi
if [[ -f /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
fi
if [[ -f /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]]; then
source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
fi

14
.zsh/70-binds.zsh Normal file
View file

@ -0,0 +1,14 @@
# INPUT MODES
# enforce vi keybindings
bindkey -e
# SEARCH BINDINGS
# map arrows to history search functions
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
# HELP MANUAL
# map keyboard shortcut to local runbook
unalias run-help 2>/dev/null
autoload -Uz run-help
bindkey '^[h' run-help

63
.zsh/80-aliases.zsh Normal file
View file

@ -0,0 +1,63 @@
# shellcheck disable=all
alias pm='sudo pacman'
alias pr='paru'
alias z='cd'
alias ls='ls --color=always'
alias lz='eza --group-directories-first'
alias l='eza -l --group-directories-first --git'
alias li='eza -laai --group-directories-first --git'
alias la='eza -la --group-directories-first --git'
alias lt='eza -l --tree --group-directories-first --git'
alias lta='eza -la --tree --group-directories-first --git'
alias lt2='eza --tree --level=2'
alias lt3='eza --tree --level=3'
alias cat='bat --paging=never'
alias bap='bat --paging=always'
alias b='bat'
alias cp='cp -iv'
alias mv='mv -iv'
alias rm='rm -Iv'
alias rmr='rm -Irv'
alias rmf='rm -frv'
alias mkdir='mkdir -pv'
alias ,='nvim'
alias ,,='sudo nvim'
alias grep='grep --color=auto'
alias diff='diff --color=auto'
alias ip='ip --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias chmod='chmod -v'
alias suchmod='sudo chmod -v'
alias cls='clear'
alias path='echo $PATH | tr ":" "\n"'
alias reload='exec bash'
alias -g L='| less'
alias -g G='| grep -i'
alias -g WC='| wc -l'
alias -g NUL='>/dev/null 2>&1'
alias -g E='2>&1'
alias -g C='; clear'
alias -g COPY='| wl-copy'
alias -g ANSICOPY='| ansifilter | wl-copy'
alias fullcharge='sudo tlp fullcharge'
alias paste='wl-paste'
alias ic='incus'
alias fastfetch='fastfetch --config examples/25.jsonc'
alias py='python3'
alias ipy='ipython'
alias zt='zathura'
alias sys='systemctl --user'
alias susys='sudo systemctl'

View file

@ -0,0 +1,10 @@
alias hl='hledger'
alias hlb='hledger bal'
alias hlbe='hledger bal Expenses'
alias hlbi='hledger bal Income'
alias hlbs='hledger balancesheet'
alias hlr='hledger reg'
alias hlis='hledger incomestatement'
alias hlu='hledger-ui'
alias hlw='hledger-web'
alias hlupdate='md-to-hledger > $HOME/obsidian/finances/journals/compiled.journal && hledger bal'

7
.zsh/82-tmux-aliases.zsh Normal file
View file

@ -0,0 +1,7 @@
alias tx='tmux'
alias txn='tmux new -s'
alias txnmain='tmux new -s main'
alias txa='tmux attach -t'
alias txkw='tmux kill-window -t'
alias txks='tmux kill-session -t'
alias txkp='tmux kill-pane -t'

21
.zsh/83-git-aliases.zsh Normal file
View file

@ -0,0 +1,21 @@
alias gs='git status'
alias gst='git status -sb'
alias gl='git log --oneline --graph --decorate --all'
alias gd='git diff'
alias gds='git diff --staged'
alias ga='git add'
alias gaa='git add -A'
alias gc='git commit'
alias gcm='git commit -m'
alias gca='git commit -am'
alias gco='git checkout'
alias gb='git branch'
alias gm='git merge'
alias gp='git push'
alias gpl='git pull'
alias gun='git reset HEAD --'
alias dot='git dot'
alias git.last='git log -1 HEAD'
alias git.oops='git commit --amend --no-edit'
alias git.unstage='git reset HEAD --'
alias git.today="git log --since='24 hours ago' --oneline --graph --all"

Some files were not shown because too many files have changed in this diff Show more