Compare commits

...
10 Commits
Author SHA1 Message Date
coja 3ec2503f38 [Chore] cleanup 2026-08-13 03:36:03 +02:00
coja 1e39e0cb4b [Sync] replace the flat layout with the unified stow tree
Supersedes the old flat .config/ layout (last published 2026-06-28) with the
private repo's structure: one shared base plus per-host overlays.

- packages: common/ gui/ lw/ fl/ wm/ plus install.sh and bin/ tooling
  (dotsync, reconcile-hyde.sh)
- new README covering the layout, deploy order and the HyDE dependency
- current HyDE waybar rig (layouts/, cava), pi agent extensions, claude/
  config, tmux, presenterm, aichat roles
- fish: kp (keepassxc-cli + fzf picker, db path from $KP_DB) and
  bind_M_n_history (alt+1..9 recalls the nth history entry)
- drops cruft that should never have been tracked: the duplicate top-level
  .pi/ copy, btop.log, zellij config.kdl.bak, fish_variables
- .pi/agent/auth.json is gitignored; auth.json.example ships instead

Host-specific work sessions and the personal backlog stay in the private
tree. Endpoint locators in the llamacpp/whisper guides are placeholders
($SERVER, <own-domain>) — the guides themselves stay, since they are the
useful part.
2026-08-13 02:53:06 +02:00
coja f62cb40499 [Zelij] vim binds 2026-06-28 04:23:16 +02:00
coja a5a5bc1b1b [Pyradio] station update 2026-06-28 04:22:51 +02:00
coja 9d447e1cd0 [Tuis] update 2026-06-28 04:22:05 +02:00
coja e305c812ef [Fish] update 2026-06-28 04:21:07 +02:00
coja 71addd70ea [Cava] update 2026-06-28 04:20:12 +02:00
coja 077e5acba6 [Btop] conf update 2026-06-28 04:20:00 +02:00
coja 7a8a8fce6f [Pi] llms and keys 2026-06-28 04:19:53 +02:00
coja 0e07bcced0 [Git] gitignore 2026-06-28 04:18:55 +02:00
311 changed files with 12058 additions and 10880 deletions
-41
View File
@@ -1,41 +0,0 @@
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
alias grep='grep --color=auto'
alias fzf="fzf --preview color='always {}'"
alias pacs="sudo pacman -Syu"
alias nmaps="sudo nmap -sn 192.168.0.0/24"
alias mem="df -H --output=source,size,used,avail | grep 480G | sort -u"
alias kittyimg="kitten icat"
alias vpn="sudo wg-quick up wg0"
alias vpnoff="sudo wg-quick down wg0"
alias xremaps="sudo xremap ~/.config/xremap/config.yml"
alias llama="~/projects/llama.cpp/build/bin/llama-server -m /home/anon/projects/llama.cpp/models/Llama-3.2-3B-Instruct-F16.gguf"
alias dots="cd ~/.dotfiles/.config/ && nvim"
alias aliases="bat ~/.config/fish/config.fish"
# List Directory
alias ls="lsd"
alias l="ls -l"
alias la="ls -a"
alias lla="ls -la"
alias lt="ls --tree"
# Git
alias gs='git status'
alias gca='git add -p . && git commit'
alias gd="git diff --word-diff"
alias gl='git log --graph --show-signature'
alias gla="git log --all --decorate --oneline --graph"
alias gm='git merge'
alias gis='git status'
PS1='[\u@\h \W]\$'
set -o vi
eval "$(zoxide init --cmd cd bash)"
-2
View File
@@ -1,2 +0,0 @@
greet.txt
fish_variables
-28
View File
@@ -1,28 +0,0 @@
# see https://github.com/sigoden/aichat/blob/main/config.example.yaml
keybindings: vi
editor: nvim
model: duskadiy:Qwen3-Coder-30B-Instruct-IQ4_XS
clients:
- type: openai-compatible
name: local
api_base: http://192.168.0.204:11343/v1
models:
- name: Qwen3.5-27B-UD-Q3_K_XL
- name: Qwen3.5-9B-Q8_0
- name: Qwen3-Coder-30B-Instruct-IQ4_XS
- name: GLM-4.7-Flash-UD-Q3_K_XL
- name: DeepSeek-Coder-V2-Lite-Instruct-Q8_0
- name: gemma-4-E4B-it-UD-Q8_K_XL
- name: gemma-4-26B-A4B-it-UD-IQ4_XS
- type: openai-compatible
name: duskadiy
api_base: https://llm.duskadiy.com/api/v1
api_key: ${LLM_API_KEY}
models:
- name: Qwen3.5-27B-UD-Q3_K_XL
- name: Qwen3.5-9B-Q8_0
- name: Qwen3-Coder-30B-Instruct-IQ4_XS
- name: GLM-4.7-Flash-UD-Q3_K_XL
- name: DeepSeek-Coder-V2-Lite-Instruct-Q8_0
- name: gemma-4-E4B-it-UD-Q8_K_XL
- name: gemma-4-26B-A4B-it-UD-IQ4_XS
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-40
View File
@@ -1,40 +0,0 @@
2024/11/19 (20:36:51) | ===> btop++ v.1.4.0
2024/11/19 (20:36:51) | WARNING: Intel GPU: Failed to initialize PMU
2024/11/19 (20:37:28) | INFO: Logger set to INFO
2024/11/26 (00:47:27) | ===> btop++ v.1.4.0
2024/11/26 (00:47:27) | WARNING: Intel GPU: Failed to initialize PMU
2024/11/26 (00:47:32) | ===> btop++ v.1.4.0
2024/11/26 (00:47:32) | WARNING: Intel GPU: Failed to initialize PMU
2024/12/16 (20:32:28) | ===> btop++ v.1.4.0
2024/12/16 (20:32:28) | WARNING: Intel GPU: Failed to initialize PMU
2025/01/06 (01:34:00) | ===> btop++ v.1.4.0
2025/01/06 (01:34:00) | WARNING: Intel GPU: Failed to initialize PMU
2025/02/23 (01:19:32) | ===> btop++ v.1.4.0
2025/02/23 (01:19:32) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/12 (19:08:38) | ===> btop++ v.1.4.0
2025/03/12 (19:08:38) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/13 (00:34:24) | ===> btop++ v.1.4.0
2025/03/13 (00:34:24) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/19 (17:51:06) | ===> btop++ v.1.4.0
2025/03/19 (17:51:06) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/22 (05:43:25) | ===> btop++ v.1.4.0
2025/03/22 (05:43:25) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/22 (05:43:39) | ===> btop++ v.1.4.0
2025/03/22 (05:43:39) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/24 (02:13:29) | ===> btop++ v.1.4.0
2025/03/24 (02:13:29) | WARNING: Intel GPU: Failed to initialize PMU
2025/03/25 (22:21:48) | ===> btop++ v.1.4.0
2025/03/25 (22:21:48) | WARNING: Intel GPU: Failed to initialize PMU
-13
View File
@@ -1,13 +0,0 @@
[color]
gradient = 1
gradient_count = 8
gradient_color_1 = '#332952'
gradient_color_2 = '#463A6B'
gradient_color_3 = '#7D4B5F'
gradient_color_4 = '#8F576D'
gradient_color_5 = '#A36578'
gradient_color_6 = '#C27A90'
gradient_color_7 = '#E69A9D'
gradient_color_8 = '#F0AAAD'
@@ -1,11 +0,0 @@
[color]
gradient = 1
gradient_color_1 = '#81c8be'
gradient_color_2 = '#99d1db'
gradient_color_3 = '#85c1dc'
gradient_color_4 = '#8caaee'
gradient_color_5 = '#ca9ee6'
gradient_color_6 = '#f4b8e4'
gradient_color_7 = '#ea999c'
gradient_color_8 = '#e78284'
-13
View File
@@ -1,13 +0,0 @@
[color]
background = '#303446'
gradient = 1
gradient_color_1 = '#81c8be'
gradient_color_2 = '#99d1db'
gradient_color_3 = '#85c1dc'
gradient_color_4 = '#8caaee'
gradient_color_5 = '#ca9ee6'
gradient_color_6 = '#f4b8e4'
gradient_color_7 = '#ea999c'
gradient_color_8 = '#e78284'
@@ -1,11 +0,0 @@
[color]
gradient = 1
gradient_color_1 = '#179299'
gradient_color_2 = '#04a5e5'
gradient_color_3 = '#209fb5'
gradient_color_4 = '#1e66f5'
gradient_color_5 = '#8839ef'
gradient_color_6 = '#ea76cb'
gradient_color_7 = '#e64553'
gradient_color_8 = '#d20f39'
-13
View File
@@ -1,13 +0,0 @@
[color]
background = '#eff1f5'
gradient = 1
gradient_color_1 = '#179299'
gradient_color_2 = '#04a5e5'
gradient_color_3 = '#209fb5'
gradient_color_4 = '#1e66f5'
gradient_color_5 = '#8839ef'
gradient_color_6 = '#ea76cb'
gradient_color_7 = '#e64553'
gradient_color_8 = '#d20f39'
@@ -1,11 +0,0 @@
[color]
gradient = 1
gradient_color_1 = '#8bd5ca'
gradient_color_2 = '#91d7e3'
gradient_color_3 = '#7dc4e4'
gradient_color_4 = '#8aadf4'
gradient_color_5 = '#c6a0f6'
gradient_color_6 = '#f5bde6'
gradient_color_7 = '#ee99a0'
gradient_color_8 = '#ed8796'
-13
View File
@@ -1,13 +0,0 @@
[color]
background = '#24273a'
gradient = 1
gradient_color_1 = '#8bd5ca'
gradient_color_2 = '#91d7e3'
gradient_color_3 = '#7dc4e4'
gradient_color_4 = '#8aadf4'
gradient_color_5 = '#c6a0f6'
gradient_color_6 = '#f5bde6'
gradient_color_7 = '#ee99a0'
gradient_color_8 = '#ed8796'
@@ -1,11 +0,0 @@
[color]
gradient = 1
gradient_color_1 = '#94e2d5'
gradient_color_2 = '#89dceb'
gradient_color_3 = '#74c7ec'
gradient_color_4 = '#89b4fa'
gradient_color_5 = '#cba6f7'
gradient_color_6 = '#f5c2e7'
gradient_color_7 = '#eba0ac'
gradient_color_8 = '#f38ba8'
-13
View File
@@ -1,13 +0,0 @@
[color]
background = '#1e1e2e'
gradient = 1
gradient_color_1 = '#94e2d5'
gradient_color_2 = '#89dceb'
gradient_color_3 = '#74c7ec'
gradient_color_4 = '#89b4fa'
gradient_color_5 = '#cba6f7'
gradient_color_6 = '#f5c2e7'
gradient_color_7 = '#eba0ac'
gradient_color_8 = '#f38ba8'
-90
View File
@@ -1,90 +0,0 @@
set -gx QT_FONT_DPI 96
set -gx EDITOR nvim
set -gx PAGER less
set -gx PATH "/run/user/1000/fnm_multishells/14287_1763490645306/bin" $PATH;
set -gx FNM_MULTISHELL_PATH "/run/user/1000/fnm_multishells/14287_1763490645306";
set -gx FNM_VERSION_FILE_STRATEGY "local";
set -gx FNM_DIR "/home/anon/.local/share/fnm";
set -gx FNM_LOGLEVEL "info";
set -gx FNM_NODE_DIST_MIRROR "https://nodejs.org/dist";
set -gx FNM_COREPACK_ENABLED "false";
set -gx FNM_RESOLVE_ENGINES "true";
set -gx FNM_ARCH "x64";
set -Ux MANPAGER "nvim +Man!"
set -x NEWT_COLORS 'root=black,black;window=black,black;border=white,black;listbox=white,black;label=blue,black;checkbox=red,black;title=green,black;button=white,red;actsellistbox=white,red;actlistbox=white,gray;compactbutton=white,gray;actcheckbox=white,blue;entry=lightgray,black;textbox=blue,black' nmtui
#set -Ux LLM_API_KEY "api_key"
alias dmz="cat ~/.config/fish/dmz.txt"
alias matrix="~/projects/random-clones/matrix/matrix"
alias nmatrix="neo-matrix -DS 3"
# LLMs
alias llamasrv="~/projects/pkgs/llama.cpp/build/bin/llama-server"
abbr llamastart "llamasrv --host 0.0.0.0 --port 11343 --models-max 3 --models-preset ~/Documents/models/config.ini"
# Shorts
alias fzf="fzf --multi --preview 'bat --style=numbers --color=always {}' | xargs -n 1 nvim"
alias grep="grep --color=auto"
# Git
alias gs="git status --short"
alias gca="git add -p . && git commit"
alias gd="git diff --word-diff"
alias gl="git log --graph --show-signature"
alias gla="git log --all --decorate --oneline --graph"
alias gls="serie"
alias gm="git merge"
alias gm="git merge"
# List Directory
alias ls="lsd"
alias l="ls -l"
alias la="ls -a"
alias lla="ls -la"
alias lt="ls --tree"
alias cdots="cd ~/.dotfiles/.config/"
alias cpnotes="cd ~/sync/PersonalNotes/"
abbr dots "cdots && nvim"
abbr pnotes "cpnotes && nvim"
abbr todo "cpnotes && nvim ToDoNext.md"
abbr cat "bat -p"
abbr copy "wl-copy"
abbr img "kitten icat"
abbr fm "yazi" ## file manager
abbr lg "lazygit"
abbr mkdir "mkdir -p"
abbr faillock "sudo faillock --reset"
abbr ipadd "sudo ip route add 192.168.0.234 dev wg0"
abbr xremaps "sudo xremap --watch ~/.config/xremap/config.yml"
abbr pacs "sudo pacman -Syu --noconfirm"
abbr yays "yay --noconfirm --sudoloop"
abbr nmaps "sudo nmap -sn 192.168.0.0/24"
abbr scrcpyc 'scrcpy -wSK -m 1920 --window-borderless --always-on-top --power-off-on-close'
abbr scrcpys 'scrcpy -wS --power-off-on-close'
abbr aliases "bat ~/.config/fish/config.fish"
abbr tts "tt -notheme -bold -showwpm -json"
# Handy change dir shortcuts
abbr .. "cd .."
abbr ... "cd ../.."
abbr .3 "cd ../../.."
abbr .4 "cd ../../../.."
abbr .5 "cd ../../../../.."
zoxide init --cmd cd fish | source
export PATH="$HOME/.cargo/bin:$PATH"
thefuck --alias | source
# Created by `pipx` on 2025-05-31 20:14:06
set PATH $PATH /home/anon/.local/bin
# pnpm
set -gx PNPM_HOME "/home/anon/.local/share/pnpm"
if not string match -q -- $PNPM_HOME $PATH
set -gx PATH "$PNPM_HOME" $PATH
end
# pnpm end
-7
View File
@@ -1,7 +0,0 @@
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR --export EDITOR:nvim
SETUVAR --export MANPAGER:nvim\x20\x2bMan\x21
SETUVAR --export MANROFFOPT:\x2dP\x20\x2dc
SETUVAR TMP:/tmp/tmp\x2eYYqSsNsses
SETUVAR __fish_initialized:4300
-3
View File
@@ -1,3 +0,0 @@
function askllama --description 'answer from llama'
~/projects/pkgs/llama.cpp/build/bin/llama-simple -m ~/Documents/models/Qwen3-4B-Instruct-2507.Q4_K_M.gguf $(string join '+' $argv[1..])
end
-3
View File
@@ -1,3 +0,0 @@
function clis --description 'fzf select cli'
eval (string split "\n" (cat $HOME/.config/misc/clis.txt) | bashs "fzf --multi")
end
@@ -1,7 +0,0 @@
function fish_greeting
#curl "wttr.in/Belgrade?format=%l:+%c+%t+|+%h+|+%w+|+(%%M+%m)\n"
cat ~/.config/fish/greet.txt ## populated by cronjob
date +"📅 %d. %b 🕗 %H:%M - %A"
end
-3
View File
@@ -1,3 +0,0 @@
function sshf --description 'fzf select for ssh hosts'
ssh $(grep Host -w ~/.ssh/config | awk '{print $2}' | bashs "fzf --multi")
end
-3
View File
@@ -1,3 +0,0 @@
function tuis --description 'fzf select tui'
eval (string split "\n" (cat $HOME/.config/misc/tuis.txt) | bashs "fzf --multi")
end
-27
View File
@@ -1,27 +0,0 @@
#!/bin/bash
# Get the current battery percentage
battery_percentage=$(cat /sys/class/power_supply/BAT0/capacity)
# Get the battery status (Charging or Discharging)
battery_status=$(cat /sys/class/power_supply/BAT0/status)
# Define the battery icons for each 10% segment
battery_icons=("󰂃" "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰁹")
# Define the charging icon
charging_icon="󰂄"
# Calculate the index for the icon array
icon_index=$((battery_percentage / 10))
# Get the corresponding icon
battery_icon=${battery_icons[icon_index]}
# Check if the battery is charging
if [ "$battery_status" = "Charging" ]; then
battery_icon="$charging_icon"
fi
# Output the battery percentage and icon
echo "$battery_percentage% $battery_icon"
-42
View File
@@ -1,42 +0,0 @@
#!   ░▒▒▒░░░▓▓          ___________
#! ░░▒▒▒░░░░░▓▓        //___________/
#! ░░▒▒▒░░░░░▓▓     _   _ _    _ _____
#! ░░▒▒░░░░░▓▓▓▓▓▓ | | | | |  | |  __/
#! ░▒▒░░░░▓▓   ▓▓ | |_| | |_/ /| |___
#!  ░▒▒░░▓▓   ▓▓   \__  |____/ |____/
#!    ░▒▓▓   ▓▓  //____/
# $LOCKSCREEN = hyprlock # preferred LOCKSCREEN e.g swaylock
$LOCKSCREEN = lockscreen.sh # Calls $LOCKSCREEN set from hyprland
general {
lock_cmd = pidof hyprlock || hyprlock
on_unlock_cmd = bash -c 'notify-send "Welcome back!"'
before_sleep_cmd = loginctl lock-session
after_sleep_cmd = bash -c 'brightnessctl -r; brightnessctl -d tpacpi::kbd_backlight set 1; hyprctl dispatch dpms on'
}
listener {
timeout = 180
on-timeout = bash -c 'brightnessctl -s set 50'
on-resume = bash -c 'brightnessctl -r'
}
listener {
timeout = 250 # 5min
on-timeout = loginctl lock-session # lock screen when timeout has passed
}
listener {
timeout = 350 # 3.6 minutes
on-timeout = bash -c 'brightnessctl -r; brightnessctl -s set 10; brightnessctl -d tpacpi::kbd_backlight set 0; hyprctl dispatch dpms off'
on-resume = bash -c 'brightnessctl -r; brightnessctl -d tpacpi::kbd_backlight set 1; hyprctl dispatch dpms on'
}
listener {
timeout = 550 # 5.5min
on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed
on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired.
}
-116
View File
@@ -1,116 +0,0 @@
#! █░█ █▄█ █▀█ █▀█ █░░ █▀█ █▀▀ █▄▀
#! █▀█ ░█░ █▀▀ █▀▄ █▄▄ █▄█ █▄▄ █░█
#*  Hyprlock Configuration File 
$fontColor = rgba(255,255,255,0.5)
$fontFamily = SourceCodeVF ExtraBold
$timeFontFamily = Cantarell ExtraBold
general {
no_fade_in = true
grade = 1
ignore_empty_input = true
text_trim = true
fade_in_time = 0.8
disable_loading_bar = 0
}
background {
monitor =
path = ~/sync/Drive/pics/foss-world-crop.png
blur_passes = 2
blur_size = 4
contrast = 0.7
brightness = 0.3
vibrancy = 0.15
vibrancy_darkness = 0.5
}
image {
monitor =
path = ~/sync/Drive/pics/matrixom-crop.png
position = 0, 80
reload_time = -1
reload_cmd =
}
input-field {
monitor =
size = 20%, 5%
position = 0, -60
outline_thickness = 3
dots_size = 0.08
dots_spacing = 0.2
dots_center = true
outer_color = rgba(0,0,0,0.1)
inner_color = $fontColor
fade_on_empty = true
}
label {
monitor =
text = cmd[update:5000] echo "<span alpha='40%'>$(uptime -p)</span>"
color = $fontColor
font_family = $fontFamily
font_size = 15
position = 0, 350
}
label {
monitor =
text = cmd[update:300000] echo "<span alpha='50%'>$(cat ~/.config/fish/greet.txt)</span>"
color = $fontColor
font_family = $fontFamily
font_size = 14
position = 0, 310
}
label {
monitor =
text = cmd[update:1000] echo "<span alpha='50%'>$(date +"%H:%M")</span>"
color = $fontColor
font_family = $timeFontFamily
font_size = 120
position = 0, -180
animation = pulse 2s infinite
opacity = 0.5
shadow_passes = 1
shadow_size = 1
shadow_color = rgba(0, 0, 0, 0.5)
}
label {
monitor =
text = cmd[update:1000] echo "<span alpha='50%'>$(LC_TIME=en_US.UF-8 date +'%A, %B %d')</span>"
color = $fontColor
font_family = $fontFamily
font_size = 25
position = 0, -300
}
label {
monitor =
text = cmd[update:1000] echo -e "<span alpha='50%'>$(~/.config/hypr/battery.sh)</span>"
color = $fontColor
font_family = $fontFamily
font_size = 14
position = -50, 30
halign = right
valign = bottom
}
label {
monitor =
color = $fontColor
text = cmd[] echo "<span alpha='50%'>⏻ </span>"
font-size=24
onclick = systemctl poweroff
position = 50, 30
halign = left
valign = bottom
}
-138
View File
@@ -1,138 +0,0 @@
# HyDE default layout
# Wallbash generated layout
# Local Variables
$fontFamily = JetBrainsMono Nerd Font
$fn_greet=echo "Good $(date +%H | awk '{if ($1 < 12) print "Morning"; else if ($1 < 18) print "Afternoon"; else print "Evening"}'), ${USER}"
# GENERAL
general {
no_fade_in = false
grace = 0
disable_loading_bar = true
}
# BACKGROUND
background {
monitor =
path = $BACKGROUND_PATH
blur_passes = 2
}
# TIME
label {
monitor =
text = $TIME
color = $wallbash_txt1_rgba
font_size = 90
font_family = $fontFamily
position = -30, 0
halign = right
valign = top
}
# DATE
label {
monitor =
text = cmd[update:43200000] date +"%A, %d %B %Y"
color = $wallbash_txt2_rgba
font_size = 25
font_family = $fontFamily
position = -30, -150
halign = right
valign = top
}
# USER AVATAR
image {
monitor =
path = $MPRIS_IMAGE
size = 150 # lesser side if not 1:1 ratio
reload_time = 0 # seconds between reloading, 0 to reload with SIGUSR2
reload_cmd = # command to get new path. if empty, old path will be used. don't run "follow" commands like tail -F
position = 0, 0
halign = center
valign = center
}
# INPUT FIELD
input-field {
monitor =
size = 200, 50 #!
outline_thickness = 3
dots_size = 0.33 # Scale of input-field height, 0.2 - 0.8
dots_spacing = 0.15 # Scale of dots' absolute size, 0.0 - 1.0
dots_center = true
dots_rounding = -1 # -1 default circle, -2 follow input-field rounding
outer_color = $wallbash_pry4_rgba
inner_color = $wallbash_pry2_rgba
font_color = $wallbash_3xa9_rgba
fade_on_empty = true
fade_timeout = 1000 # Milliseconds before fade_on_empty is triggered.
placeholder_text = <i>Input Password...</i> # Text rendered in the input box when it's empty.
hide_input = false
rounding = -1 # -1 means complete rounding (circle/oval)
check_color = $wallbash_pry4_rgba
fail_color = rgba(FF0000FF) # if authentication failed, changes outer_color and fail message color
fail_text = <i>$FAIL <b>($ATTEMPTS)</b></i> # can be set to empty
fail_transition = 300 # transition time in ms between normal outer_color and fail_color
capslock_color = -1
numlock_color = -1
bothlock_color = -1 # when both locks are active. -1 means don't change outer color (same for above)
invert_numlock = false # change color if numlock is off
swap_font_color = true # see below
position = 0, 80
halign = center
valign = bottom
}
# USER Greeting
label {
monitor =
text = cmd[update:60000] $fn_greet
color = $text
font_size = 20
font_family = $fontFamily Bold
position = 0, -190
halign = center
valign = center
}
# Mpris and SPLASH
label {
monitor =
text = cmd[update:1000] $SPLASH_CMD # Outputs the song title when mpris is available, otherwise, it will output the splash command.
color = $wallbash_txt2_rgba
font_family = $fontFamily
font_size = 15
position = 0, 0
halign = center
valign = bottom
}
# Battery Status if present
label {
monitor =
text = cmd[update:5000] $BATTERY_ICON
color = $wallbash_4xa9_rgba
font_size = 20
font_family = JetBrainsMono Nerd Font
position = -1%, 1%
halign = right
valign = bottom
}
# Current Keyboard Layout
label {
monitor =
text = cmd[update:1000] $KEYBOARD_LAYOUT
color = $wallbash_4xa9_rgba
font_size = 20
font_family = $fontFamily
position = -2%, 1%
halign = right
valign = bottom
}
View File
-28
View File
@@ -1,28 +0,0 @@
# █▀▀ █░█ █▀█ █▀ █▀█ █▀█
# █▄▄ █▄█ █▀▄ ▄█ █▄█ █▀▄
exec = hyprctl setcursor Bibata-Modern-Ice 20
exec = gsettings set org.gnome.desktop.interface cursor-theme 'Bibata-Modern-Ice'
exec = gsettings set org.gnome.desktop.interface cursor-size 20
# █▀▀ █▀█ █▄░█ ▀█▀
# █▀░ █▄█ █░▀█ ░█░
exec = gsettings set org.gnome.desktop.interface font-name 'Cantarell 10'
exec = gsettings set org.gnome.desktop.interface document-font-name 'Cantarell 10'
exec = gsettings set org.gnome.desktop.interface monospace-font-name 'CaskaydiaCove Nerd Font Mono 9'
exec = gsettings set org.gnome.desktop.interface font-antialiasing 'rgba'
exec = gsettings set org.gnome.desktop.interface font-hinting 'full'
# █▀ █▀█ █▀▀ █▀▀ █ ▄▀█ █░░
# ▄█ █▀▀ ██▄ █▄▄ █ █▀█ █▄▄
decoration {
dim_special = 0.3
blur {
special = true
}
}
-35
View File
@@ -1,35 +0,0 @@
exec = gsettings set org.gnome.desktop.interface icon-theme 'Tela-circle-dracula'
exec = gsettings set org.gnome.desktop.interface gtk-theme 'Catppuccin-Mocha'
exec = gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
general {
gaps_in = 2 #3
gaps_out = 4 #8
border_size = 1 #2
col.active_border = rgba(ca9ee6ff) rgba(f2d5cfff) 45deg
col.inactive_border = rgba(b4befecc) rgba(6c7086cc) 45deg
layout = dwindle
resize_on_border = true
}
group {
col.border_active = rgba(ca9ee6ff) rgba(f2d5cfff) 45deg
col.border_inactive = rgba(b4befecc) rgba(6c7086cc) 45deg
col.border_locked_active = rgba(ca9ee6ff) rgba(f2d5cfff) 45deg
col.border_locked_inactive = rgba(b4befecc) rgba(6c7086cc) 45deg
}
decoration {
rounding = 4 #10
# drop_shadow = true #false
blur {
enabled = yes
size = 6
passes = 3
new_optimizations = on
ignore_opacity = on
xray = false
}
}
# layerrule = blur,waybar
-50
View File
@@ -1,50 +0,0 @@
# Auto-generated by HyDE // Read-only
# // ----------------------------
# HyDE Theme: Catppuccin Mocha
# Configuration File: "/home/coja/.config/hyde/themes/Catppuccin Mocha/hypr.theme"
# Wallbash Mode : theme
# // ----------------------------
$HYDE_THEME=Catppuccin Mocha
$GTK_THEME=Catppuccin-Mocha
$COLOR-SCHEME=prefer-dark
$ICON_THEME=Tela-circle-dracula
$CURSOR_THEME=Bibata-Modern-Ice
$CURSOR_SIZE=24
$FONT=Cantarell
$FONT_SIZE=10
$DOCUMENT_FONT=Cantarell
$DOCUMENT_FONT_SIZE=10
$MONOSPACE_FONT=CaskaydiaCove Nerd Font Mono
$MONOSPACE_FONT_SIZE=9
$CODE_THEME=Wallbash
$SDDM_THEME=
# // ----------------------------
# README:
# Values above are derived and sanitized from the theme.conf file,
# This is to ensure themes won't have any 'exec' or 'source'
# commands that could potentially harm the system
# or undesired behavior.
#
# Dear Theme Owner:
# You can still add your own custom 'exec' or 'source' commands
# by adding it as variable, examples (you can name the variable anything):
# Note that you should indicate it in your README.md
#
#
# -- ⌨️ theme.conf --
# $RUN_CMD="some_command"
# $SOURCE_FILE="/some/files"
#
#
# -- ⌨️ userprefs.conf --
# exec = ${RUN_CMD}"
# source = ${SOURCE_FILE}
# exec = hyde-shell walbash code $CODE_THEME # Setting the code theme
# // ----------------------------
-72
View File
@@ -1,72 +0,0 @@
# █░█░█ █ █▄░█ █▀▄ █▀█ █░█░█   █▀█ █░█ █░░ █▀▀ █▀
# ▀▄▀▄▀ █ █░▀█ █▄▀ █▄█ ▀▄▀▄▀   █▀▄ █▄█ █▄▄ ██▄ ▄█
# See https://wiki.hyprland.org/Configuring/Window-Rules/
# hyprctl clients to get names
windowrule = opacity 0.80 0.80, match:class ^(code-url-handler)$
windowrule = opacity 0.80 0.80, match:class ^(code-insiders-url-handler)$
windowrule = opacity 0.80 0.80, match:class ^(org.kde.dolphin)$
windowrule = opacity 0.80 0.80, float on, match:class ^(org.kde.ark)$
windowrule = opacity 0.80 0.80, float on, match:class ^(nwg-look)$
windowrule = opacity 0.80 0.80, float on, match:class ^(qt5ct)$
windowrule = opacity 0.80 0.80, float on, match:class ^(qt6ct)$
windowrule = opacity 0.80 0.80, float on, match:class ^(kvantummanager)$
windowrule = opacity 0.80 0.70, float on, match:class ^(org.pulseaudio.pavucontrol)$
windowrule = opacity 0.80 0.70, match:class ^(lagrange)$
windowrule = opacity 0.60 0.60, match:class ^(com.github.wwmm.easyeffects)$
windowrule = opacity 0.80 0.70, float on, match:class ^(blueman-manager)$
windowrule = opacity 0.80 0.70, float on, match:class ^(nm-applet)$
windowrule = opacity 0.80 0.70, float on, match:class ^(nm-connection-editor)$
windowrule = opacity 0.80 0.70, float on, match:class ^(org.kde.polkit-kde-authentication-agent-1)$
windowrule = opacity 0.80 0.70, match:class ^(polkit-gnome-authentication-agent-1)$
windowrule = opacity 0.80 0.70, match:class ^(org.freedesktop.impl.portal.desktop.gtk)$
windowrule = opacity 0.80 0.70, match:class ^(org.freedesktop.impl.portal.desktop.hyprland)$
windowrule = opacity 0.70 0.70, match:class ^([Ss]team)$
windowrule = opacity 0.90 0.90, float on, match:class ^(com.github.rafostar.Clapper)$ # Clapper-Gtk
windowrule = opacity 0.80 0.80, match:class ^(com.github.tchx84.Flatseal)$ # Flatseal-Gtk
windowrule = opacity 0.80 0.80, match:class ^(hu.kramo.Cartridges)$ # Cartridges-Gtk
windowrule = opacity 0.80 0.80, match:class ^(com.obsproject.Studio)$ # Obs-Qt
windowrule = opacity 0.80 0.80, match:class ^(gnome-boxes)$ # Boxes-Gtk
windowrule = opacity 0.80 0.80, match:class ^(discord)$ # Discord-Electron
windowrule = opacity 0.80 0.80, match:class ^(WebCord)$ # WebCord-Electron
windowrule = opacity 0.80 0.80, match:class ^(ArmCord)$ # ArmCord-Electron
windowrule = opacity 0.80 0.80, float on, match:class ^(app.drey.Warp)$ # Warp-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(net.davidotek.pupgui2)$ # ProtonUp-Qt
windowrule = opacity 0.80 0.80, float on, match:class ^(yad)$ # Protontricks-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(Signal)$ # Signal-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(io.github.alainm23.planify)$ # planify-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk
windowrule = opacity 0.80 0.80, match:class ^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(io.gitlab.adhami3310.Impression)$ # Impression-Gtk
windowrule = opacity 0.80 0.80, float on, match:class ^(io.missioncenter.MissionCenter)$ # MissionCenter-Gtk
windowrule = opacity 0.80 0.80, match:class ^(io.github.flattool.Warehouse)$ # Warehouse-Gtk
windowrule = float on, match:class ^(org.kde.dolphin)$, match:title ^(Progress Dialog — Dolphin)$
windowrule = float on, match:class ^(org.kde.dolphin)$, match:title ^(Copying — Dolphin)$
windowrule = float on, match:title ^(About Mozilla Firefox)$
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
windowrule = float on, match:class ^(firefox)$, match:title ^(Library)$
windowrule = float on, match:class ^(kitty)$, match:title ^(top)$
windowrule = float on, match:class ^(kitty)$, match:title ^(btop)$
windowrule = float on, match:class ^(kitty)$, match:title ^(htop)$
windowrule = float on, match:class ^(vlc)$
windowrule = float on, match:class ^(eog)$ # Imageviewer-Gtk
windowrule = float on, match:class ^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gkk
# █░░ ▄▀█ █▄█ █▀▀ █▀█   █▀█ █░█ █░░ █▀▀ █▀
# █▄▄ █▀█ ░█░ ██▄ █▀▄   █▀▄ █▄█ █▄▄ ██▄ ▄█
# layerrule = blur,rofi
# layerrule = ignorezero,rofi
# layerrule = blur,notifications
# layerrule = ignorezero,notifications
# layerrule = blur,swaync-notification-window
# layerrule = ignorezero,swaync-notification-window
# layerrule = blur,swaync-control-center
# layerrule = ignorezero,swaync-control-center
# layerrule = blur,logout_dialog
View File
-17
View File
@@ -1,17 +0,0 @@
# run with `kitty --session ~/.config/kitty/c2.conf`
layout tall
# create a new tab
new_tab c2
cd ~/projects/wingman/c2/
launch --var window=first nvim
new_tab git
cd ~/projects/wingman/c2/
launch --var window=second lazygit
# focus neovim
# focus first
-17
View File
@@ -1,17 +0,0 @@
# run with `kitty --session ~/.config/kitty/music.conf`
layout tall
# os_window_size 220c 64c
# create a new tab
new_tab music
# set the working directory for windows in the current tab
cd
launch --var window=first cava
# create additional terminals
launch --location=hsplit pyradio
resize_window taller 10
focus pyradio
launch --location=vsplit pipes-rs
resize_window wider 20
-20
View File
@@ -1,20 +0,0 @@
# run with `kitty --session ~/.config/kitty/runners.conf`
layout stack
# create a new tab
new_tab runners
cd ~/projects/wingman/c2/
launch --var window=first pnpm dev
launch --location=hsplit react-devtools
resize_window narrower 20
launch --location=hsplit lazydocker
resize_window wider 120
# new_tab simulator
# cd ~/wmclient
# launch --var window=first wmsimulator.sh
-29
View File
@@ -1,29 +0,0 @@
cal
calc
speedtest
aichat -e
askllama
ask
tldr
man
info
rsync -avkP
lsd
zioxide
httpie
bat
xxd
mpv
nix
distrobox
scrcpy
adb
watch
unzip
open
nmcli
iwctl
lsusb
thefuck
fastfetch
-40
View File
@@ -1,40 +0,0 @@
browsh
wiki-tui
wikiman
yazi
tdf
dysk
dust
ncdu
systemctl-tui
btop
bandwhich
mtr
gping
bmon
profanity
nmtui
bluetui
lazygit
lazydocker
lazyjorunal
pastel
hexyl
pyradio
kew
tt
gittype
matrix
cava
tetrigo
tui-mines
nyaa
stig
tmux
zellij
nvim
vim
tuios
llmfit
battop
subtui
-21
View File
@@ -1,21 +0,0 @@
# Volume
9 add volume -5
0 add volume 5
UP add volume 2
DOWN add volume -2
k add volume 2
j add volume -2
# Seek backward / forward
LEFT seek -5
RIGHT seek 5
l seek 5
h seek -5
# Bigger jumps
Shift+LEFT seek -30
Shift+RIGHT seek 30
-32
View File
@@ -1,32 +0,0 @@
{
"LuaSnip": { "branch": "master", "commit": "a62e1083a3cfe8b6b206e7d3d33a51091df25357" },
"NvChad": { "branch": "v2.5", "commit": "d042cc975247c2aa55fcb228e5d146dc1dc6c648" },
"base46": { "branch": "v2.5", "commit": "fde7a2cd54599e148d376f82980407c2d24b0fa2" },
"cmp-async-path": { "branch": "main", "commit": "f8af3f726e07f2e9d37672eaa9102581aefce149" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
"cmp-nvim-lua": { "branch": "main", "commit": "e3a22cb071eb9d6508a156306b102c45cd2d573d" },
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
"codecompanion.nvim": { "branch": "main", "commit": "680c445eb357c72129d175ea5d481aa9e92ae6a5" },
"conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" },
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
"gitsigns.nvim": { "branch": "main", "commit": "6d808f99bd63303646794406e270bd553ad7792e" },
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
"mason.nvim": { "branch": "main", "commit": "12ddd182d9efbdc848b540f16484a583d52da0fb" },
"menu": { "branch": "main", "commit": "7a0a4a2896b715c066cfbe320bdc048091874cc6" },
"minty": { "branch": "main", "commit": "aafc9e8e0afe6bf57580858a2849578d8d8db9e0" },
"nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" },
"nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
"nvim-lspconfig": { "branch": "master", "commit": "e146efacbafed3789ac568abcc5a981c5decaa58" },
"nvim-tree.lua": { "branch": "master", "commit": "d277467fc0d1d0e2bca88165a1de6b526f9f6fe8" },
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
"nvim-web-devicons": { "branch": "master", "commit": "c72328a5494b4502947a022fe69c0c47e53b6aa6" },
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
"render-markdown.nvim": { "branch": "main", "commit": "d67113f11384c0dad96fced2f7b91f1fc811e97f" },
"snacks.nvim": { "branch": "main", "commit": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e" },
"telescope.nvim": { "branch": "master", "commit": "028d9a0695a0cc4cfa893889f8c408ed7ccc8adc" },
"ui": { "branch": "v3.0", "commit": "cb75908a86720172594b30de147272c1b3a7f452" },
"volt": { "branch": "main", "commit": "620de1321f275ec9d80028c68d1b88b409c0c8b1" },
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
}
@@ -1,34 +0,0 @@
local model = "Qwen3-Coder-30B-Instruct-IQ4_XS"
return {
adapters = {
http = {
llama_cpp = function()
return require("codecompanion.adapters").extend("openai_compatible", {
env = {
url = "http://localhost:11343",
chat_url = "/v1/chat/completions",
models_endpoint = "/v1/models",
api_key = "dummy",
},
schema = {
model = {
default = model,
},
},
})
end,
},
},
interactions = {
chat = {
adapter = "llama_cpp",
},
inline = {
adapter = "llama_cpp",
},
cmd = {
adapter = "llama_cpp",
},
},
}
-24
View File
@@ -1,24 +0,0 @@
0|28|bottom|( cpu memory custom/cpuinfo custom/gpuinfo ) ( idle_inhibitor clock )|( hyprland/workspaces hyprland/window )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|28|top|( cpu memory custom/cpuinfo custom/gpuinfo ) ( idle_inhibitor clock )|( hyprland/workspaces hyprland/window )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|28|bottom|( cpu memory custom/cpuinfo custom/gpuinfo ) ( idle_inhibitor clock ) ( hyprland/workspaces )|( hyprland/window )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|28|top|( cpu memory custom/cpuinfo ) ( idle_inhibitor clock ) ( hyprland/workspaces )|( hyprland/window )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0||bottom|( hyprland/workspaces hyprland/window )|( idle_inhibitor clock )|( cpu memory custom/cpuinfo custom/gpuinfo ) ( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0||top|( hyprland/workspaces hyprland/window )|( idle_inhibitor clock )|( cpu memory custom/cpuinfo custom/gpuinfo ) ( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|31|bottom|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( wlr/taskbar custom/spotify ) |( idle_inhibitor clock )|( tray battery ) ( backlight network pulseaudio pulseaudio#microphone custom/notifications custom/keybindhint )
0|31|left|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( wlr/taskbar custom/spotify ) |( idle_inhibitor clock )|( tray battery ) ( backlight network pulseaudio pulseaudio#microphone custom/keybindhint )
0|31|top|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( wlr/taskbar custom/spotify ) |( idle_inhibitor clock )|( tray battery ) ( backlight network pulseaudio pulseaudio#microphone custom/keybindhint )
0|31|right|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( wlr/taskbar custom/spotify ) |( idle_inhibitor clock )|( tray battery ) ( backlight network pulseaudio pulseaudio#microphone custom/keybindhint )
0|32|bottom||( custom/power ) ( tray battery ) ( wlr/taskbar idle_inhibitor clock ) ( custom/cliphist ) ( custom/wbar ) ( custom/wallchange ) ( custom/theme )|
0|32|left||( custom/power ) ( tray battery ) ( wlr/taskbar idle_inhibitor clock ) ( custom/cliphist ) ( custom/wbar ) ( custom/wallchange ) ( custom/theme )|
1|32|top||( custom/power ) ( tray battery ) ( wlr/taskbar idle_inhibitor clock ) ( custom/cliphist ) ( custom/wbar ) ( custom/wallchange ) ( custom/theme )|
0|32|right||( custom/power ) ( tray battery ) ( wlr/taskbar idle_inhibitor clock ) ( custom/cliphist ) ( custom/wbar ) ( custom/wallchange ) ( custom/theme )|
0|31|bottom|( cpu memory custom/cpuinfo custom/gpuinfo ) ( idle_inhibitor clock ) ( hyprland/workspaces )|( wlr/taskbar )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|31|top|( cpu memory custom/cpuinfo custom/gpuinfo ) ( idle_inhibitor clock ) ( hyprland/workspaces )|( wlr/taskbar )|( backlight network pulseaudio pulseaudio#microphone custom/updates custom/keybindhint ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|29|bottom|( wlr/taskbar mpris )|( idle_inhibitor clock )|( backlight network pulseaudio pulseaudio#microphone custom/updates ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|29|left|( wlr/taskbar mpris )|( idle_inhibitor clock )|( backlight network pulseaudio pulseaudio#microphone custom/updates ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|29|top|( wlr/taskbar mpris )|( idle_inhibitor clock )|( backlight network pulseaudio pulseaudio#microphone custom/updates ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|29|right|( wlr/taskbar mpris )|( idle_inhibitor clock )|( backlight network pulseaudio pulseaudio#microphone custom/updates ) ( tray battery ) ( custom/wallchange custom/theme custom/wbar custom/cliphist custom/power )
0|28|bottom|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( idle_inhibitor clock custom/spotify )|( wlr/taskbar )|( tray ) ( backlight network pulseaudio pulseaudio#microphone )
0|28|left|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( idle_inhibitor clock custom/spotify )|( wlr/taskbar )|( tray ) ( backlight network pulseaudio pulseaudio#microphone )
0|28|top|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( idle_inhibitor clock custom/spotify )|( wlr/taskbar )|( tray ) ( backlight network pulseaudio pulseaudio#microphone )
0|28|right|( custom/power custom/cliphist custom/wbar custom/theme custom/wallchange ) ( idle_inhibitor clock custom/spotify )|( wlr/taskbar )|( tray ) ( backlight network pulseaudio pulseaudio#microphone )
-17
View File
@@ -1,17 +0,0 @@
"hyprland/window": {
"format": "  {}",
"rotate": ${r_deg},
"separate-outputs": true,
"rewrite": {
"${USER}@${set_sysname}:(.*)": "$1 ",
"(.*) — Mozilla Firefox": "$1 󰈹",
"(.*)Mozilla Firefox": "Firefox 󰈹",
"(.*) - Visual Studio Code": "$1 󰨞",
"(.*)Visual Studio Code": "Code 󰨞",
"(.*) — Dolphin": "$1 󰉋",
"(.*)Spotify": "Spotify 󰓇",
"(.*)Steam": "Steam 󰓓"
},
"max-length": 1000
},
-26
View File
@@ -1,26 +0,0 @@
modmap:
- name: CapsLock to LeftCtrl/Esc
remap:
CapsLock:
held: leftctrl
alone: esc
alone_timeout_millis: 150
# keymap:
# - name: Alt+hjkl to Arrows
# remap:
# # Alt-H: Alt+left #back
# Alt-h: left
# Alt-l: right
# Alt-j: down
# Alt-k: up
# Alt-u: pageup
# Alt-d: pagedown
# keymap:
# - name: Freetube remap
# application:
# only: FreeTube
# remap:
# Alt-h: Left
# Alt-l: Right
# Alt-j: Down
# Alt-k: Up
-452
View File
@@ -1,452 +0,0 @@
keybinds clear-defaults=true {
locked {
bind "Ctrl g" { SwitchToMode "normal"; }
}
pane {
bind "left" { MoveFocus "left"; }
bind "down" { MoveFocus "down"; }
bind "up" { MoveFocus "up"; }
bind "right" { MoveFocus "right"; }
bind "c" { SwitchToMode "renamepane"; PaneNameInput 0; }
bind "d" { NewPane "down"; SwitchToMode "normal"; }
bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "normal"; }
bind "f" { ToggleFocusFullscreen; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; }
bind "i" { TogglePanePinned; SwitchToMode "normal"; }
bind "j" { MoveFocus "down"; }
bind "k" { MoveFocus "up"; }
bind "l" { MoveFocus "right"; }
bind "n" { NewPane; SwitchToMode "normal"; }
bind "p" { SwitchFocus; }
bind "Ctrl p" { SwitchToMode "normal"; }
bind "r" { NewPane "right"; SwitchToMode "normal"; }
bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; }
bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
}
tab {
bind "left" { GoToPreviousTab; }
bind "down" { GoToNextTab; }
bind "up" { GoToPreviousTab; }
bind "right" { GoToNextTab; }
bind "1" { GoToTab 1; SwitchToMode "normal"; }
bind "2" { GoToTab 2; SwitchToMode "normal"; }
bind "3" { GoToTab 3; SwitchToMode "normal"; }
bind "4" { GoToTab 4; SwitchToMode "normal"; }
bind "5" { GoToTab 5; SwitchToMode "normal"; }
bind "6" { GoToTab 6; SwitchToMode "normal"; }
bind "7" { GoToTab 7; SwitchToMode "normal"; }
bind "8" { GoToTab 8; SwitchToMode "normal"; }
bind "9" { GoToTab 9; SwitchToMode "normal"; }
bind "[" { BreakPaneLeft; SwitchToMode "normal"; }
bind "]" { BreakPaneRight; SwitchToMode "normal"; }
bind "b" { BreakPane; SwitchToMode "normal"; }
bind "h" { GoToPreviousTab; }
bind "j" { GoToNextTab; }
bind "k" { GoToPreviousTab; }
bind "l" { GoToNextTab; }
bind "n" { NewTab; SwitchToMode "normal"; }
bind "r" { SwitchToMode "renametab"; TabNameInput 0; }
bind "s" { ToggleActiveSyncTab; SwitchToMode "normal"; }
bind "Ctrl t" { SwitchToMode "normal"; }
bind "x" { CloseTab; SwitchToMode "normal"; }
bind "tab" { ToggleTab; }
}
resize {
bind "left" { Resize "Increase left"; }
bind "down" { Resize "Increase down"; }
bind "up" { Resize "Increase up"; }
bind "right" { Resize "Increase right"; }
bind "+" { Resize "Increase"; }
bind "-" { Resize "Decrease"; }
bind "=" { Resize "Increase"; }
bind "H" { Resize "Decrease left"; }
bind "J" { Resize "Decrease down"; }
bind "K" { Resize "Decrease up"; }
bind "L" { Resize "Decrease right"; }
bind "h" { Resize "Increase left"; }
bind "j" { Resize "Increase down"; }
bind "k" { Resize "Increase up"; }
bind "l" { Resize "Increase right"; }
bind "Ctrl n" { SwitchToMode "normal"; }
}
move {
bind "left" { MovePane "left"; }
bind "down" { MovePane "down"; }
bind "up" { MovePane "up"; }
bind "right" { MovePane "right"; }
bind "h" { MovePane "left"; }
bind "Ctrl h" { SwitchToMode "normal"; }
bind "j" { MovePane "down"; }
bind "k" { MovePane "up"; }
bind "l" { MovePane "right"; }
bind "n" { MovePane; }
bind "p" { MovePaneBackwards; }
bind "tab" { MovePane; }
}
scroll {
bind "e" { EditScrollback; SwitchToMode "normal"; }
bind "s" { SwitchToMode "entersearch"; SearchInput 0; }
}
search {
bind "c" { SearchToggleOption "CaseSensitivity"; }
bind "n" { Search "down"; }
bind "o" { SearchToggleOption "WholeWord"; }
bind "p" { Search "up"; }
bind "w" { SearchToggleOption "Wrap"; }
}
session {
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "c" {
LaunchOrFocusPlugin "configuration" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "Ctrl o" { SwitchToMode "normal"; }
bind "p" {
LaunchOrFocusPlugin "plugin-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
bind "w" {
LaunchOrFocusPlugin "session-manager" {
floating true
move_to_focused_tab true
}
SwitchToMode "normal"
}
}
shared_except "locked" {
bind "Alt left" { MoveFocusOrTab "left"; }
bind "Alt down" { MoveFocus "down"; }
bind "Alt up" { MoveFocus "up"; }
bind "Alt right" { MoveFocusOrTab "right"; }
bind "Alt +" { Resize "Increase"; }
bind "Alt -" { Resize "Decrease"; }
bind "Alt =" { Resize "Increase"; }
bind "Alt [" { PreviousSwapLayout; }
bind "Alt ]" { NextSwapLayout; }
bind "Alt f" { ToggleFloatingPanes; }
bind "Ctrl g" { SwitchToMode "locked"; }
bind "Alt h" { MoveFocusOrTab "left"; }
bind "Alt i" { MoveTab "left"; }
bind "Alt j" { MoveFocus "down"; }
bind "Alt k" { MoveFocus "up"; }
bind "Alt l" { MoveFocusOrTab "right"; }
bind "Alt n" { NewPane; }
bind "Alt o" { MoveTab "right"; }
bind "Ctrl q" { Quit; }
}
shared_except "locked" "move" {
bind "Ctrl h" { SwitchToMode "move"; }
}
shared_except "locked" "session" {
bind "Ctrl o" { SwitchToMode "session"; }
}
shared_except "locked" "scroll" "search" "tmux" {
bind "Ctrl b" { SwitchToMode "tmux"; }
}
shared_except "locked" "scroll" "search" {
bind "Ctrl s" { SwitchToMode "scroll"; }
}
shared_except "locked" "tab" {
bind "Ctrl t" { SwitchToMode "tab"; }
}
shared_except "locked" "pane" {
bind "Ctrl p" { SwitchToMode "pane"; }
}
shared_except "locked" "resize" {
bind "Ctrl n" { SwitchToMode "resize"; }
}
shared_except "normal" "locked" "entersearch" {
bind "enter" { SwitchToMode "normal"; }
}
shared_except "normal" "locked" "entersearch" "renametab" "renamepane" {
bind "esc" { SwitchToMode "normal"; }
}
shared_among "pane" "tmux" {
bind "x" { CloseFocus; SwitchToMode "normal"; }
}
shared_among "scroll" "search" {
bind "PageDown" { PageScrollDown; }
bind "PageUp" { PageScrollUp; }
bind "left" { PageScrollUp; }
bind "down" { ScrollDown; }
bind "up" { ScrollUp; }
bind "right" { PageScrollDown; }
bind "Ctrl b" { PageScrollUp; }
bind "Ctrl c" { ScrollToBottom; SwitchToMode "normal"; }
bind "d" { HalfPageScrollDown; }
bind "Ctrl f" { PageScrollDown; }
bind "h" { PageScrollUp; }
bind "j" { ScrollDown; }
bind "k" { ScrollUp; }
bind "l" { PageScrollDown; }
bind "Ctrl s" { SwitchToMode "normal"; }
bind "u" { HalfPageScrollUp; }
}
entersearch {
bind "Ctrl c" { SwitchToMode "scroll"; }
bind "esc" { SwitchToMode "scroll"; }
bind "enter" { SwitchToMode "search"; }
}
renametab {
bind "esc" { UndoRenameTab; SwitchToMode "tab"; }
}
shared_among "renametab" "renamepane" {
bind "Ctrl c" { SwitchToMode "normal"; }
}
renamepane {
bind "esc" { UndoRenamePane; SwitchToMode "pane"; }
}
shared_among "session" "tmux" {
bind "d" { Detach; }
}
tmux {
bind "left" { MoveFocus "left"; SwitchToMode "normal"; }
bind "down" { MoveFocus "down"; SwitchToMode "normal"; }
bind "up" { MoveFocus "up"; SwitchToMode "normal"; }
bind "right" { MoveFocus "right"; SwitchToMode "normal"; }
bind "space" { NextSwapLayout; }
bind "\"" { NewPane "down"; SwitchToMode "normal"; }
bind "%" { NewPane "right"; SwitchToMode "normal"; }
bind "," { SwitchToMode "renametab"; }
bind "[" { SwitchToMode "scroll"; }
bind "Ctrl b" { Write 2; SwitchToMode "normal"; }
bind "c" { NewTab; SwitchToMode "normal"; }
bind "h" { MoveFocus "left"; SwitchToMode "normal"; }
bind "j" { MoveFocus "down"; SwitchToMode "normal"; }
bind "k" { MoveFocus "up"; SwitchToMode "normal"; }
bind "l" { MoveFocus "right"; SwitchToMode "normal"; }
bind "n" { GoToNextTab; SwitchToMode "normal"; }
bind "o" { FocusNextPane; }
bind "p" { GoToPreviousTab; SwitchToMode "normal"; }
bind "z" { ToggleFocusFullscreen; SwitchToMode "normal"; }
}
}
// Plugin aliases - can be used to change the implementation of Zellij
// changing these requires a restart to take effect
plugins {
about location="zellij:about"
compact-bar location="zellij:compact-bar"
configuration location="zellij:configuration"
filepicker location="zellij:strider" {
cwd "/"
}
plugin-manager location="zellij:plugin-manager"
session-manager location="zellij:session-manager"
status-bar location="zellij:status-bar"
strider location="zellij:strider"
tab-bar location="zellij:tab-bar"
welcome-screen location="zellij:session-manager" {
welcome_screen true
}
}
// Plugins to load in the background when a new session starts
// eg. "file:/path/to/my-plugin.wasm"
// eg. "https://example.com/my-plugin.wasm"
load_plugins {
}
// Use a simplified UI without special fonts (arrow glyphs)
// Options:
// - true
// - false (Default)
//
// simplified_ui true
// Choose the theme that is specified in the themes section.
// Default: default
//
// theme "dracula"
// Choose the base input mode of zellij.
// Default: normal
//
// default_mode "locked"
// Choose the path to the default shell that zellij will use for opening new panes
// Default: $SHELL
//
// default_shell "fish"
// Choose the path to override cwd that zellij will use for opening new panes
//
// default_cwd "/tmp"
// The name of the default layout to load on startup
// Default: "default"
//
// default_layout "compact"
// The folder in which Zellij will look for layouts
// (Requires restart)
//
// layout_dir "/tmp"
// The folder in which Zellij will look for themes
// (Requires restart)
//
// theme_dir "/tmp"
// Toggle enabling the mouse mode.
// On certain configurations, or terminals this could
// potentially interfere with copying text.
// Options:
// - true (default)
// - false
//
// mouse_mode false
// Toggle having pane frames around the panes
// Options:
// - true (default, enabled)
// - false
//
// pane_frames false
// When attaching to an existing session with other users,
// should the session be mirrored (true)
// or should each user have their own cursor (false)
// (Requires restart)
// Default: false
//
// mirror_session true
// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP
// eg. when terminal window with an active zellij session is closed
// (Requires restart)
// Options:
// - detach (Default)
// - quit
//
// on_force_close "quit"
// Configure the scroll back buffer size
// This is the number of lines zellij stores for each pane in the scroll back
// buffer. Excess number of lines are discarded in a FIFO fashion.
// (Requires restart)
// Valid values: positive integers
// Default value: 10000
//
// scroll_buffer_size 10000
// Provide a command to execute when copying text. The text will be piped to
// the stdin of the program to perform the copy. This can be used with
// terminal emulators which do not support the OSC 52 ANSI control sequence
// that will be used by default if this option is not set.
// Examples:
//
// copy_command "xclip -selection clipboard" // x11
// copy_command "wl-copy" // wayland
// copy_command "pbcopy" // osx
//
// copy_command "pbcopy"
// Choose the destination for copied text
// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard.
// Does not apply when using copy_command.
// Options:
// - system (default)
// - primary
//
// copy_clipboard "primary"
// Enable automatic copying (and clearing) of selection when releasing mouse
// Default: true
//
// copy_on_select true
// Path to the default editor to use to edit pane scrollbuffer
// Default: $EDITOR or $VISUAL
// scrollback_editor "/usr/bin/vim"
// A fixed name to always give the Zellij session.
// Consider also setting `attach_to_session true,`
// otherwise this will error if such a session exists.
// Default: <RANDOM>
//
// session_name "My singleton session"
// When `session_name` is provided, attaches to that session
// if it is already running or creates it otherwise.
// Default: false
//
// attach_to_session true
// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible
// Options:
// - true (default)
// - false
//
// auto_layout false
// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected
// Options:
// - true (default)
// - false
//
// session_serialization false
// Whether pane viewports are serialized along with the session, default is false
// Options:
// - true
// - false (default)
//
// serialize_pane_viewport false
// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0
// defaults to the scrollback size. If this number is higher than the scrollback size, it will
// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true.
//
// scrollback_lines_to_serialize 10000
// Enable or disable the rendering of styled and colored underlines (undercurl).
// May need to be disabled for certain unsupported terminals
// (Requires restart)
// Default: true
//
// styled_underlines false
// How often in seconds sessions are serialized
//
// serialization_interval 10000
// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know
// metadata info on this session)
// (Requires restart)
// Default: false
//
// disable_session_metadata false
// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)
// (Requires restart)
// Default: true (if the host terminal supports it)
//
// support_kitty_keyboard_protocol false
// Whether to stack panes when resizing beyond a certain size
// Default: true
//
// stacked_resize false
// Whether to show tips on startup
// Default: true
//
// show_startup_tips false
// Whether to show release notes on first version run
// Default: true
//
// show_release_notes false
+34 -3
View File
@@ -1,3 +1,34 @@
!.gitignore # Secrets / local-only (matched at any depth)
.config/hyde/themes/ secrets.fish
.config/fish/greet.txt fish_variables
greet.txt
settings.local.json
# pi agent runtime + machine-local auth/state
**/.pi/agent/auth.json
**/.pi/agent/sessions/
**/.pi/agent/bin/
**/.pi/agent/git/
**/.pi/agent/npm/
**/.pi/agent/trust.json
# tmux plugins (tpm-managed, not vendored)
**/tmux/plugins/
# HyDE generated theme + wallpaper assets (per-machine, huge — bloated the old repo to 255MB)
**/hyde/themes/
**/wallpapers/
# Claude Code project dir (worktrees, local settings, history) — never part of the dotfiles
.claude/
# backups / logs / package noise / os
*.bak
*.bak.*
*.log
package-lock.json
__pycache__/
**/hypr/themes/
# HyDE writes these at theme-apply time (sourced by animations/theme.conf & hyprlock/theme.conf)
**/hypr/animations/HyDE.conf
**/hypr/hyprlock/HyDE.conf
+3
View File
@@ -0,0 +1,3 @@
# Search config for THIS repo only (ripgrep / fd / telescope read .ignore).
!.*
.git/
-21
View File
@@ -1,21 +0,0 @@
# Vi mode
set editing-mode vi
set keymap vi
set show-mode-in-prompt on
set vi-ins-mode-string "\1\e[32m\2[I] \1\e[0m\2"
set vi-cmd-mode-string "\1\e[31m\2[N] \1\e[0m\2"
# exclude .dotfles
set match-hidden-files off
set show-all-if-ambiguous on
set show-all-if-unmodified on
set colored-stats on
set colored-completion-prefix on
set mark-symlinked-directories on
set completion-ignore-case on
# Ignore duplicates in history where supported
set skip-completed-text on
-1
View File
@@ -1 +0,0 @@
{}
-190
View File
@@ -1,190 +0,0 @@
{
"providers": {
"duskadiy": {
"baseUrl": "https://llm.duskadiy.com/api/v1",
"api": "openai-completions",
"apiKey": "no-key-required",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "DeepSeek-Coder-V2-Lite-Instruct-Q8_0",
"name": "DeepSeek Coder V2",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-E4B-it-UD-Q8_K_XL",
"name": "Gemma 4 E4B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-26B-A4B-it-UD-IQ4_XS",
"name": "Gemma 4 26B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3-Coder-30B-Instruct-IQ4_XS",
"name": "Qwen3 Coder A3B",
"reasoning": false,
"input": ["text"],
"contextWindow": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-UD-IQ3_XXS",
"name": "Qwen3.6 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-27B-UD-Q3_K_XL",
"name": "Qwen3.5 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-9B-Q8_0",
"name": "Qwen3.5 9B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M",
"name": "Qwen3.6 27B Uncensored",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q3_K_XL",
"name": "GLM-4.7 Flash",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
},
"localcpp": {
"baseUrl": "http://192.168.0.204:11343/v1",
"api": "openai-completions",
"apiKey": "no-key-required",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "DeepSeek-Coder-V2-Lite-Instruct-Q8_0",
"name": "DeepSeek Coder V2",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-E4B-it-UD-Q8_K_XL",
"name": "Gemma 4 E4B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-26B-A4B-it-UD-IQ4_XS",
"name": "Gemma 4 26B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3-Coder-30B-Instruct-IQ4_XS",
"name": "Qwen3 Coder A3B",
"reasoning": false,
"input": ["text"],
"contextWindow": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-UD-IQ3_XXS",
"name": "Qwen3.6 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-27B-UD-Q3_K_XL",
"name": "Qwen3.5 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-9B-Q8_0",
"name": "Qwen3.5 9B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M",
"name": "Qwen3.6 27B Uncensored",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q3_K_XL",
"name": "GLM-4.7 Flash",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
-190
View File
@@ -1,190 +0,0 @@
{
"providers": {
"duskadiy": {
"baseUrl": "https://llm.duskadiy.com/api/v1",
"api": "openai-completions",
"apiKey": "no-key-required",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "DeepSeek-Coder-V2-Lite-Instruct-Q8_0",
"name": "DeepSeek Coder V2",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-E4B-it-UD-Q8_K_XL",
"name": "Gemma 4 E4B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-26B-A4B-it-UD-IQ4_XS",
"name": "Gemma 4 26B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3-Coder-30B-Instruct-IQ4_XS",
"name": "Qwen3 Coder A3B",
"reasoning": false,
"input": ["text"],
"contextWindow": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-UD-IQ3_XXS",
"name": "Qwen3.6 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-27B-UD-Q3_K_XL",
"name": "Qwen3.5 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-9B-Q8_0",
"name": "Qwen3.5 9B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M",
"name": "Qwen3.6 27B Uncensored",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q3_K_XL",
"name": "GLM-4.7 Flash",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
},
"localcpp": {
"baseUrl": "http://192.168.0.204:11343/v1",
"api": "openai-completions",
"apiKey": "no-key-required",
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "DeepSeek-Coder-V2-Lite-Instruct-Q8_0",
"name": "DeepSeek Coder V2",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-E4B-it-UD-Q8_K_XL",
"name": "Gemma 4 E4B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "gemma-4-26B-A4B-it-UD-IQ4_XS",
"name": "Gemma 4 26B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3-Coder-30B-Instruct-IQ4_XS",
"name": "Qwen3 Coder A3B",
"reasoning": false,
"input": ["text"],
"contextWindow": 8192,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-UD-IQ3_XXS",
"name": "Qwen3.6 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-27B-UD-Q3_K_XL",
"name": "Qwen3.5 27B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.5-9B-Q8_0",
"name": "Qwen3.5 9B",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "Qwen3.6-27B-Uncensored-HauhauCS-Aggressive-IQ3_M",
"name": "Qwen3.6 27B Uncensored",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "GLM-4.7-Flash-UD-Q3_K_XL",
"name": "GLM-4.7 Flash",
"reasoning": false,
"input": ["text"],
"contextWindow": 81920,
"maxTokens": 16384,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
-11
View File
@@ -1,11 +0,0 @@
---
description: Write a Conventional Commits message for the staged changes
argument-hint: "[extra context]"
---
Inspect the staged changes with `git diff --cached` (and `git status` for context). Then write a single Conventional Commits message:
- A `<type>(<scope>): <subject>` summary line, imperative mood, ≤72 chars (types: feat, fix, refactor, docs, chore, test, perf, build).
- An optional short body explaining the *why* only when it isn't obvious from the diff.
- Do NOT run `git commit`. Output only the message inside a code block so I can review it.
Extra context to incorporate: ${1:-none}
-14
View File
@@ -1,14 +0,0 @@
---
description: Review the current diff for bugs and issues
argument-hint: "[focus area]"
---
Review the current changes. Check both `git diff` (unstaged) and `git diff --cached` (staged). Focus on:
- Correctness and logic bugs
- Error handling and missing edge cases
- Security issues (injection, secrets, unsafe input)
- Anything that doesn't match the surrounding code's conventions
List concrete findings as `file:line — issue — suggested fix`, ordered by severity. Be concise; skip praise. If nothing is wrong, say so.
Extra focus this round: ${1:-general correctness}
-30
View File
@@ -1,30 +0,0 @@
{
"defaultProvider": "localcpp",
"defaultModel": "gemma-4-26B-A4B-it-UD-IQ4_XS",
"defaultThinkingLevel": "off",
"theme": "catppuccin-mocha",
"quietStartup": false,
"defaultProjectTrust": "ask",
"enableInstallTelemetry": false,
"enabledModels": [
"localcpp/*",
"duskadiy/*"
],
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
},
"retry": {
"enabled": true,
"maxRetries": 3,
"baseDelayMs": 2000,
"provider": {
"timeoutMs": 3600000
}
},
"httpIdleTimeoutMs": 600000,
"npmCommand": ["fnm", "exec", "--using=22", "--", "npm"],
"showHardwareCursor": true,
"lastChangelogVersion": "0.80.1"
}
-90
View File
@@ -1,90 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
"name": "catppuccin-mocha",
"vars": {
"rosewater": "#f5e0dc",
"flamingo": "#f2cdcd",
"pink": "#f5c2e7",
"mauve": "#cba6f7",
"red": "#f38ba8",
"maroon": "#eba0ac",
"peach": "#fab387",
"yellow": "#f9e2af",
"green": "#a6e3a1",
"teal": "#94e2d5",
"sky": "#89dceb",
"sapphire": "#74c7ec",
"blue": "#89b4fa",
"lavender": "#b4befe",
"fg": "#cdd6f4",
"subtext1": "#bac2de",
"subtext0": "#a6adc8",
"overlay2": "#9399b2",
"overlay1": "#7f849c",
"overlay0": "#6c7086",
"surface2": "#585b70",
"surface1": "#45475a",
"surface0": "#313244",
"base": "#1e1e2e",
"mantle": "#181825",
"crust": "#11111b"
},
"colors": {
"accent": "mauve",
"border": "surface1",
"borderAccent": "mauve",
"borderMuted": "surface0",
"success": "green",
"error": "red",
"warning": "yellow",
"muted": "subtext0",
"dim": "overlay0",
"text": "fg",
"thinkingText": "overlay1",
"selectedBg": "surface0",
"userMessageBg": "surface0",
"userMessageText": "fg",
"customMessageBg": "surface0",
"customMessageText": "fg",
"customMessageLabel": "mauve",
"toolPendingBg": "mantle",
"toolSuccessBg": "#1e2b22",
"toolErrorBg": "#2b1e22",
"toolTitle": "mauve",
"toolOutput": "fg",
"mdHeading": "mauve",
"mdLink": "blue",
"mdLinkUrl": "sapphire",
"mdCode": "green",
"mdCodeBlock": "fg",
"mdCodeBlockBorder": "surface1",
"mdQuote": "subtext0",
"mdQuoteBorder": "surface2",
"mdHr": "surface1",
"mdListBullet": "mauve",
"toolDiffAdded": "green",
"toolDiffRemoved": "red",
"toolDiffContext": "overlay0",
"syntaxComment": "overlay0",
"syntaxKeyword": "mauve",
"syntaxFunction": "blue",
"syntaxVariable": "fg",
"syntaxString": "green",
"syntaxNumber": "peach",
"syntaxType": "yellow",
"syntaxOperator": "sky",
"syntaxPunctuation": "overlay2",
"thinkingOff": "surface1",
"thinkingMinimal": "overlay0",
"thinkingLow": "blue",
"thinkingMedium": "teal",
"thinkingHigh": "peach",
"thinkingXhigh": "red",
"bashMode": "peach"
},
"export": {
"pageBg": "#11111b",
"cardBg": "#1e1e2e",
"infoBg": "#313244"
}
}
+2 -2
View File
@@ -209,7 +209,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
dots dots
Copyright (C) 2025 coja Copyright (C) 2026 coja
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
dots Copyright (C) 2025 coja dots Copyright (C) 2026 coja
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
+187 -4
View File
@@ -1,13 +1,196 @@
# dots
using `gnu stow` Unified dotfiles for all machines: one shared base + per-host overlays, deployed with
[GNU Stow](https://www.gnu.org/software/stow/).
Customized [Hyde](https://github.com/HyDE-Project/HyDE) ## Layout
`hyde`, `waybar` and `fish` are from hyprdots with customizations Every top-level directory is a Stow *package* (they all share one stow dir, which `--override`
requires). `bin/` holds repo tooling and is **not** a package (never stowed):
`nvim` is [nvchad](https://nvchad.com/) custom conf ```
common/ # shared everywhere (mirrors $HOME): fish, nvim, bat, btop, tmux, pi, claude, presenterm, ...
gui/ # GUI configs shared by graphical hosts: hypr, waybar, kitty, alacritty, mpv, zellij
wm/ # desktop: 4K monitors, NVIDIA, cloud LLM (duskadiy), work aliases
lw/ # laptop: no NVIDIA, roaming VPNs, network/LLM tools
fl/ # KDE Plasma box: a few Hyprland overrides + llamacpp, evremap
bin/ # tooling (not a stow package): install.sh companions
install.sh
```
A **headless** (terminal-only) machine has no overlay package — it runs `common` only. Mark any box
headless with `touch ~/.config/dots-headless` (a machine-local, hostname-independent marker); it then
skips `gui` too, so the same terminal-only setup replicates across any number of machines. In other
words `common` *is* the headless baseline; `gui` and the host packages layer graphical machines on top.
### How it deploys
Deploy order is `common → gui → <host>`; later layers win (the host package is stowed last with
`--override`, so a host file replaces the common/gui file at the same path). Stow runs with
`--no-folding`, so each *file* is symlinked individually (directories stay real) — this is what lets
a host override a single file in a shared dir without shadowing the rest.
**Editing a config edits the repo copy directly** (it's a symlink) — no deploy step. You only re-run
`install.sh` when files are **added or removed** (to create/prune symlinks). See **Syncing** below.
The repo must be cloned to **`~/.dots`** (the relative `../.dots` symlink convention). A host's
package name must match `hostname -s` (or pass the host explicitly, e.g. `./install.sh lw`).
## Install (new machine)
```fish
sudo pacman -S stow
git clone <remote> ~/.dots
cd ~/.dots
./install.sh # auto-detects host via `hostname -s`
```
`./install.sh -h` lists all options: `-n` dry-run, `-a` adopt (first run), `-D` unstow (revert),
`-h` help. GUI hosts also need **HyDE installed** first — see *HyDE dependency* below.
Once per machine afterwards: `bat cache --build` (registers the repo's custom bat themes —
without it every `bat` run warns `Unknown theme 'onedark'`), and `:Lazy restore` inside nvim
(`restore` obeys the shared `lazy-lock.json` pins; `sync`/`update` would rewrite them).
nvim's treesitter (`main` branch) builds parsers via the `tree-sitter` CLI — `pacman -S
tree-sitter-cli` first, or `:TSInstallAll` fails with `ENOENT ... 'tree-sitter'`.
### First run on a machine that still has real config files
Stow **aborts the whole package on the first conflict** (one real file blocks *everything* in
`common`), and refuses to clobber real files without `--adopt` — you'll see
`cannot stow … since neither a link nor a directory`. Adopt them, review, then keep the repo's
versions:
```fish
cd ~/.dots
git status # make sure the repo is clean first (see the restore caveat)
./install.sh -a <host> # --adopt: pull existing files into the repo, symlink them back
git diff --stat # what this machine had vs the repo (review the drift!)
git restore . # discard adopted content, keep the repo versions
```
`--adopt` overwrites the repo copies with the machine's files, so `git restore .` is what keeps the
repo versions. Cautions: adopted changes can land under `common/`/`gui/` even when host-specific, and
`git restore .` drops **all** uncommitted edits — so commit/stash anything you want first. To keep a
genuine machine change (e.g. custom pyradio `stations.csv`), hand-copy it into the right package
instead of restoring that path.
## Syncing (day-to-day)
Because configs are symlinks into the repo, **you almost never run `install.sh` to sync**:
- **Pull/propagate edits:** `cd ~/.dots && git pull` — changes are live in place instantly. No
re-stow, so **no symlink churn and no app reloads** (kitty etc. keep runtime state).
- **`bin/dotsync`** does this safely: `git pull`, then re-link via `install.sh` **only if** the pull
added/removed/renamed files (pure edits skip the re-stow). `dotsync -n` previews incoming changes.
- **`install.sh` is a (re)deploy tool, not a sync tool** — its `stow -R` deletes+recreates every
symlink, which is what makes apps reload. Only needed for the initial link or when files are
added/removed.
- **Never `stow` the repo root** (`stow .dots`, `stow */`) — it folds package dirs into `~` as stray
`~/common`, `~/gui`, … symlinks. `install.sh` only ever links package *contents*.
- **New host:** create a top-level `<name>/` package (at least a `host.fish`), commit, then
`./install.sh`. If it's terminal-only, skip the package and just `touch ~/.config/dots-headless`
(it runs `common` only).
## Tooling (`bin/`)
| Command | What it does |
|---|---|
| `./install.sh [host]` | Deploy `common → gui → <host>`. Flags: `-n` dry-run, `-a` adopt (first run), `-D`/`--unstow` revert (remove this host's symlinks; repo + real files kept), `-h` help. Self-heals: headless hosts get any stray `gui` unstowed; fails loudly (exit 1) if a package couldn't stow. |
| `./bin/dotsync [-n]` | Sync with least churn: `git pull`, re-stow only if files were added/removed. |
| `./bin/reconcile-hyde.sh [-i\|--relink] [host]` | After a HyDE update overwrote `~/.config/hypr`, reconcile HyDE's new files against your dots versions (report / `-i` keep·take·merge each / `--relink` re-symlink). |
## How per-host differences are expressed
| Difference | Mechanism |
|---|---|
| Monitors | `<h>/.config/hypr/monitors.conf` (sourced by the shared `hyprland.conf`) |
| NVIDIA | `wm/.config/hypr/nvidia.conf`, pulled in by wm's `host.conf` (fl/lw have no NVIDIA) |
| Shell aliases | shared `config.fish` sources `~/.config/fish/host.fish` (per host) |
| Bash extras | shared `.bashrc` sources `~/.bashrc.host` (optional, per host) |
| fl's Hyprland | overrides ~9 hypr files (`hyprland.conf`, keybinds, windowrules, theme colors, monitors); inherits the rest from `gui` |
| Unique apps | in the owning host overlay (fl: llamacpp/evremap; lw: xremap) |
`aichat`, `pi`, and `opencode` are **single shared configs** in `common` (LAN-first, with the cloud
provider also listed so any host can switch) — no per-host overrides.
## HyDE dependency (important for GUI hosts)
The Hyprland config is [HyDE](https://github.com/HyDE-Project/HyDE)-based. `gui/.config/hypr/*` is
your customization layer, but it **sources HyDE's framework**:
`source = ~/.local/share/hyde/hyprland.conf` — which defines `$mainMod` and other variables and is
**not tracked by dots** (HyDE generates it). So a GUI host must have **HyDE installed**, or Hyprland
fails to start (`source globbing error` + `invalid mod $mainMod`).
- Install/update HyDE with its own installer (`~/HyDE/Scripts/install.sh`); it also pulls the
required apps (`xdg-desktop-portal-hyprland`, `xdg-desktop-portal-gtk`, `hyprpolkitagent`).
- HyDE's `-r`/restore overwrites `~/.config/hypr` — use **`bin/reconcile-hyde.sh`** afterward to fold
its new defaults back against your dots configs.
- **Wallpapers and generated themes are HyDE-owned, never in dots** (`**/hyde/themes/` and
`**/wallpapers/` are gitignored — they once bloated a source repo to 255 MB). A fresh machine gets
them from HyDE, not from this repo.
## Waybar (two worlds: HyDE-generated vs the standalone rig)
**HyDE owns `~/.config/waybar/config.jsonc` AND `style.css`** — its `waybar.py` *writes* both on
any `--set`/`--update`, **through the stow symlinks, i.e. into this repo's checkout**. So dots
track neither. HyDE-managed customization goes via:
- `gui/.config/waybar/layouts/*.jsonc` — dots-tracked layouts; switch with `hyde-shell waybar --set <name>`.
- `gui/.config/waybar/modules/*.jsonc` — module defs, globbed into the generated config.
**The custom cava rig is NOT HyDE-managed**: `layouts/custom.jsonc` + `layouts/custom.css` form a
self-contained two-bar config launched raw (`waybar -c …custom.jsonc -s …custom.css` — exec-once,
the Ctrl+Alt+W / Super+Shift+R binds, and the `wbar` abbr). **Never route it through
`hyde-shell waybar --set`** — waybar.py would clobber the repo's files as above. HyDE only reads
from `layouts/`, which is what makes them safe to track. On a rig host, HyDE's own bar (systemd
user unit `hyde-<XDG_SESSION_DESKTOP>-bar.service`, auto-restarting) must be disabled once:
`systemctl --user stop <unit>; systemctl --user mask <unit>`.
Note: the Arch `waybar` build has **no native `cava`** module — the rig uses the script module
`custom/cava` running `cava-waybar.sh` (self-contained, no HyDE dependency).
## Secrets
Never committed. Put `DUSKADIY_API_KEY` etc. in `~/.config/fish/conf.d/secrets.fish` (gitignored).
`pi`'s `auth.json` is **not** tracked — it only ever holds `$VAR` references, but pi's `/login`
rewrites it with the literal key, so one stray `/login` would commit a real token. A new host
bootstraps it with `cp common/.pi/agent/auth.json.example common/.pi/agent/auth.json`, then
`./install.sh`. Also gitignored: `fish_variables`,
`greet.txt`, pi agent runtime/state, `tmux/plugins/`, `.claude/` (Claude Code project dir/worktrees),
`*.bak`/`*.log`.
## Merge notes (history — the one-time unification)
The four former per-machine repos had drifted; the merge made some judgment calls:
- **`config.fish` / `.bashrc`** were hand-reconciled into a neutral shared base + per-host
`host.fish` / `.bashrc.host` (removed a committed `fnm_multishells` runtime PATH, stray
absolute home-dir paths, duplicate git aliases).
- **`pi` lives at `~/.pi`** (repo `.pi/`); **`tmux` at `~/.config/tmux/`** (delete a legacy
`~/.tmux.conf` if present after migrating).
- **Appearance drift was unified to one canonical** (mostly wm): bat, btop, cava, fastfetch, lazygit,
mpv, pyradio, waybar, zellij, most of nvim.
- **nvim** is shared entirely (no host overrides); plugin set / `lazy-lock.json` are shared — run
`:Lazy restore` per machine once (obeys the lockfile pins — `:Lazy update` on one machine +
commit is how versions move forward; other machines converge with `restore`).
- **`zellij`** is one shared config with **vim keybinds** (h/j/k/l) + the `guvbox-dark` theme.
- **`misc/{clis,tuis}.txt`** launcher inventories use wm's lists in `common`; a machine needing its
own can override in its overlay.
## Third-party content and licensing
The repo is GPL-3.0 (`LICENSE`). What that does and doesn't cover:
- **`hypr/`, `waybar/` and the other desktop configs are derived from
[HyDE](https://github.com/HyDE-Project/HyDE)** (and, further back, legacy Hyprdots). HyDE is
itself GPL-3.0, so the derivation and this repo's licence agree — no conflict, and the
Hyprdots-era ASCII banners in those files are left in place as attribution.
- **`common/.config/aichat/dark.tmTheme`** is *One Half Dark* by Son A. Pham, **MIT**. The
licence header at the top of that file is the attribution — don't strip it.
- **`common/.config/fastfetch/pngs/`** holds project and distro marks (Arch, the Open Source
Initiative and Open Source Hardware logos, …). Trademarks aren't relicensable, so GPL-3 does
not extend to them; they are bundled as nominative use, the way most dotfiles repos do.
- **`gui/.config/hypr/hyprlock/assets/`** — the lock screen background and logo. EXIF is
stripped, so the files carry no provenance; confirm these are yours to redistribute before
publishing, or swap them for something you know the origin of.
Executable
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# dotsync — sync dotfiles with the least churn.
#
# Deployed configs are symlinks into this repo, so `git pull` makes edits LIVE instantly
# with no re-stow — meaning no symlink churn and no app reloads (kitty etc. keep their
# runtime state). Re-stowing (install.sh) is only needed when files are ADDED / REMOVED,
# to create or prune symlinks. dotsync does the minimum: pull, and re-link ONLY if the
# pull added/removed/renamed files.
#
# dotsync pull, then relink only if the file set changed
# dotsync -n preview incoming changes (fetch only; no pull, no relink)
set -uo pipefail
DOTS="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)"
cd "$DOTS" || exit 1
if [ "${1:-}" = "-n" ] || [ "${1:-}" = "--dry-run" ]; then
git fetch -q 2>/dev/null || { echo "fetch failed (offline?)"; exit 1; }
up="$(git rev-parse --abbrev-ref '@{u}' 2>/dev/null)" || { echo "no upstream set"; exit 1; }
# rev-list, not diff: a plain diff also fires when the LOCAL side is ahead, mislabelling
# your own unpushed commits as incoming. Count commits that exist only on the upstream.
n="$(git rev-list --count "HEAD..$up")"
if [ "$n" -eq 0 ]; then echo "already up to date with $up."; else
echo "incoming from $up ($n commit(s)):"; git --no-pager diff --stat "HEAD...$up"
fi
exit 0
fi
before="$(git rev-parse HEAD)"
if ! git pull --ff-only; then
echo "pull not fast-forward (local commits or conflicts) — commit/stash, then retry." >&2
exit 1
fi
after="$(git rev-parse HEAD)"
if [ "$before" = "$after" ]; then
echo "already up to date — nothing to sync."
exit 0
fi
# Services that read config ONCE at startup don't pick up pulled edits — nudge loudly.
# (Symlinked edits are live for everything else; these are the exceptions that bit us.)
if git diff --name-only "$before" "$after" | grep -q '\.config/llamacpp/' \
&& systemctl cat llama.service &>/dev/null; then
echo ""
echo "⚠⚠ llamacpp presets changed — the router reads them only at startup. Apply with:"
echo " sudo systemctl restart llama.service"
echo ""
fi
# A unit file is read from /etc, not through the stow symlink — needs daemon-reload as well.
if git diff --name-only "$before" "$after" | grep -q '\.config/whisper/' \
&& systemctl cat whisper.service &>/dev/null; then
echo ""
echo "⚠⚠ whisper unit changed — apply with:"
echo " sudo systemctl daemon-reload && sudo systemctl restart whisper.service"
echo ""
fi
# Modified files (M) are already live through the symlinks. Only Added/Deleted/Renamed
# need (un)linking, which is the only case that justifies the churny re-stow.
if git diff --name-status "$before" "$after" | grep -qE '^(A|D|R)'; then
echo "files added/removed/renamed — re-linking via install.sh (this may reload some apps)…"
exec "$DOTS/install.sh"
else
echo "edits only — already live through the symlinks; no re-stow, no reload needed."
fi
+104
View File
@@ -0,0 +1,104 @@
#
# ~/.bashrc (shared; host-specific bits in ~/.bashrc.host)
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# fzf's built-in walker ignores .ignore files; fd honours them. Mirrors config.fish.
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'
alias grep='grep --color=auto'
alias fzf="fzf --preview 'bat --color=always {}'"
alias copy="wl-copy"
alias img="kitten icat"
alias fm="yazi"
alias lg="lazygit"
alias mkdir="mkdir -p"
alias pacs="sudo pacman -Syu --noconfirm"
alias yays="yay --noconfirm --sudoloop"
alias nmaps="sudo nmap -sn 192.168.0.0/24"
alias faillock="sudo faillock --reset"
alias xremaps="sudo xremap ~/.config/xremap/config.yml"
alias dots="cd ~/.dots/ && nvim"
alias aliases="bat ~/.config/fish/config.fish"
alias ipadd="sudo ip route add 192.168.0.234 dev wg0"
# List directory
alias l="ls -l"
alias la="ls -a"
alias lla="ls -la"
alias lt="ls --tree"
# Git
alias gs='git status'
alias gca='git add -p . && git commit'
alias gd="git diff --word-diff"
alias gl='git log --graph --show-signature'
alias gla="git log --all --decorate --oneline --graph"
alias gm='git merge'
# Powerline prompt, synthwave palette (needs a Nerd Font for the glyphs)
PROMPT_DIRTRIM=3
__pl_sep=$'\ue0b0' # powerline triangle
__pl_branch=$'\ue0a0' # branch glyph
__pl_seg() { # $1=bg $2=fg $3=text (colors as "R;G;B")
[[ -n $__pl_bg ]] && PS1+="\[\e[38;2;${__pl_bg};48;2;${1}m\]${__pl_sep}\[\e[0m\]"
PS1+="\[\e[48;2;${1};38;2;${2}m\] ${3} \[\e[0m\]"
__pl_bg=$1
}
__pl_end() {
PS1+="\[\e[38;2;${__pl_bg}m\]${__pl_sep}\[\e[0m\] "
__pl_bg=''
}
__prompt() {
local s=$?
# muted synthwave: segment colors pre-blended ~40% onto the terminal bg
# (#262335) so they read as semi-transparent overlays
local fg='205;200;220'
local purple='67;56;93' green='65;107;99' yellow='114;100;67'
local red='124;39;52' pink='122;71;77'
PS1="\[\e]0;\w\a\]"
__pl_bg=''
[[ -n $SSH_TTY ]] && __pl_seg "$pink" "$fg" '\u@\h'
__pl_seg "$purple" "$fg" '\w'
local b
if b=$(git symbolic-ref --short HEAD 2>/dev/null || git describe --contains --all HEAD 2>/dev/null); then
if [[ -n $(git status --porcelain 2>/dev/null | head -n1) ]]; then
__pl_seg "$yellow" "$fg" "${__pl_branch} ${b}"
else
__pl_seg "$green" "$fg" "${__pl_branch} ${b}"
fi
fi
((s != 0)) && __pl_seg "$red" "$fg" "${s}"
[[ $EUID == 0 ]] && __pl_seg "$red" "$fg" '#'
__pl_end
}
PROMPT_COMMAND=__prompt
set -o vi
# eval "$(zoxide init --cmd cd bash)"
# colorized --help
h() { "$@" --help 2>&1 | bat --plain --language=help; }
# Record lookup against the local dmz db (adjust the db path to this machine)
lk(){
local DB="$HOME/projects/dmz/soft/lk/db.rec"
passes=0 count=0; until [ "$count" -eq "1" ] || [ "$passes" -gt 2 ] ; do \
query="$(recsel "$DB" -p aim,tag | recsel -iq "$query" -CP aim,tag | sort -u | fzf --preview="recsel \"$DB\" -e \"aim~{}\"")" \
&& count="$(recsel "$DB" -q "$query" -c )" ;\
passes=$(( passes + 1 )) ;\
done \
&& recsel "$DB" -q "$query" | recfmt -f "$HOME/projects/dmz/soft/lk/lists.fmt" | less
}
# Keepassxc search
kp() {
local DB="${KP_DB:?Error: KP_DB environment variable is not set. Check .bash_profile.}"
read -sp "Enter db password: " pass; echo ""; printf '%s\n' "$pass" | keepassxc-cli ls -q -R "$DB" | fzf | (read -r entry; printf '%s\n' "$pass" | keepassxc-cli clip -q "$DB" "$entry" 0)
}
# Per-host extras
[ -f ~/.bashrc.host ] && . ~/.bashrc.host
+56
View File
@@ -0,0 +1,56 @@
# see https://github.com/sigoden/aichat/blob/main/config.example.yaml
keybindings: vi
editor: nvim
model: local:Qwen3-Coder-30B-Instruct-UD-Q3_K_XL
# Sessions: persist REPL sessions and keep more history before summarizing.
# The default compress_threshold (4000) summarizes far too early for 24k+ windows.
save_session: true
compress_threshold: 16000
# REPL prompts show live context usage (needs max_input_tokens, set per model below)
left_prompt: '{color.green}{?session {session}{?role /}}{role}{color.cyan}{?rag @{rag}}{color.reset}> '
right_prompt: '{color.purple}{?session {consume_tokens}/{max_input_tokens} }{color.reset}'
# NOTE: temperature/top_p are intentionally unset — the LAN server applies tuned
# per-model sampling via --jinja (e.g. GLM 0.6/0.95); a global value would clobber it.
# max_input_tokens = real ctx-size (from the router) minus output headroom.
clients:
# LAN access (fast; only reachable on the home network)
- type: openai-compatible
name: local
api_base: http://192.168.0.204:11343/v1
models: &lan_models
- name: Qwen3-Coder-30B-Instruct-UD-Q3_K_XL
max_input_tokens: 30000 # ctx 32768
- name: Qwen3-Coder-Next-UD-IQ3_XXS
max_input_tokens: 128000 # ctx 131072
- name: Qwen3.6-35B-A3B-MTP-UD-IQ3_XXS
max_input_tokens: 22000 # ctx 24576
supports_vision: true
- name: Qwen3.6-35B-A3B-Thinking
max_input_tokens: 22000 # ctx 24576
supports_vision: true
- name: Qwen3.5-9B-UD-Q6_K_XL
max_input_tokens: 30000 # ctx 32768
supports_vision: true
- name: gemma-4-26B-A4B-it-UD-IQ4_XS
max_input_tokens: 22000 # ctx 24576
supports_vision: true
- name: gemma-4-E4B-it-UD-Q8_K_XL
max_input_tokens: 62000 # ctx 65536
supports_vision: true
- name: GLM-4.7-Flash-UD-Q4_K_XL
max_input_tokens: 22000 # ctx 24576
- name: gpt-oss-20b
max_input_tokens: 62000 # ctx 65536
- name: gpt-oss-20b-low
max_input_tokens: 62000 # ctx 65536
# Remote access via duskadiy.com (reachable from anywhere; requires an API key —
# $DUSKADIY_API_KEY, from the gitignored conf.d/secrets.fish). The LAN endpoint above is keyless.
# Same server, same model ids — reuse the list above via a YAML anchor.
- type: openai-compatible
name: duskadiy
api_base: https://llm.duskadiy.com/api/v1
models: *lan_models
+663
View File
@@ -0,0 +1,663 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Name: One Half Dark
Author: Son A. Pham <sp@sonpham.me>
Url: https://github.com/sonph/onehalf
License: The MIT License (MIT)
A dark Sublime Text color scheme based on Atom's One. See
github.com/sonph/onehalf for installation instructions, a light color
scheme, and versions for other editors/terminals such as Vim or iTerm.
Red: #e06c75
Green: #98c379
Yellow: #e5c07b
Blue: #61afef
Purple: #c678dd
Cyan: #56b6c2
White: #dcdfe4
Black: #282c34
Fg: #dcdfe4
Bg: #282c34
Comment: #5c6370
Gutter foreground: #919baa
Gutter background: #282c34
Selection: #474e5d
-->
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>OneHalfLight</string>
<key>semanticClass</key>
<string>theme.dark.one_half_dark</string>
<key>uuid</key>
<string></string>
<key>colorSpaceName</key>
<string>sRGB</string>
<key>author</key>
<string>Son A. Pham &lt;sp@sonpham.me&gt;</string>
<key>comment</key>
<string>A dark iTerm color scheme based on Atom&apos;s One. See github.com&#x2f;sonph&#x2f;onehalf for installation instructions, a light color scheme, and versions for other editors&#x2f;terminals such as (Neo)Vim and Sublime Text.</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#dcdfe4</string>
<key>background</key>
<string>#282c34</string>
<key>bracketsOptions</key>
<string>underline</string>
<key>caret</key>
<string>#a3b3cc</string>
<key>gutter</key>
<string>#282c34</string>
<key>gutterForeground</key>
<string>#919baa</string>
<key>invisibles</key>
<string>#5c6370</string>
<key>lineHighlight</key>
<string>#313640</string>
<key>selection</key>
<string>#474e5d</string>
<key>selectionBorder</key>
<string>#474e5d</string>
<key>tagsForeground</key>
<string></string>
<key>tagsOptions</key>
<string>stippled_underline</string>
<key>bracketContentsOptions</key>
<string>underline</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comments</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#5c6370</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Text</string>
<key>scope</key>
<string>variable.parameter.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Delimiters</string>
<key>scope</key>
<string>none</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Operators</string>
<key>scope</key>
<string>keyword.operator</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keywords</string>
<key>scope</key>
<string>keyword</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c678dd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variables</string>
<key>scope</key>
<string>variable</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Functions</string>
<key>scope</key>
<string>entity.name.function, meta.require, support.function.any-method</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Classes</string>
<key>scope</key>
<string>support.class, entity.name.class, entity.name.type.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Classes</string>
<key>scope</key>
<string>meta.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Methods</string>
<key>scope</key>
<string>keyword.other.special-method</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c678dd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Strings, Inherited Class</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Integers</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Floats</string>
<key>scope</key>
<string>none</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Boolean</string>
<key>scope</key>
<string>none</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Constants</string>
<key>scope</key>
<string>constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>HTML: Tags</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>HTML: Tag attributes</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Attribute IDs</string>
<key>scope</key>
<string>entity.other.attribute-name.id, punctuation.definition.entity</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Selector</string>
<key>scope</key>
<string>meta.selector</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c678dd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Headings</string>
<key>scope</key>
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Bold</string>
<key>scope</key>
<string>markup.bold, punctuation.definition.bold</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c678dd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Italic</string>
<key>scope</key>
<string>markup.italic, punctuation.definition.italic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c678dd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Code</string>
<key>scope</key>
<string>markup.raw.inline</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Link Text</string>
<key>scope</key>
<string>string.other.link, punctuation.definition.string.end.markdown</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Link Url</string>
<key>scope</key>
<string>meta.link</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Lists</string>
<key>scope</key>
<string>markup.list</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markdown: Quotes</string>
<key>scope</key>
<string>markup.quote</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Java Source</string>
<key>scope</key>
<string>source.java meta.class.java meta.method.java</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Java Class Body</string>
<key>scope</key>
<string>source.java meta.class.java meta.class.body.java</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Function Arguments</string>
<key>scope</key>
<string>source.js meta.function.js variable.parameter.function.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: New Variables</string>
<key>scope</key>
<string>source.js variable.other.readwrite.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Variables</string>
<key>scope</key>
<string>source.js variable.other.object.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Variables in Function Calls</string>
<key>scope</key>
<string>source.js meta.function-call.method.js variable.other.readwrite.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: New Block Variables</string>
<key>scope</key>
<string>source.js meta.block.js variable.other.readwrite.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Block Variables</string>
<key>scope</key>
<string>source.js meta.block.js variable.other.object.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Block Variables in Function Calls</string>
<key>scope</key>
<string>source.js meta.block.js meta.function-call.method.js variable.other.readwrite.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Function Calls</string>
<key>scope</key>
<string>source.js meta.function-call.method.js variable.function.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Properties</string>
<key>scope</key>
<string>source.js meta.property.object.js entity.name.function.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Javascript: Prototypes</string>
<key>scope</key>
<string>source.js support.constant.prototype.js</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Separator</string>
<key>scope</key>
<string>meta.separator</string>
<key>settings</key>
<dict>
<key>background</key>
<string></string>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Changed</string>
<key>scope</key>
<string>markup.changed</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e5c07b</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Regular Expressions</string>
<key>scope</key>
<string>string.regexp</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Escape Characters</string>
<key>scope</key>
<string>constant.character.escape</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#56b6c2</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Embedded</string>
<key>scope</key>
<string>punctuation.section.embedded, variable.interpolation</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string></string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Illegal</string>
<key>scope</key>
<string>invalid.illegal</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#e06c75</string>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Broken</string>
<key>scope</key>
<string>invalid.broken</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#e5c07b</string>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Deprecated</string>
<key>scope</key>
<string>invalid.deprecated</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#e5c07b</string>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Unimplemented</string>
<key>scope</key>
<string>invalid.unimplemented</string>
<key>settings</key>
<dict>
<key>background</key>
<string>#c678dd</string>
<key>foreground</key>
<string>#dcdfe4</string>
</dict>
</dict>
</array>
</dict>
</plist>
+9
View File
@@ -0,0 +1,9 @@
---
temperature: 0.2
---
You are a terse coding assistant.
- Lead with the code, as a single fenced block.
- Add at most 12 short sentences of explanation, and only if genuinely needed. No preamble, no "here is", no closing summary.
- Match the language and style implied by the question; if unspecified, infer the most likely one.
- If the request is ambiguous, make the most reasonable assumption and state it in one line above the code.
+11
View File
@@ -0,0 +1,11 @@
---
temperature: 0.2
---
You write a git commit message from a diff. Output ONLY the message — no preamble, no code fences, no explanation.
- Subject: `[Scope] imperative summary`, where Scope is a short tag inferred from the changed paths, matching the style of recent `git log` (e.g. [Waybar], [Fish], [LLM]). Keep it under 70 characters.
- Imperative mood ("add", "fix", "rename"), never past tense.
- For a non-trivial change, leave a blank line then add 14 terse bullets ("- ...") covering what changed and why. Omit the body for small, self-explanatory changes.
- Describe only what the diff actually shows; do not invent rationale or files.
The user's input is the diff (typically `git diff --cached`).
+9
View File
@@ -0,0 +1,9 @@
---
temperature: 0.1
---
You translate a request into a single shell command for the fish shell on Arch Linux. Output ONLY the command on one line — no code fence, no explanation.
- Use fish syntax, not bash. Notably: `set -x VAR value` (not `export`), `$status` (not `$?`), the `test` / `string` / `math` builtins, and `(cmd)` for command substitution (not `$(cmd)`). `&&` / `||` work in modern fish.
- Package management is pacman / yay (`yay -S`, `pacman -Qi`, `pacman -Qo`). Clipboard is Wayland: `wl-copy` / `wl-paste`.
- Prefer one pipeline. If several steps are unavoidable, chain them with `; and`.
- If it can't be done safely in one command, output a single `# ...` comment saying why instead.
+11
View File
@@ -0,0 +1,11 @@
---
temperature: 0.2
---
You review a code diff for defects. Be concise and specific.
- Focus on correctness bugs, edge cases, error handling, security issues, and clear regressions. Skip style and formatting nits unless they cause a bug.
- Format each finding as `file:line — issue — why it matters`, most severe first.
- Reference exact identifiers and lines from the diff; do not speculate about code you cannot see.
- If nothing substantive is wrong, say so in one line rather than inventing issues.
The user's input is the diff (typically `git diff`).
+10
View File
@@ -0,0 +1,10 @@
---
model: local:gemma-4-E4B-it-UD-Q8_K_XL
temperature: 0.1
---
You translate a request into a single shell command for the fish shell on Arch Linux. Output ONLY the command on one line — no code fence, no explanation.
- Use fish syntax, not bash. Notably: `set -x VAR value` (not `export`), `$status` (not `$?`), the `test` / `string` / `math` builtins, and `(cmd)` for command substitution (not `$(cmd)`). `&&` / `||` work in modern fish.
- Package management is pacman / yay (`yay -S`, `pacman -Qi`, `pacman -Qo`). Clipboard is Wayland: `wl-copy` / `wl-paste`.
- Prefer one pipeline. If several steps are unavoidable, chain them with `; and`.
- If it can't be done safely in one command, output a single `# ...` comment saying why instead.
@@ -4,12 +4,13 @@
# Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes` # Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes`
# for a list of all available themes # for a list of all available themes
--theme="Catppuccin Mocha" # Custom theme lives in ./themes/ — run `bat cache --build` after changing it
--style="numbers,changes,header" --theme="OneHalfDark"
--style="numbers,changes,header,snip"
# Enable this to use italic text on the terminal. This is not supported on all # Enable this to use italic text on the terminal. This is not supported on all
# terminal emulators (like tmux, by default): # terminal emulators (like tmux, by default):
#--italic-text=always --italic-text=always
# Uncomment the following line to disable automatic paging: # Uncomment the following line to disable automatic paging:
#--paging=never #--paging=never
@@ -17,10 +18,17 @@
# Uncomment the following line if you are using less version >= 551 and want to # Uncomment the following line if you are using less version >= 551 and want to
# enable mouse scrolling support in `bat` when running inside tmux. This might # enable mouse scrolling support in `bat` when running inside tmux. This might
# disable text selection, unless you press shift. # disable text selection, unless you press shift.
--pager="less --RAW-CONTROL-CHARS --quit-if-one-screen --mouse" --pager="less --RAW-CONTROL-CHARS --quit-if-one-screen --mouse --ignore-case"
# Syntax mappings: map a certain filename pattern to a language. # Syntax mappings: map a certain filename pattern to a language.
# Example 1: use the C++ syntax for Arduino .ino files # Example 1: use the C++ syntax for Arduino .ino files
# Example 2: Use ".gitignore"-style highlighting for ".ignore" files # Example 2: Use ".gitignore"-style highlighting for ".ignore" files
--map-syntax "*.ino:C++" --map-syntax "*.ino:C++"
--map-syntax ".ignore:Git Ignore" --map-syntax ".ignore:Git Ignore"
--map-syntax "**/hypr/**/*.conf:INI"
--map-syntax "**/kitty/*.conf:INI"
--map-syntax "*.ctl:INI"
--map-syntax ".inputrc:bash"
--map-syntax "**/cava/config:INI"
--map-syntax "**/mpv/*.conf:INI"
--map-syntax "**/btop/btop.conf:INI"
+260
View File
@@ -0,0 +1,260 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>One Dark (NvChad base46)</string>
<key>uuid</key>
<string>3c1e2b84-9d5f-4a7c-8b2e-6f4a1d9c7e5b</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#1e222a</string>
<key>foreground</key>
<string>#abb2bf</string>
<key>caret</key>
<string>#abb2bf</string>
<key>selection</key>
<string>#3e4451</string>
<key>lineHighlight</key>
<string>#252931</string>
<key>gutterForeground</key>
<string>#545862</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment, punctuation.definition.comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#6f737b</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number, constant</string>
<key>scope</key>
<string>constant.numeric, constant.language, constant.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d19a66</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Escape character</string>
<key>scope</key>
<string>constant.character.escape</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#a3b8ef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword, storage</string>
<key>scope</key>
<string>keyword, storage</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#c882e7</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Operator</string>
<key>scope</key>
<string>keyword.operator</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#abb2bf</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function</string>
<key>scope</key>
<string>entity.name.function, support.function, meta.function-call.generic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class, type</string>
<key>scope</key>
<string>entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, support.type, support.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e7c787</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable, variable.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function parameter</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d19a66</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag</string>
<key>scope</key>
<string>entity.name.tag, meta.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup heading</string>
<key>scope</key>
<string>markup.heading, entity.name.section</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup bold</string>
<key>scope</key>
<string>markup.bold</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup italic</string>
<key>scope</key>
<string>markup.italic</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup link</string>
<key>scope</key>
<string>markup.underline.link, string.other.link</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#61afef</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup quote</string>
<key>scope</key>
<string>markup.quote</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#6f737b</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98c379</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e06c75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid, invalid.illegal</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ffffff</string>
<key>background</key>
<string>#be5046</string>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,262 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>Synthwave 84</string>
<key>uuid</key>
<string>7b2c3a10-4b6e-4d8f-9c1a-2e5f8d7a9b3c</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#262335</string>
<key>foreground</key>
<string>#ffffff</string>
<key>caret</key>
<string>#f97e72</string>
<key>selection</key>
<string>#463465</string>
<key>lineHighlight</key>
<string>#2a2139</string>
<key>gutterForeground</key>
<string>#848bbd</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment, punctuation.definition.comment</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#848bbd</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ff8b39</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number, constant</string>
<key>scope</key>
<string>constant.numeric, constant.language, constant.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#f97e72</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Escape character</string>
<key>scope</key>
<string>constant.character.escape</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#36f9f6</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword, storage</string>
<key>scope</key>
<string>keyword, storage, keyword.operator</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fede5d</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage type</string>
<key>scope</key>
<string>storage.type, storage.modifier</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fede5d</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function</string>
<key>scope</key>
<string>entity.name.function, support.function, meta.function-call.generic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#36f9f6</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class, type</string>
<key>scope</key>
<string>entity.name.class, entity.name.type, entity.name.struct, entity.name.enum, support.type, support.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe4450</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable, variable.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ff7edb</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function parameter</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ff7edb</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fede5d</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag</string>
<key>scope</key>
<string>entity.name.tag, meta.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#72f1b8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup heading</string>
<key>scope</key>
<string>markup.heading, entity.name.section</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe4450</string>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup bold</string>
<key>scope</key>
<string>markup.bold</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>bold</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup italic</string>
<key>scope</key>
<string>markup.italic</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup link</string>
<key>scope</key>
<string>markup.underline.link, string.other.link</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#36f9f6</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Markup quote</string>
<key>scope</key>
<string>markup.quote</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#848bbd</string>
<key>fontStyle</key>
<string>italic</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff inserted</string>
<key>scope</key>
<string>markup.inserted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#72f1b8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Diff deleted</string>
<key>scope</key>
<string>markup.deleted</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#fe4450</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Invalid</string>
<key>scope</key>
<string>invalid, invalid.illegal</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ffffff</string>
<key>background</key>
<string>#fe4450</string>
</dict>
</dict>
</array>
</dict>
</plist>
@@ -1,4 +1,4 @@
#? Config file for btop v.1.4.6 #? Config file for btop v.1.4.7
#* Name of a btop++/bpytop/bashtop formatted ".theme" file, "Default" and "TTY" for builtin themes. #* 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" #* Themes should be placed in "../share/btop/themes" relative to binary or "$HOME/.config/btop/themes"
@@ -14,6 +14,11 @@ truecolor = true
#* Will force 16-color mode and TTY theme, set all graph symbols to "tty" and swap out other non tty friendly symbols. #* 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 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. #* 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. #* 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. #* Use whitespace " " as separator between different presets.
@@ -24,6 +29,9 @@ presets = "cpu:1:default,proc:0:default cpu:0:default,mem:0:default,net:0:defaul
#* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift. #* Conflicting keys for h:"help" and k:"kill" is accessible while holding shift.
vim_keys = true vim_keys = true
#* Disable all mouse events.
disable_mouse = false
#* Rounded corners on boxes, is ignored if TTY mode is ON. #* Rounded corners on boxes, is ignored if TTY mode is ON.
rounded_corners = true rounded_corners = true
@@ -60,7 +68,7 @@ update_ms = 2000
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu direct", #* 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. #* "cpu lazy" sorts top process over time (easier to follow), "cpu direct" updates top process directly.
proc_sorting = "cpu direct" proc_sorting = "cpu lazy"
#* Reverse sorting order, True or False. #* Reverse sorting order, True or False.
proc_reversed = false proc_reversed = false
@@ -92,6 +100,9 @@ proc_left = false
#* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop). #* (Linux) Filter processes tied to the Linux kernel(similar behavior to htop).
proc_filter_kernel = false 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. #* In tree-view, always accumulate child process resources in the parent process.
proc_aggregate = false proc_aggregate = false
@@ -208,6 +219,9 @@ io_graph_combined = false
#* Example: "/mnt/media:100 /:20 /boot:1". #* Example: "/mnt/media:100 /:20 /boot:1".
io_graph_speeds = "" 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. #* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False.
net_download = 100 net_download = 100
@@ -239,7 +253,7 @@ show_battery_watts = true
log_level = "WARNING" log_level = "WARNING"
#* Automatically save current settings to config file on exit. #* Automatically save current settings to config file on exit.
save_config_on_exit = true save_config_on_exit = false
#* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards. #* Measure PCIe throughput on NVIDIA cards, may impact performance on certain cards.
nvml_measure_pcie_speeds = true nvml_measure_pcie_speeds = true
@@ -250,7 +264,7 @@ rsmi_measure_pcie_speeds = true
#* Horizontally mirror the GPU graph. #* Horizontally mirror the GPU graph.
gpu_mirror_graph = true gpu_mirror_graph = true
#* Set which GPU vendors to show. Available values are "nvidia amd intel" #* Set which GPU vendors to show. Available values are "nvidia amd intel apple"
shown_gpus = "nvidia amd intel" shown_gpus = "nvidia amd intel"
#* Custom gpu0 model name, empty string to disable. #* Custom gpu0 model name, empty string to disable.
@@ -1,33 +1,33 @@
## configuration file for cava. ## Configuration file for CAVA.
# remove the ; to change parameters. # Remove the ; to change parameters.
[general] [general]
# smoothing mode. can be 'normal', 'scientific' or 'waves'. deprecated as of 0.6.0 # Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
mode = scientific # mode = scientific # deprecated since cava 0.6.0, ignored
# accepts only non-negative values. # Accepts only non-negative values.
framerate = 60 framerate = 60
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off # 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
# new as of 0.6.0 autosens of low values (dynamic range) # new as of 0.6.0 autosens of low values (dynamic range)
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. deprecated as of 0.6.0 # 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
autosens = 1 autosens = 1
; overshoot = 20 ; overshoot = 20
# manual sensitivity in %. if autosens is enabled, this will only be the initial value. # Manual sensitivity in %. If autosens is enabled, this will only be the initial value.
# 200 means double height. accepts only non-negative values. # 200 means double height. Accepts only non-negative values.
; sensitivity = 100 ; sensitivity = 100
# the number of bars (0-512). 0 sets it to auto (fill up console). # The number of bars (0-512). 0 sets it to auto (fill up console).
# bars' width and space between bars in number of characters. # Bars' width and space between bars in number of characters.
; bars = 0 ; bars = 0
; bar_width = 2 ; bar_width = 2
; bar_spacing = 1 ; bar_spacing = 1
# bar_height is only used for output in "noritake" format # bar_height is only used for output in "noritake" format
; bar_height = 32 ; bar_height = 32
# for sdl width and space between bars is in pixels, defaults are: # For SDL width and space between bars is in pixels, defaults are:
; bar_width = 20 ; bar_width = 20
; bar_spacing = 5 ; bar_spacing = 5
@@ -42,47 +42,47 @@ autosens = 1
; max_height = 100 ; max_height = 100
# lower and higher cutoff frequencies for lowest and highest bars # Lower and higher cutoff frequencies for lowest and highest bars
# the bandwidth of the visualizer. # the bandwidth of the visualizer.
# note: there is a minimum total bandwidth of 43mhz x number of bars. # Note: there is a minimum total bandwidth of 43Mhz x number of bars.
# cava will automatically increase the higher cutoff if a too low band is specified. # Cava will automatically increase the higher cutoff if a too low band is specified.
; lower_cutoff_freq = 50 ; lower_cutoff_freq = 50
; higher_cutoff_freq = 10000 ; higher_cutoff_freq = 10000
# seconds with no input before cava goes to sleep mode. cava will not perform fft or drawing and # Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
# only check for input once per second. cava will wake up once input is detected. 0 = disable. # only check for input once per second. Cava will wake up once input is detected. 0 = disable.
; sleep_timer = 0 ; sleep_timer = 0
[input] [input]
# audio capturing method. possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem' # Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem'
# defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. # Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with.
# on mac it defaults to 'portaudio' or 'fifo' # On Mac it defaults to 'portaudio' or 'fifo'
# on windows this is automatic and no input settings are needed. # On windows this is automatic and no input settings are needed.
# #
# all input methods uses the same config variable 'source' # All input methods uses the same config variable 'source'
# to define where it should get the audio. # to define where it should get the audio.
# #
# for pulseaudio and pipewire 'source' will be the source. default: 'auto', which uses the monitor source of the default sink # For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them). # (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
# #
# for pipewire 'source' will be the object name or object.serial of the device to capture from. # For pipewire 'source' will be the object name or object.serial of the device to capture from.
# both input and output devices are supported. to capture the monitor source of a sink node, append '.monitor' to the sink's object name. # Both input and output devices are supported. To capture the monitor source of a sink node, append '.monitor' to the sink's object name.
# #
# for alsa 'source' will be the capture device. # For alsa 'source' will be the capture device.
# for fifo 'source' will be the path to fifo-file. # For fifo 'source' will be the path to fifo-file.
# for shmem 'source' will be /squeezelite-aa:bb:cc:dd:ee:ff where 'aa:bb:cc:dd:ee:ff' will be squeezelite's mac address # For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
# #
# for sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. default: 'default'. # For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'.
# readme.md contains further information on how to setup cava for sndio. # README.md contains further information on how to setup CAVA for sndio.
# #
# for oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. default: '/dev/dsp', i.e. the default audio device. # For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device.
# readme.md contains further information on how to setup cava for oss on freebsd. # README.md contains further information on how to setup CAVA for OSS on FreeBSD.
# #
# for jack 'source' will be the name of the jack server to connect to, e.g. 'foobar'. default: 'default'. # For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'.
# readme.md contains further information on how to setup cava for jack. # README.md contains further information on how to setup CAVA for JACK.
# #
; method = pulse ; method = pulse
; source = auto ; source = auto
@@ -91,13 +91,13 @@ autosens = 1
; source = auto ; source = auto
; method = alsa ; method = alsa
; source = hw:loopback,1 ; source = hw:Loopback,1
; method = fifo ; method = fifo
; source = /tmp/mpd.fifo ; source = /tmp/mpd.fifo
; method = shmem ; method = shmem
; source = /squeezelite-aa:bb:cc:dd:ee:ff ; source = /squeezelite-AA:BB:CC:DD:EE:FF
; method = portaudio ; method = portaudio
; source = auto ; source = auto
@@ -111,16 +111,16 @@ autosens = 1
; method = jack ; method = jack
; source = default ; source = default
# the options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods: # The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods:
# sample_rate: fifo, pipewire, sndio, oss # sample_rate: fifo, pipewire, sndio, oss
# sample_bits: fifo, pipewire, sndio, oss # sample_bits: fifo, pipewire, sndio, oss
# channels: sndio, oss, jack # channels: sndio, oss, jack
# autoconnect: jack # autoconnect: jack
# other methods ignore these settings. # Other methods ignore these settings.
# #
# for 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported # For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported
# by the chosen audio device, the device will use other supported values instead. # by the chosen audio device, the device will use other supported values instead.
# example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it # Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it
# will use 44100, 16 and 1. # will use 44100, 16 and 1.
# #
; sample_rate = 44100 ; sample_rate = 44100
@@ -131,33 +131,33 @@ autosens = 1
[output] [output]
# output method. can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl' # Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl'
# or 'sdl_glsl'. # or 'sdl_glsl'.
# 'noncurses' (default) uses a buffer and cursor movements to only print # 'noncurses' (default) uses a buffer and cursor movements to only print
# changes from frame to frame in the terminal. uses less resources and is less # changes from frame to frame in the terminal. Uses less resources and is less
# prone to tearing (vsync issues) than 'ncurses'. # prone to tearing (vsync issues) than 'ncurses'.
# #
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data # 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
# stream of the bar heights that can be used to send to other applications. # stream of the bar heights that can be used to send to other applications.
# 'raw' defaults to 1024 bars stereo (512 bars mono), which can be adjusted in the 'bars' option above. # 'raw' defaults to 1024 bars stereo (512 bars mono), which can be adjusted in the 'bars' option above.
# #
# 'noritake' outputs a bitmap in the format expected by a noritake vfd display # 'noritake' outputs a bitmap in the format expected by a Noritake VFD display
# in graphic mode. it only support the 3000 series graphical vfds for now. # in graphic mode. It only support the 3000 series graphical VFDs for now.
# #
# 'sdl' uses the simple directmedia layer to render in a graphical context. # 'sdl' uses the Simple DirectMedia Layer to render in a graphical context.
# 'sdl_glsl' uses sdl to create an opengl context. write your own shaders or # 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or
# use one of the predefined ones. # use one of the predefined ones.
; method = noncurses ; method = noncurses
# orientation of the visualization. can be 'bottom', 'top', 'left', 'right' or # Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or
# 'horizontal'. default is 'bottom'. 'left and 'right' are only supported on sdl # 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl
# and ncruses output. 'horizontal' (bars go up and down from center) is only supported # and ncruses output. 'horizontal' (bars go up and down from center) is only supported
# on noncurses output. # on noncurses output.
# note: many fonts have weird or missing glyphs for characters used in orientations # Note: many fonts have weird or missing glyphs for characters used in orientations
# other than 'bottom', which can make output not look right. # other than 'bottom', which can make output not look right.
; orientation = bottom ; orientation = bottom
# visual channels. can be 'stereo' or 'mono'. # Visual channels. Can be 'stereo' or 'mono'.
# 'stereo' mirrors both channels with low frequencies in center. # 'stereo' mirrors both channels with low frequencies in center.
# 'mono' outputs left to right lowest to highest frequencies. # 'mono' outputs left to right lowest to highest frequencies.
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'. # 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
@@ -166,22 +166,22 @@ autosens = 1
; mono_option = average ; mono_option = average
; reverse = 0 ; reverse = 0
# raw output target. # Raw output target.
# on linux, a fifo will be created if target does not exist. # On Linux, a fifo will be created if target does not exist.
# on windows, a named pipe will be created if target does not exist. # On Windows, a named pipe will be created if target does not exist.
; raw_target = /dev/stdout ; raw_target = /dev/stdout
# raw data format. can be 'binary' or 'ascii'. # Raw data format. Can be 'binary' or 'ascii'.
; data_format = binary ; data_format = binary
# binary bit format, can be '8bit' (0-255) or '16bit' (0-65530). # Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
; bit_format = 16bit ; bit_format = 16bit
# ascii max value. in 'ascii' mode range will run from 0 to value specified here # Ascii max value. In 'ascii' mode range will run from 0 to value specified here
; ascii_max_range = 1000 ; ascii_max_range = 1000
# ascii delimiters. in ascii format each bar and frame is separated by a delimiters. # Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
# use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)). # Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
; bar_delimiter = 59 ; bar_delimiter = 59
; frame_delimiter = 10 ; frame_delimiter = 10
@@ -192,9 +192,9 @@ autosens = 1
; sdl_y= -1 ; sdl_y= -1
; sdl_full_screen = 0 ; sdl_full_screen = 0
# set label on bars on the x-axis. can be 'frequency' or 'none'. default: 'none' # set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none'
# 'frequency' displays the lower cut off frequency of the bar above. # 'frequency' displays the lower cut off frequency of the bar above.
# only supported on ncurses and noncurses output. # Only supported on ncurses and noncurses output.
; xaxis = none ; xaxis = none
# enable synchronized sync. 1 = on, 0 = off # enable synchronized sync. 1 = on, 0 = off
@@ -202,7 +202,7 @@ autosens = 1
# defaults to off since the behaviour in other terminal emulators is unknown # defaults to off since the behaviour in other terminal emulators is unknown
; synchronized_sync = 0 ; synchronized_sync = 0
# shaders for sdl_glsl, located in $home/.config/cava/shaders # Shaders for sdl_glsl, located in $HOME/.config/cava/shaders
; vertex_shader = pass_through.vert ; vertex_shader = pass_through.vert
; fragment_shader = bar_spectrum.frag ; fragment_shader = bar_spectrum.frag
@@ -210,7 +210,7 @@ autosens = 1
; continuous_rendering = 0 ; continuous_rendering = 0
# disable console blank (screen saver) in tty # disable console blank (screen saver) in tty
# (not supported on freebsd) # (Not supported on FreeBSD)
; disable_blanking = 0 ; disable_blanking = 0
# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off # show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off
@@ -221,21 +221,21 @@ autosens = 1
[color] [color]
# colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. # Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
# or defined by hex code '#xxxxxx' (hex code must be within ''). user defined colors requires # Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
# a terminal that can change color definitions such as gnome-terminal or rxvt. # a terminal that can change color definitions such as Gnome-terminal or rxvt.
# default is to keep current terminal color # default is to keep current terminal color
; background = default ; background = default
; foreground = default ; foreground = default
# sdl and sdl_glsl only support hex code colors, these are the default: # SDL and sdl_glsl only support hex code colors, these are the default:
; background = '#111111' ; background = '#111111'
; foreground = '#d34b11' ; foreground = '#D34b11'
# ; foreground = '#d34b11' # ; foreground = '#D34b11'
# gradient mode, only hex defined colors are supported, # Gradient mode, only hex defined colors are supported,
# background must also be defined in hex or remain commented out. 1 = on, 0 = off. # background must also be defined in hex or remain commented out. 1 = on, 0 = off.
# you can define as many as 8 different colors. they range from bottom to top of screen # You can define as many as 8 different colors. They range from bottom to top of screen
gradient = 1 gradient = 1
gradient_color_1 = '#d34b11' gradient_color_1 = '#d34b11'
gradient_color_2 = '#cc5933' gradient_color_2 = '#cc5933'
@@ -246,8 +246,8 @@ gradient_color_6 = '#a6cc33'
gradient_color_7 = '#80cc33' gradient_color_7 = '#80cc33'
gradient_color_8 = '#59cc33' gradient_color_8 = '#59cc33'
# horizontal is only supported on noncurses output. # Horizontal is only supported on noncurses output.
# only one color will be calculated per bar. # Only one color will be calculated per bar.
; horizontal_gradient = 1 ; horizontal_gradient = 1
; horizontal_gradient_color_1 = '#c45161' ; horizontal_gradient_color_1 = '#c45161'
; horizontal_gradient_color_2 = '#e094a0' ; horizontal_gradient_color_2 = '#e094a0'
@@ -259,39 +259,39 @@ gradient_color_8 = '#59cc33'
; horizontal_gradient_color_8 = '#434279' ; horizontal_gradient_color_8 = '#434279'
# if both vertical and horizontal gradient is enabled, vertical will be blended in this direction. # If both vertical and horizontal gradient is enabled, vertical will be blended in this direction.
# can be 'up', 'down', 'left' or 'right'. 'up' means the vertical gradient will be blended in from # Can be 'up', 'down', 'left' or 'right'. 'up' means the vertical gradient will be blended in from
# bottom to top. i.e. the bottom will be only the horizontal # bottom to top. I.e. the bottom will be only the horizontal
# and top will be only the color of the vertical gradient. # and top will be only the color of the vertical gradient.
; blend_direction = 'up' ; blend_direction = 'up'
# use theme file instead of defining colors in this file # use theme file instead of defining colors in this file
# themes are located in $home/.config/cava/themes # themes are located in $HOME/.config/cava/themes
; theme = 'none' ; theme = 'none'
[smoothing] [smoothing]
# percentage value for integral smoothing. takes values from 0 - 100. # Percentage value for integral smoothing. Takes values from 0 - 100.
# higher values means smoother, but less precise. 0 to disable. # Higher values means smoother, but less precise. 0 to disable.
# deprecated as of 0.8.0, use noise_reduction instead # DEPRECATED as of 0.8.0, use noise_reduction instead
; integral = 77 ; integral = 77
# disables or enables the so-called "monstercat smoothing" with or without "waves". set to 0 to disable. # Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
; monstercat = 0 ; monstercat = 0
; waves = 0 ; waves = 0
# set gravity percentage for "drop off". higher values means bars will drop faster. # Set gravity percentage for "drop off". Higher values means bars will drop faster.
# accepts only non-negative values. 50 means half gravity, 200 means double. set to 0 to disable "drop off". # Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
# deprecated as of 0.8.0, use noise_reduction instead # DEPRECATED as of 0.8.0, use noise_reduction instead
; gravity = 100 ; gravity = 100
# in bar height, bars that would have been lower that this will not be drawn. # In bar height, bars that would have been lower that this will not be drawn.
# deprecated as of 0.8.0 # DEPRECATED as of 0.8.0
; ignore = 0 ; ignore = 0
# noise reduction, int 0 - 100. default 77 # Noise reduction, int 0 - 100. default 77
# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth # the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth
# 100 will be very slow and smooth, 0 will be fast but noisy. # 100 will be very slow and smooth, 0 will be fast but noisy.
; noise_reduction = 77 ; noise_reduction = 77
@@ -299,9 +299,9 @@ gradient_color_8 = '#59cc33'
[eq] [eq]
# this one is tricky. you can have as much keys as you want. # This one is tricky. You can have as much keys as you want.
# remember to uncomment more than one key! more keys = more precision. # Remember to uncomment more than one key! More keys = more precision.
# look at readme.md on github for further explanations and examples. # Look at readme.md on github for further explanations and examples.
; 1 = 1 # bass ; 1 = 1 # bass
; 2 = 1 ; 2 = 1
; 3 = 1 # midtone ; 3 = 1 # midtone
+21
View File
@@ -0,0 +1,21 @@
# Global user preferences (applies to every project)
## Environment
- Arch Linux, Wayland, fish shell, kitty terminal. Prefer fish-compatible syntax in shell snippets (not bash-isms) when writing things the user will run interactively. Clipboard is `wl-copy`.
- Package manager: pacman / yay. Common abbrs: `pacs`, `yays`.
## Local-first LLMs
- The user self-hosts models on the LAN at `192.168.0.204`:
- llama.cpp server — `http://192.168.0.204:11343/v1` (OpenAI-compatible).
- Ollama — `192.168.0.204:11434`.
- Recurring models: Qwen3-Coder-30B, gemma-4-26B, DeepSeek-Coder-V2-Lite, GLM-4.7-Flash. When configuring any AI/agent tool, default to these endpoints rather than a cloud API unless asked otherwise.
## Git
- **Do not run `git commit` or `git push`, and never offer or ask to do the committing/pushing yourself** — the user reviews and commits entirely manually (these are also denied in settings.json). Stage/prepare changes and stop there. You may suggest a commit message for the user to use, but nothing more.
- To hand off a suggested commit message, write it to the per-worktree file `$(git rev-parse --git-path CLAUDE_COMMIT_MSG)`**not** `COMMIT_EDITMSG` (git overwrites that on `git commit`). A `prepare-commit-msg` hook prefills the editor from it (one-shot) on the next `git commit` / lazygit `C`. Just the raw message, no `#` comments.
## Formatting
- Lua: format with `stylua` (2-space indent, 120 col, no call parentheses — see any `.stylua.toml`).
## Dotfiles
- Configs live in the dots repo under `.config/` and are surfaced via per-directory symlinks into `~/.config/` (and `~/.claude/`). Editing a file under the repo's `.config/` is editing the live config — no deploy step.
+91
View File
@@ -0,0 +1,91 @@
# Claude Code config
Portable [Claude Code](https://claude.com/claude-code) customizations, synced via these
dots. Claude Code still uses its default `~/.claude/` directory; we surface the tracked
files below into it via per-file/dir symlinks, so edits made through Claude write straight
back into this repo.
## What's tracked here
- `settings.json` — global settings: permissions (deny git commit/push + read of secret
files), default `model`, `enabledPlugins`, vim mode, dark theme, `effortLevel`, fullscreen
TUI, `statusLine`, `hooks`, `worktree` defaults, …
- `statusline.py` — rich status line (dir, git, model, context %, cost, disk — system metrics live in the tmux bar).
See [StatusBar.md](StatusBar.md) for what each segment and colour means.
- `keybindings.json` — custom keybindings (vim-style scroll/navigation).
- `CLAUDE.md` — global user preferences applied to every project.
- `hooks/format.py` — PostToolUse hook: formats edited files by extension (stylua / ruff /
prettier / gofmt / rustfmt / fish_indent / shfmt / taplo). Opinionated formatters (stylua, ruff,
prettier, taplo) only run where the project opts in via a config file found walking up; gofmt and
rustfmt gate on a project marker (`go.mod` / `Cargo.toml`); fish_indent and shfmt run on sight.
No-ops if the formatter isn't installed, and always exits 0.
- `skills/` — custom [Agent Skills](https://code.claude.com/docs/en/skills). See **Skills** below.
- `link.sh` — idempotent bootstrap that creates the `~/.claude` symlinks below.
Paths inside `settings.json` reference `~/.claude/...` via `$HOME`/`PATH` (`/usr/bin/env
python3 ~/.claude/...`), so they work regardless of the username, python location, or where the
dots repo is cloned (`~/.dots`, …).
## What is **not** tracked (stays local, per device)
Everything else under `~/.claude/` is machine-specific or secret and must never be committed:
credentials (`.credentials.json`), session history, `projects/` (transcripts + memory),
caches, and the `plugins/` cache/binaries.
## Setup on a new device
```sh
# 1. Deploy the dotfiles repo — install.sh stows ~/.config/claude AND runs link.sh for you
cd ~/.dots && ./install.sh
# 2. Launch Claude and log in once (credentials are NOT synced)
claude
# 3. Reinstall the plugins from settings.json -> enabledPlugins
# (typescript-lsp, frontend-design — anthropics/claude-plugins-official) via /plugin.
# Optional: install any formatters you want the hook to use (stylua, ruff, prettier, …).
```
`install.sh` deploys `~/.config/claude` (via stow) and then runs `link.sh` automatically. You only
need to run `link.sh` by hand (`bash ~/.config/claude/link.sh`) if a link later gets clobbered
(e.g. Claude's `/config` replaces one with a plain file) — it's idempotent and self-healing.
A **new skill/hook** added to the repo shows up after a re-stow — `./install.sh` (or `dotsync`) links
the new file into `~/.config/claude`, which `link.sh` then surfaces into `~/.claude`.
## Skills
Custom skills live in `skills/<name>/SKILL.md` (the directory name is the `/command`) and are
linked in as `~/.claude/skills`. All are `disable-model-invocation: true` — explicit `/` only,
so Claude never auto-triggers them. Run them inside the repo whose branch you're working on.
- **`/review-branch [base]`** — reviews the current branch's diff (vs its merge-base with the
base branch) for 🐞 bugs / 🔒 security / ⚡ optimizations / 📖 readability, reports grouped
findings with `file:line`, then applies the readability + safe fixes *after you approve*. Never
commits.
Borrows `/code-review`'s discipline: CLAUDE.md-aware, changed-lines-only, verified bugs with a
false-positive filter (skips what linters/CI catch and pre-existing issues). Ends by pointing
you at the relevant built-in follow-ups (`/security-review`, `/code-review`, `/verify`, …).
- **`/pr-description [base]`** — auto-detects this repo's GitHub PR template
(`.github/pull_request_template.md`, …), fills it from the branch diff + commits, prints a
copy-paste markdown block, and copies it to the clipboard with `wl-copy`. Leaves verification
checkboxes for you; never creates/pushes the PR.
- **`/commit-msg [hint]`** — reads the **staged** diff, infers the repo's commit style from recent
`git log` (e.g. this repo's `[Scope] summary`), drafts a matching message, prints it, and copies
it to the clipboard with `wl-copy`. Never stages or commits — draft only.
- **`/pr-loop [pr]`** — babysits the current branch's PR in a self-paced loop: fixes review
comments that don't need your input — from **any** reviewer (Codex, Claude, humans) plus any
`@claude` request — `git add`s them, drafts the commit message (`/commit-msg` style) and marks
each thread with a reaction instead of an "Addressed — …" reply: **👍** fixed, **👀** parked for
your decision, **👎** a bot false positive (bots only, always with a one-line why). Then pings you
to commit & push and re-checks every ~10 min (via `ScheduleWakeup`). Resolves threads once their
fix is pushed — dismissals right away, since there's nothing to land. Stops when no unresolved comments
remain or the automated reviewers (Codex/Claude) hit their limit. Prep-only — never
commits/pushes/merges; needs `gh` authed and the session left open. Each cycle it prints when it
will re-check and that `Esc` (between cycles) stops it; on the last cycle it prints a clear
**"Finished — no more active issues"** and does not reschedule.
## Adding more config later
Drop the file/dir under `.config/claude/` here, add its name to the `items` list in `link.sh`,
and re-run `bash ~/.config/claude/link.sh`. For example `commands/` or `agents/`.
+76
View File
@@ -0,0 +1,76 @@
# Status line (`statusline.py`)
The custom Claude Code status line, ordered **work info (left) → system info (right)**,
segments joined by a light-gray `│`. It's **responsive**: always a single line — if the
rendered width would exceed the terminal (`$COLUMNS`, which Claude Code re-exports on
resize), trailing (lowest-priority) segments are dropped from the right until it fits.
Wide — everything:
```
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h │ 💾 81%
```
Narrow — trailing segments trimmed:
```
📁 ~/.dots │ 🌿 .dots main* │ 🤖 Opus 4.8 (1M) · xhigh │ 📝 84% (843k) │ 📊 34% 2h54m │ 💰 $0.40 $12.00/h
```
Every segment is defensive: if its data is missing or a command fails, the segment is
simply omitted (the bar never crashes the UI). Secondary detail (the parts in light gray)
is supplementary to the main colored value.
## Live segments
| | Segment | Shows | Notes |
|---|---|---|---|
| | **Vim mode** | `[I]`/`[N]`/`[V]` editor mode | only with `editorMode: vim`; pair with `hideVimModeIndicator: true` so the built-in one doesn't duplicate it |
| 📁 | **Directory** | Current working dir, with `~` for home | cyan |
| 🌿 | **Git** | `repo branch` + `*` if dirty + `↑N`/`↓N` ahead/behind upstream | repo = magenta; branch = green when clean, yellow + red `*` when dirty; `↑` cyan, `↓` yellow. Omitted outside a repo |
| 🤖 | **Model** | Active model display name + `· effort` level (`low`/`medium`/`high`/`xhigh`) | name = blue, effort = light gray; effort omitted for models without the param |
| 📝 | **Context** | `% of context window used` + `(Nk)` tokens | window = 1M for `[1m]` models, else 200k. Adds a red **⚠compact** at ≥80% |
| 📊 | **5h usage** | `% of the 5-hour rolling limit used` + time until it resets | from `rate_limits.five_hour`; Pro/Max only, and absent until the first API response of a session |
| 💰 | **Cost** | `$` session cost so far + `$/h` burn rate | burn rate shown once the session exceeds ~30s |
| 💾 | **Disk** | `% used` of the filesystem at the cwd | from `statvfs` |
## Colour legend
Most numbers are **green / yellow / red** by threshold — green = healthy, red = needs
attention. Light gray = secondary detail. Thresholds (`green < … < yellow < … ≤ red`):
| Segment | green | yellow | red |
|---|---|---|---|
| Context | `< 50%` | `5080%` | `≥ 80%` (⚠compact) |
| 5h usage | `< 50%` | `5080%` | `≥ 80%` |
| Disk | `< 75%` | `7590%` | `≥ 90%` |
> Light gray is `\033[37m`; dimming (`\033[2m`) is disabled because it blended the gray
> detail text into dark terminal backgrounds. To go brighter, set `"gray"` to `\033[97m`
> (bright white) in the `C` table near the top of `statusline.py`.
## Optional segments (defined but not shown)
These functions exist in `statusline.py` but aren't in the output. Enable one by adding it
to the `work` or `system` list in `main()`:
RAM/CPU/temp were **retired 2026-07-26**: the status line only refreshes on message
events, so system metrics sat visibly stale between turns — the tmux status bar
(`tmux/scripts/`) owns live system metrics now.
| | Function | Shows |
|---|---|---|
| 🧠 | `ram_segment` | `% used` + `used/totalG` from `/proc/meminfo` (green `<70%` / yellow / red `≥85%`) |
| 🖥️ | `cpu_segment` | `% busy` (diffed `/proc/stat` snapshot) + loadavg + cores (green `<60%` / red `≥85%`) |
| 🌡️ | `temp_segment` | hottest CPU thermal zone °C (green `<60` / red `≥80`) |
| ✏️ | `velocity_segment` | `+added/-removed` lines this session + lines/min |
| ♻️ | `cache_segment` | prompt-cache hit % (higher is better) |
| ⚙️ | `api_segment` | share of wall-clock time spent in API/inference |
| | `version_segment` | Claude Code version (`vX.Y.Z`) |
| 🎨 | `style_segment` | active output-style name |
## Where it lives
`statusline.py` is tracked in this repo and symlinked to `~/.claude/statusline.py`;
`settings.json` runs it via `statusLine` (`/usr/bin/env python3 ~/.claude/statusline.py`).
Edits take effect on the next status refresh.
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""PostToolUse hook: format an edited file with the right formatter.
Reads the hook JSON from stdin, extracts the edited file path, and runs a
language-appropriate formatter -- but, to avoid imposing a style on a project
that never asked for it, opinionated formatters (stylua, ruff, prettier, taplo)
only run when the project opts in via a config file discoverable by walking up
from the edited file. Canonical single-style toolchains run on sight: gofmt and
rustfmt on their go.mod / Cargo.toml marker; fish_indent and shfmt whenever the
tool itself is installed (shell and fish have one de-facto style, no config).
Best-effort: always exits 0 so a format hiccup never blocks an edit. jq is not
guaranteed on the host, hence python for the stdin JSON parse.
"""
import json
import os
import shutil
import subprocess
import sys
def find_up(start, names):
"""Nearest ancestor dir (incl. the file's own) containing any of `names`."""
d = os.path.dirname(os.path.abspath(start))
while True:
if any(os.path.exists(os.path.join(d, n)) for n in names):
return d
parent = os.path.dirname(d)
if parent == d:
return None
d = parent
def has_toml_section(start, filename, section):
"""True if the nearest ancestor `filename` contains `section`."""
d = find_up(start, [filename])
if not d:
return False
try:
with open(os.path.join(d, filename), errors="ignore") as fh:
return section in fh.read()
except Exception:
return False
def stylua_cmd(path):
exe = os.path.expanduser("~/.local/share/nvim/mason/bin/stylua")
if not os.access(exe, os.X_OK):
exe = shutil.which("stylua")
if exe and find_up(path, [".stylua.toml", "stylua.toml"]):
return [exe, path]
return None
def ruff_cmd(path):
exe = shutil.which("ruff")
if exe and (find_up(path, ["ruff.toml", ".ruff.toml"])
or has_toml_section(path, "pyproject.toml", "[tool.ruff")):
return [exe, "format", path]
return None
_PRETTIER_CFGS = [
".prettierrc", ".prettierrc.json", ".prettierrc.yaml", ".prettierrc.yml",
".prettierrc.json5", ".prettierrc.js", ".prettierrc.cjs", ".prettierrc.mjs",
".prettierrc.toml", "prettier.config.js", "prettier.config.cjs",
"prettier.config.mjs",
]
def prettier_cmd(path):
exe = shutil.which("prettier")
if exe and find_up(path, _PRETTIER_CFGS):
return [exe, "--write", path]
return None
def gofmt_cmd(path):
exe = shutil.which("gofmt")
if exe and find_up(path, ["go.mod"]): # one canonical style
return [exe, "-w", path]
return None
def rustfmt_cmd(path):
exe = shutil.which("rustfmt")
if exe and find_up(path, ["Cargo.toml", "rustfmt.toml", ".rustfmt.toml"]):
return [exe, path]
return None
def fish_cmd(path):
exe = shutil.which("fish_indent") # ships with fish, one canonical style
return [exe, "-w", path] if exe else None
def shfmt_cmd(path):
exe = shutil.which("shfmt") # de-facto shell formatter, sane defaults
return [exe, "-w", path] if exe else None
def taplo_cmd(path):
exe = shutil.which("taplo")
# opt-in: TOML reflow/align is intrusive, so only where the project asked.
if exe and find_up(path, ["taplo.toml", ".taplo.toml"]):
return [exe, "format", path]
return None
HANDLERS = {
".lua": stylua_cmd,
".py": ruff_cmd,
".js": prettier_cmd, ".jsx": prettier_cmd, ".mjs": prettier_cmd,
".cjs": prettier_cmd, ".ts": prettier_cmd, ".tsx": prettier_cmd,
".css": prettier_cmd, ".scss": prettier_cmd, ".less": prettier_cmd,
".html": prettier_cmd, ".vue": prettier_cmd, ".json": prettier_cmd,
".md": prettier_cmd, ".yaml": prettier_cmd, ".yml": prettier_cmd,
".go": gofmt_cmd,
".rs": rustfmt_cmd,
".fish": fish_cmd,
".sh": shfmt_cmd, ".bash": shfmt_cmd,
".toml": taplo_cmd,
}
def main():
try:
data = json.load(sys.stdin)
except Exception:
return
ti = data.get("tool_input") or {}
tr = data.get("tool_response") or {}
path = ti.get("file_path") or tr.get("filePath") or ""
if not path or not os.path.isfile(path):
return
handler = HANDLERS.get(os.path.splitext(path)[1].lower())
if not handler:
return
cmd = handler(path)
if not cmd:
return
try:
subprocess.run(cmd, timeout=15, capture_output=True)
except Exception:
pass
if __name__ == "__main__":
main()
+36
View File
@@ -0,0 +1,36 @@
{
"$schema": "https://www.schemastore.org/claude-code-keybindings.json",
"$docs": "https://code.claude.com/docs/en/keybindings",
"bindings": [
{
"context": "Global",
"bindings": {
"alt+j": "scroll:lineDown",
"alt+k": "scroll:lineUp",
"ctrl+shift+j": "scroll:lineDown",
"ctrl+shift+k": "scroll:lineUp"
}
},
{
"context": "MessageSelector",
"bindings": {
"g": "messageSelector:top",
"shift+g": "messageSelector:bottom"
}
},
{
"context": "Select",
"bindings": {
"g": "select:first",
"shift+g": "select:last"
}
},
{
"context": "Footer",
"bindings": {
"j": "footer:down",
"k": "footer:up"
}
}
]
}
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Surface this repo's Claude Code config into ~/.claude via symlinks.
#
# `stow` deploys the repo to ~/.config/claude; Claude itself reads from ~/.claude,
# so we link the individual files/dirs across. Idempotent and self-healing: re-run
# any time. It also flags any file that has broken out of stow into a plain file —
# which silently stops Claude's edits from reaching the repo.
# (New skills/hooks added to the repo appear after a re-stow: `./install.sh` or `dotsync`.)
#
# bash ~/.config/claude/link.sh
set -eu
SRC="$HOME/.config/claude"
DST="$HOME/.claude"
if [ ! -e "$SRC" ]; then
echo "error: $SRC not found — run 'cd ~/.dots && ./install.sh' first" >&2
exit 1
fi
mkdir -p "$DST"
# This repo's claude dir, resolved via link.sh's own symlink (location-independent).
repo=$(dirname "$(readlink -f "$SRC/link.sh")")
# If link.sh itself has broken out of stow into a plain file, $repo resolves to $SRC and
# the drift heal below would relink each drifted file onto ITSELF — destroying the only
# copy of its content. Refuse to continue.
if [ "$repo" = "$SRC" ]; then
echo "error: link.sh itself is a plain file (broken out of stow) — re-stow first:" >&2
echo " cd ~/.dots && ./install.sh" >&2
exit 1
fi
# Tracked items to expose in ~/.claude. Add new ones (e.g. commands agents) here.
items="settings.json statusline.py keybindings.json CLAUDE.md hooks skills"
# Expose each item in ~/.claude (where Claude actually reads).
for item in $items; do
[ -e "$SRC/$item" ] || continue
if [ -d "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
echo "skip ~/.claude/$item (real directory in the way — move it aside first)" >&2
continue
fi
# A plain FILE here means an atomic write replaced the link (Claude writes e.g.
# ~/.claude/settings.json) — it may hold newer edits, so never clobber it silently:
# heal only when provably lossless, warn otherwise (mirrors the SRC drift guard below).
if [ -f "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
same=0
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$DST/$item" "$SRC/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$DST/$item" "$SRC/$item" && same=1 ;;
esac
if [ "$same" != 1 ]; then
echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2
echo " it may hold newer edits. Review, then re-link:" >&2
echo " diff '$DST/$item' '$SRC/$item' # compare; keep whichever is right" >&2
echo " ln -sfn '$SRC/$item' '$DST/$item' # re-link" >&2
continue
fi
echo "healed ~/.claude/$item (plain copy identical to repo — re-linking)"
fi
ln -sfn "$SRC/$item" "$DST/$item"
echo "linked ~/.claude/$item -> ~/.config/claude/$item"
done
# Stow-layer drift guard: each SRC *file* should be a symlink into the repo, so edits
# Claude writes (e.g. via /config) flow back and stay tracked. If an atomic write
# replaced one with a plain file, it has silently broken out of stow. Auto-heal only
# when provably lossless — the plain file is identical to the repo copy (JSON compared
# ignoring key order, since /config often just reorders keys). Otherwise warn and stop:
# the file may hold newer edits worth diffing in before re-linking.
for item in $items; do
[ -f "$SRC/$item" ] && [ ! -L "$SRC/$item" ] || continue # a real file where a link belongs
same=0
if [ -f "$repo/$item" ]; then
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$SRC/$item" "$repo/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$SRC/$item" "$repo/$item" && same=1 ;;
esac
fi
if [ "$same" = 1 ]; then
ln -sfrn "$repo/$item" "$SRC/$item" # -r: relative link, so stow keeps owning it
echo "healed ~/.config/claude/$item (plain copy identical to repo — re-linked)"
continue
fi
echo "DRIFT ~/.config/claude/$item is a real file that differs from the repo copy —" >&2
echo " it may hold edits not yet tracked. Review, then re-link:" >&2
echo " diff '$SRC/$item' '$repo/$item' # compare; keep whichever is right" >&2
echo " ln -sfrn '$repo/$item' '$SRC/$item' # re-link (relative, stow-owned)" >&2
done
+79
View File
@@ -0,0 +1,79 @@
{
"attribution": {
"commit": "",
"pr": ""
},
"permissions": {
"deny": [
"Bash(git push)",
"Bash(git push:*)",
"Bash(git commit)",
"Bash(git commit:*)",
"Bash(git reset --hard)",
"Bash(git reset --hard:*)",
"Bash(git clean)",
"Bash(git clean:*)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/*.env)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/*.p12)",
"Read(**/*.pfx)",
"Read(**/*.keystore)",
"Read(**/id_rsa*)",
"Read(**/.ssh/**)",
"Read(**/.aws/**)",
"Read(**/.npmrc)",
"Read(**/.netrc)",
"Read(**/.git-credentials)",
"Read(**/.claude/.credentials.json)",
"Read(**/secrets.fish)",
"Read(**/*.token)",
"Read(**/.envrc)"
],
"defaultMode": "auto"
},
"model": "opus[1m]",
"enableWorkflows": false,
"skipWorkflowUsageWarning": true,
"enabledPlugins": {
"typescript-lsp@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true
},
"statusLine": {
"type": "command",
"command": "/usr/bin/env python3 ~/.claude/statusline.py",
"padding": 0,
"hideVimModeIndicator": true
},
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "/usr/bin/env python3 ~/.claude/hooks/format.py",
"timeout": 20,
"statusMessage": "format"
}
]
}
]
},
"worktree": {
"baseRef": "fresh"
},
"effortLevel": "xhigh",
"advisorModel": "fable",
"tui": "fullscreen",
"theme": "dark",
"editorMode": "vim",
"verbose": true,
"autoCompactEnabled": true,
"remoteControlAtStartup": false,
"inputNeededNotifEnabled": false,
"agentPushNotifEnabled": false,
"skipAutoPermissionPrompt": true
}
@@ -0,0 +1,42 @@
---
description: Draft a commit message for the staged changes, matching this repo's commit style, then print it in the reply, save it to the repo's CLAUDE_COMMIT_MSG file, and copy it to the clipboard. Never stages or commits. Use once you've staged what you want to commit.
argument-hint: [optional focus/scope hint]
disable-model-invocation: true
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git rev-parse:*), Bash(wl-copy:*), Read, Glob, Write
---
# Draft a commit message for the staged changes
Context for the current commit — what's staged, plus this repo's recent style:
!`echo "=== Staged (git diff --cached --stat) ==="; git --no-pager diff --cached --stat 2>/dev/null; echo; echo "=== Working tree (git status --short) ==="; git --no-pager status --short 2>/dev/null; echo; echo "=== Recent subjects — match this convention ==="; git --no-pager log -20 --pretty=format:'%s' 2>/dev/null`
## What to do
1. **See exactly what's being committed.** Read the full staged diff with
`git --no-pager diff --cached`. If nothing is staged (the stat above is empty), tell me and
**stop** — I stage changes myself; never run `git add`.
2. **Learn the repo's commit style** from the recent subjects above — prefix convention (this repo
uses `[Scope] summary`), imperative vs. past tense, subject length, whether bodies are used.
Match what's already there; don't impose Conventional Commits / gitmoji if the repo doesn't use
them.
3. **Write the message from the *actual* staged diff** (not a guess):
- A concise subject in the repo's style (~50 chars, imperative). Fold in my hint if given:
**$ARGUMENTS**.
- Add a short body (wrapped ~72 cols) only when the change needs the *why*; otherwise
subject-only. Don't invent motivation that isn't evident from the diff.
4. **Print the message in your reply first** — as a single fenced ` ```text ` block, copy-paste
ready. This in-reply copy is the durable one: it lives in the transcript, so it survives even if
the clipboard gets overwritten later (by me copying something else, or another Claude session
running `wl-copy`). Always print it; don't rely on the clipboard alone.
5. **Save it to a file in the repo** so I can recover it any time: resolve the path with
`git rev-parse --git-path CLAUDE_COMMIT_MSG` (per-worktree correct), then use the Write tool to
write the raw message there — **not** `COMMIT_EDITMSG`, which git overwrites on every
`git commit` before the editor opens. My `prepare-commit-msg` hook prefills the commit editor
from `CLAUDE_COMMIT_MSG` (one-shot) on the next `git commit` / lazygit `C`. The file lives
inside the git dir so it never shows up in `git status`.
6. **Also copy it to the clipboard** as a convenience: `wl-copy < <path from step 5>`. Then
tell me all three places it is: **printed above**, **saved to `CLAUDE_COMMIT_MSG`** (hook
prefills the next commit), and **on the clipboard** — so I never lose it to a stray copy.
7. **Never** run `git add`, `git commit`, or `git push`, and never offer to — I stage and commit
manually. Draft only.
@@ -0,0 +1,36 @@
---
description: Draft a ready-to-paste PR description by filling this repo's GitHub PR template from the branch diff and commits, then copy it to the clipboard. Use after finishing a feature.
argument-hint: [base-branch]
disable-model-invocation: true
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git merge-base:*), Bash(git symbolic-ref:*), Bash(git rev-parse:*), Bash(git show-ref:*), Bash(ls:*), Bash(cat:*), Bash(wl-copy:*), Bash(mktemp:*), Bash(rm:*), Read, Glob
---
# Draft this branch's PR description
This repo's PR template (first match), plus the branch's changes:
!`for f in .github/pull_request_template.md .github/PULL_REQUEST_TEMPLATE.md docs/pull_request_template.md docs/PULL_REQUEST_TEMPLATE.md PULL_REQUEST_TEMPLATE.md pull_request_template.md; do if [ -f "$f" ]; then echo "=== PR template: $f ==="; cat "$f"; found=1; break; fi; done; if [ -z "$found" ]; then if [ -d .github/PULL_REQUEST_TEMPLATE ]; then echo "=== template dir .github/PULL_REQUEST_TEMPLATE/ (pick one) ==="; ls .github/PULL_REQUEST_TEMPLATE/; else echo "(no PR template found — use the fallback structure)"; fi; fi`
!`b=$(git symbolic-ref --short -q refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@'); [ -z "$b" ] && { git show-ref -q --verify refs/heads/main && b=main || b=master; }; m=$(git merge-base "$b" HEAD 2>/dev/null); echo "Branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null) Base: $b Range: ${m:-?}..HEAD"; echo; echo "Commits:"; git --no-pager log --oneline "${m}..HEAD" 2>/dev/null; echo; echo "Files changed:"; git --no-pager diff --stat "${m}...HEAD" 2>/dev/null`
## What to do
1. **Resolve the base branch** (use the invocation argument if given — **$ARGUMENTS** — else the
detected base) and read the full diff + commit messages for `merge-base..HEAD`.
2. **Fill the PR template shown above** from the *actual* changes:
- Write the Summary / motivation and any "Implementation Notes" from the diff and commits.
- Fill the **Type of Change** field or boxes with the kinds that genuinely match (feature /
fix / refactor / perf / tests / docs / chore).
- Tick `Self-reviewed` and `LLM-assisted` if those boxes exist.
- **Leave verification boxes unchecked** — e.g. "tests passed", "`validate:full` passes",
"pre-commit hooks passed". I confirm those myself; never tick them for me.
- Fill **Related Issues** from issue numbers in the branch name or commit trailers; delete any
placeholder lines you can't fill (never leave a dangling `Closes #`), and drop the whole
section if there are no issues to reference.
- If a multi-template dir was listed (no single file), pick the most fitting one and say which.
- **If no template exists**, use this fallback: `## Summary` / `## Changes` / `## Testing` /
`## Notes`.
3. **Output** the filled description as a single fenced ```markdown code block, copy-paste ready.
4. **Copy it to the clipboard**: write the same markdown to a temp file (`mktemp`) and run
`wl-copy < "$tmpfile"`, then remove the temp file. Tell me it's on the clipboard.
5. **Do not** create, push, or edit the PR (`gh pr ...`) — I add the text to the PR myself.
@@ -0,0 +1,275 @@
---
description: Babysit the current branch's PR — address review comments that don't need my input, re-check every ~10 min, and stop when there are no open comments left or the automated reviewers (Codex, Claude) hit their limit. Prep-only — stages fixes and drafts the commit message; never commits, pushes, or merges.
argument-hint: [pr number or url — optional; defaults to the current branch's PR]
disable-model-invocation: true
allowed-tools: Bash(gh:*), Bash(git status:*), Bash(git diff:*), Bash(git log:*), Bash(git rev-parse:*), Bash(git fetch:*), Bash(git add:*), Bash(wl-copy:*), Read, Edit, Write, Grep, Glob, ScheduleWakeup
---
# Watch this PR and resolve review comments (prep-only)
A self-paced loop over one PR's review feedback: fix the comments that don't need my judgement,
stage them, draft a commit message, then wait ~10 min and re-check — until there are no unresolved
comments left or the automated reviewers (Codex/Claude) hit a usage limit.
**The contract — read this first:**
- **I never commit, push, or merge.** Each cycle you *prepare*: edit + `git add` + draft a commit
message + reply on the threads you handled. Then you **ping me to commit & push**, and the loop
resumes after I do. This honours the global no-commit/no-push rule; nothing in `settings.json`
changes.
- **React, don't reply — then resolve.** A handled thread gets a 👍 **reaction** on the reviewer's
comment while unpushed — never an "Addressed — …" reply, those are just noise. Once I've pushed
and its fix is live, you **resolve the thread on GitHub** (or minimize the bot comment as
RESOLVED) rather than leaving it open — see step 6. Never resolve unpushed work or a needs-input
thread. The **only** comments you ever post are the one-line decision questions for needs-input
items (step 7) and the one-line reason on a dismissal.
- **The marker vocabulary — exactly these three, never others.** Every item you triage gets exactly
one reaction, so I can read the PR's state at a glance without opening threads:
- 👍 `+1`**addressed**: fix staged; the thread gets resolved once I push.
- 👀 `eyes`**parked**: needs a decision from me, paired with the one-line question (step 7).
- 👎 `-1`**dismissed**: a *bot* finding that's wrong, paired with a one-line why, then
resolved/minimized straight away (step 8).
**Never 👎 a human's comment.** If I'm the one who's wrong, write the reasoning as a reply and
park it as 👀 — I decide, not you. And don't invent other reactions (🎉/❤️/🚀/😄/😕): an
unexplained emoji is worse than no emoji.
- The 10-minute cadence uses `ScheduleWakeup`, so the loop only advances **while this Claude
session stays open**. **To stop it: press `Esc` while I'm idle between cycles** (that clears the
queued wake-up); closing the session also stops it. A plain message does **not** cancel a pending
wake-up — use `Esc`.
- **Every token counts.** Each wake-up re-reads the whole conversation with a cold prompt cache
(the 10-min cadence outlives the 5-min cache TTL), so cost compounds with every cycle and every
extra line in context. Therefore: fetch **filtered** data only (use the `--jq` filters below —
never let raw unfiltered JSON into the conversation), summarize deltas rather than restating
unchanged state, and keep idle cycles to **one cheap API call and ≤2 lines of output**.
Current state (auth + the current branch's PR):
!`echo "=== gh auth ==="; gh auth status 2>&1 | head -3; echo; echo "=== PR (current branch) ==="; gh pr view --json number,url,state,headRefName,baseRefName,headRefOid 2>/dev/null || echo "(no open PR for current branch — or gh not authed)"; echo; echo "=== repo ==="; gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null`
## Each cycle (one invocation / wake-up)
1. **Preflight.** If `gh auth status` failed above → **stop** and tell me to run `! gh auth login`,
then re-run `/pr-loop`. Do nothing else. Capture `owner`/`repo` from `nameWithOwner`.
2. **Load state & resolve the PR.** Read `<git-dir>/pr-loop-state.json` (find `<git-dir>` with
`git rev-parse --absolute-git-dir`). It's inside `.git/`, so it never shows in `git status` and
survives wake-ups. Shape:
```json
{ "number": 0, "lastHeadOid": "", "lastSeenUpdatedAt": "", "handledThreadIds": [],
"handledCommentIds": [], "needsInputThreadIds": [], "needsInputCommentIds": [],
"dismissedIds": [], "awaitingPush": false, "idleCount": 0, "cycleCount": 0 }
```
Resolve the target PR **in this order**: my argument if given (**$ARGUMENTS**) → the `number`
in the state file (wake-ups re-invoke `/pr-loop` *without* arguments — the state file is the
durable copy) → the current branch's PR from the context block. If none → **stop** and say so.
If the state file is missing or for a different `number`, start fresh. Increment `cycleCount`.
**Then print one status line before any network call** — so I always see the loop is alive:
> 🔄 pr-loop cycle <N> — PR #<num>: checking…
3. **Cheap activity check — one API call, before any heavy fetch.**
`gh pr view NUM --json headRefOid,updatedAt`
- If `updatedAt == lastSeenUpdatedAt` **and** `headRefOid == lastHeadOid` → nothing happened at
all (no comment, review, or push). This is an **idle cycle**: `idleCount += 1`; if a safety
cap (step 5) is now exceeded, STOP per step 5. Otherwise save state and jump straight to
step 10's reschedule. Total output ≤2 lines — if `awaitingPush`, one line is a brief
commit-&-push reminder (remind at most twice across idle cycles, then just the status line).
- Otherwise: remember the fetched `headRefOid` for step 6 and continue. (Don't update
`lastSeenUpdatedAt` yet — step 10 refreshes it *after* you've posted replies, so your own
replies don't defeat the next cycle's cheap check.)
4. **Fetch review state — filtered at the source, both calls in parallel** (one message, two
tool calls). Never fetch unfiltered `reviews,comments`.
Unresolved review threads (resolved/unresolved is GraphQL-only):
```bash
gh api graphql --paginate -F owner=OWNER -F repo=REPO -F number=NUM -f query='
query($owner:String!,$repo:String!,$number:Int!,$cursor:String){
repository(owner:$owner,name:$repo){ pullRequest(number:$number){
reviewThreads(first:100,after:$cursor){ pageInfo{hasNextPage endCursor}
nodes{ id isResolved isOutdated
comments(first:100){ nodes{ id databaseId author{login} path line body } } } } } } }' \
--jq '.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved | not)
| {id, isOutdated,
comments: [.comments.nodes[] | {id, databaseId, author: .author.login, path, line, body}]}'
```
Mind the two id kinds: the thread's own `id` (`PRRT_…`) is what you resolve; a
`comments[].id` (`PRRC_…`) is what you react to.
Automated-reviewer activity + `@claude` requests from the PR conversation — latest review per
bot (for the limit check in step 5) and only the relevant comments, not the whole history:
```bash
gh pr view NUM --json reviews,comments --jq '{
latestBotReviews: ([.reviews[] | select(.author.login | test("codex|claude"; "i"))]
| group_by(.author.login) | map(max_by(.submittedAt))
| map({author: .author.login, state, submittedAt, body})),
relevantComments: ([.comments[] | select((.isMinimized | not)
and ((.body | test("@claude")) or (.author.login | test("codex|claude"; "i"))))
| {id, author: .author.login, body}] | .[-10:])
}'
```
Act on feedback from **any** reviewer — especially the automated ones, **Codex and Claude**
(`author.login` matching `codex`/`claude`, or a Bot account). Treat any comment — inline review
**or** PR conversation — whose body **mentions `@claude`** as an explicit request to act on,
same as a review comment.
Some bots post **findings as a plain PR-conversation comment** rather than inline review threads
(e.g. Claude when its tooling can't post inline). Triage those findings like thread findings;
since they have no thread, you "resolve" them by **minimizing** the comment once fixed (step 8).
`relevantComments[].id` is the comment's GraphQL node id (`IC_…`) used for that, and the
`isMinimized` filter above already drops ones you've resolved. A bot's pure **summary / status /
usage-limit** comment carries no finding — triage/minimize only comments with concrete asks.
5. **Check stop conditions — before doing any work:**
- **Nothing left to address** → no unresolved review threads, no un-minimized bot **findings**
comments, and no open `@claude` requests. Anything in `dismissedIds` doesn't count as
outstanding (if a resolve/minimize failed, the 👎 + reason still stands). The PR is clean:
report done, save state, and **do not** schedule another wake-up. STOP.
- **The automated reviewers are spent** → an automated reviewer's most recent review/comment
body matches a limit signal (case-insensitive: `rate/usage/quota/credit … limit`,
`limit reached`, `exceeded … quota`, `out of … credits`). Automated reviewers = authors whose
`login` matches `codex` or `claude` (or a `[bot]` that leaves review comments; confirm with
`gh api users/<login> --jq .type``Bot`). STOP and report which reviewer hit the limit
**only** when no unresolved threads (incl. `@claude` requests) remain that you can still act
on. If one reviewer is limited but other threads still need work, handle those first, then
re-check.
- **Safety caps** (these apply on idle cycles too) → if `cycleCount > 20`, or `idleCount > 4`,
or `gh api rate_limit --jq .resources.core.remaining` is `< 100` → STOP and hand back to me
with a summary.
**When any stop fires:** save state, do **not** call `ScheduleWakeup`, and end with a clear
final line so I know the loop is over and won't run again — e.g.:
> ✅ **Finished — no more active issues.** I won't re-check again.
Adapt it to the reason: `✅ Finished — <reviewer> hit its usage limit; nothing left to address,
I won't re-check again.` or `⏹ Stopped — hit safety cap (<which>); re-run \`/pr-loop\` to resume.`
6. **Did I push since last cycle?** Compare the `headRefOid` from step 3 to `lastHeadOid` (no
extra API call).
- If **unchanged and `awaitingPush` is true** → I haven't pushed yet. Don't re-fix anything for
threads already handled: `idleCount += 1`, then continue with step 7 **only for genuinely new
threads/comments** (something new must exist, or step 3 would have short-circuited).
- If **changed** → I pushed. Set `awaitingPush=false`, `idleCount=0`, update `lastHeadOid`. Then,
**before handling anything new, resolve on GitHub every already-handled item whose fix is now
live** — don't leave them lingering as open, 👍-only threads. For each id in `handledThreadIds`
that is still unresolved and not re-flagged, run `resolveReviewThread`; for each id in
`handledCommentIds` not re-flagged, `minimizeComment` as RESOLVED (mutations in step 8).
Resolved threads drop out of the step-4 fetch, so each fix is resolved exactly once. Then
continue to any genuinely new threads.
7. **Triage** each unresolved review thread, every bot **findings** comment on the PR conversation,
and every `@claude` request **not already handled**:
- **Auto-handle (no input needed):** typos, lint/format, naming, missing null/error checks,
applying the reviewer's concrete suggested diff, docs/comments, and localized bugs with one
correct fix — **including fixes that touch a shared type/contract or span multiple layers,
when the repo's existing conventions determine the shape.** Blast radius (multi-file,
contract-touching, "I'd have to pick among a few representations") is **not** a reason to
defer: pick the minimal idiomatic shape that matches existing patterns, implement it, and let
me veto. A reviewer finding that names the concrete fix is almost always auto-handleable.
- **Leave for me (needs input) — 👀:** defer **only** for genuine ambiguity — two or more
*materially different* correct behaviours, a real security/performance trade-off, or missing
product/domain knowledge that existing code can't settle. Never guess these. Post the
`path:line` + a **one-line decision I can answer in a word**, and record the id in
`needsInputThreadIds` (or `needsInputCommentIds` for a plain conversation comment) so step 3
re-surfaces it every idle cycle instead of letting it fall silent.
- **Not an issue (dismiss) — 👎:** a **bot** finding that is simply wrong — it misread the code,
the behaviour is intentional and the surrounding code proves it, or the concern is already
handled elsewhere. Don't "fix" it to make it go away, and don't park it as needs-input either:
that leaves a false positive propping the loop open until a safety cap fires. Reply with the
one-line reason (facts, not opinion: the line/behaviour that disproves it), 👎 it, then resolve
the thread / minimize the comment **immediately** — no push required, since there's no fix to
land. Record the id in `dismissedIds` so a re-fetch can't re-litigate it.
**Only bots get dismissed.** A *human* comment you think is wrong is a needs-input item: state
your reasoning in a reply, mark it 👀, and let me settle it. If you'd be dismissing more than
one or two findings in a cycle, you're probably the one who's wrong — park them for me instead.
Apply the same split to Codex's and Claude's review suggestions and to every `@claude` request:
a concrete ask is auto-handled; a genuine judgement call is left for me. Skip anything whose id
is already in `handledThreadIds` / `handledCommentIds` / `dismissedIds`. When I answer a needs-input item (or you
push a fix for it), drop its id from the needs-input arrays and handle/resolve it normally.
8. **Prepare the auto-handled set (no commit, no push):**
- Apply the edits (the PostToolUse format hook auto-formats). `git add` the changed files.
- **Mark every triaged item with its reaction — never an "Addressed — …" reply.** The reaction
goes on the reviewer's comment (for a thread, its *first* comment — the finding itself):
👍 what you fixed this cycle, 👀 what you parked for me, 👎 a dismissed bot finding.
One mutation covers both surfaces. Pass the **comment** node id — `comments[].id` (`PRRC_…`)
from the thread query, or `relevantComments[].id` (`IC_…`) for a conversation comment; *not*
the thread's own `PRRT_…` id. `content` is `THUMBS_UP` / `EYES` / `THUMBS_DOWN`:
```bash
gh api graphql -f query='mutation($id:ID!,$c:ReactionContent!){
addReaction(input:{subjectId:$id,content:$c}){ reaction{ content } } }' \
-F id=COMMENT_NODE_ID -F c=THUMBS_UP
```
Re-adding the same reaction is a harmless no-op, so a repeat cycle can't double-post. When an
item **changes class** — I answer a 👀, or a reviewer re-flags something you'd 👍'd — clear the
stale marker first, same call shape, so nothing ever carries two contradictory markers:
```bash
gh api graphql -f query='mutation($id:ID!,$c:ReactionContent!){
removeReaction(input:{subjectId:$id,content:$c}){ reaction{ content } } }' \
-F id=COMMENT_NODE_ID -F c=EYES
```
- Add handled review-thread ids to `handledThreadIds` and handled conversation/`@claude`
comment ids to `handledCommentIds`; set `awaitingPush=true`.
- **Resolve** a thread — `gh api graphql -f query='mutation($id:ID!){
resolveReviewThread(input:{threadId:$id}){ thread{ id isResolved } } }' -F id=THREAD_ID` —
**only** once its fix is live (a cycle where `headRefOid` advanced, per step 6) and the
reviewer hasn't re-flagged it. Never resolve unpushed work or a "needs input" thread. This
resolves the review *conversation*, not any linked GitHub Issue.
**The one exception is a 👎 dismissal:** there's no fix to land, so resolve/minimize it in the
same cycle you post the reason — otherwise the false positive keeps the loop alive forever.
- **Resolve a plain PR-conversation findings comment** (a bot finding with no inline thread —
e.g. Claude's) by **minimizing it as resolved** — the issue-comment equivalent, under the same
rules (only once its fix is live and not re-flagged):
```bash
gh api graphql -f query='mutation($id:ID!){ minimizeComment(input:{subjectId:$id,
classifier:RESOLVED}){ minimizedComment{ isMinimized } } }' -F id=COMMENT_NODE_ID
```
`COMMENT_NODE_ID` is the `relevantComments[].id` (`IC_…`). Never minimize unpushed work, a
"needs input" comment, or a summary/status/usage-limit comment.
9. **Draft the commit message — exactly like `/commit-msg`:** from the staged diff and the repo's
recent `git log` style (this repo uses `[Scope] summary`), write a message that matches. Then:
- **(a) Print it** in your reply as a fenced ` ```text ` block — the durable copy.
- **(b) Write it** with the Write tool to the path from `git rev-parse --git-path CLAUDE_COMMIT_MSG`
(**not** `COMMIT_EDITMSG` — git overwrites that on `git commit`; the `prepare-commit-msg` hook
prefills the editor from `CLAUDE_COMMIT_MSG`).
- **(c) Copy it:** `wl-copy < <that path>`.
- Tell me all three locations. **Never** run `git commit`.
(Skip this step when nothing new was staged this cycle.)
10. **Ping + schedule.** Give me a tight summary — deltas only, don't restate unchanged threads:
- 👍 fixed & staged this cycle: threads (with `path:line`);
- 👀 left for you: threads + one-line reason each;
- 👎 dismissed as not-an-issue: thread + the one-line why (call these out explicitly — a
dismissal is me trusting your judgement, so I should see every one);
- resolved on GitHub this cycle (if any);
- the commit message (printed above), then "commit & push when ready".
Refresh `lastSeenUpdatedAt` with one `gh pr view NUM --json updatedAt` call **after** any
needs-input questions are posted (reactions don't bump `updatedAt`, comments do — so your own
activity doesn't defeat the next cheap check), then save state
to `<git-dir>/pr-loop-state.json`. Then schedule:
- **normal cycle:** `ScheduleWakeup(delaySeconds=600, prompt="/pr-loop", reason="recheck PR
#<num> review comments")`;
- **idle cycle** (step 3 short-circuited): back off — `delaySeconds=1200`, I'm clearly away;
re-running `/pr-loop` checks immediately.
End the turn with a sign-off that states when you'll run again and how to stop, e.g.:
> ⏳ **Next check in ~10 min** (~20 when idle). To stop, press `Esc` while I'm idle between
> cycles (or close the session).
(When a stop condition in step 5 fired, skip both the wake-up and this sign-off — use the
**Finished** line from step 5 instead.)
## Guardrails
- **Never** `git commit`, `git push`, `gh pr merge`, `gh pr create`, `gh pr close`, or `gh pr edit`
— I do all of those. You stage, react, resolve, and draft; nothing more. The only comment you may
post is a needs-input decision question.
- Act only on the target PR. Don't touch unrelated files or other PRs.
- **Keep context lean:** no raw JSON dumps in replies, no re-listing threads that haven't changed,
idle cycles ≤2 lines. Every line you emit is re-read (uncached) on every later cycle.
- If anything is unclear or risky, leave it for me rather than guessing.
@@ -0,0 +1,69 @@
---
description: Review all changes on the current branch (diff vs its base) for bugs, security issues, optimizations, and readability/scannability, then apply the readability + safe improvements after you approve. Use right before opening a PR.
argument-hint: [base-branch]
disable-model-invocation: true
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git status:*), Bash(git merge-base:*), Bash(git symbolic-ref:*), Bash(git rev-parse:*), Bash(git show-ref:*), Bash(git branch:*), Read, Grep, Glob
---
# Review this branch
Context auto-collected for the current branch vs. its base:
!`b=$(git symbolic-ref --short -q refs/remotes/origin/HEAD 2>/dev/null | sed 's@^origin/@@'); [ -z "$b" ] && { git show-ref -q --verify refs/heads/main && b=main || b=master; }; m=$(git merge-base "$b" HEAD 2>/dev/null); echo "Current branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null)"; echo "Base branch: $b"; echo "Diff range: ${m:-?}..HEAD"; echo; echo "Commits:"; git --no-pager log --oneline "${m}..HEAD" 2>/dev/null; echo; echo "Files changed:"; git --no-pager diff --stat "${m}...HEAD" 2>/dev/null`
## What to do
1. **Resolve the base branch.** If a base branch was passed when invoking (it appears here:
**$ARGUMENTS** — empty if none), use that. Otherwise use the base detected above. The changes
to review are the diff `git merge-base <base> HEAD`..`HEAD`.
2. **Gather context.** Read the full diff for that range (`git diff <merge-base>...HEAD`) and
review **only the lines this branch changed** — not pre-existing code. Read surrounding code
with the Read tool when you need it to judge a finding. Also read the repo's `CLAUDE.md` (root,
and any in the directories the branch touched) and treat its guidance as a review lens.
3. **Find issues** across four lenses:
- 🐞 **Correctness / bugs** — logic errors, unhandled edge cases, swallowed/ignored errors,
race conditions, wrong/loose types, off-by-ones, and anything that violates the repo's
`CLAUDE.md`.
- 🔒 **Security** — injection (SQL / command / path traversal), missing input validation or
output encoding, authn/authz gaps, secrets or credentials committed / logged / echoed,
unsafe deserialization, SSRF, weak crypto or randomness, unsafe defaults, and sensitive data
leaked in logs or error messages.
- ⚡ **Optimizations** — redundant work, needless allocations/copies, N+1 patterns, a simpler
or standard-library equivalent.
- 📖 **Readability & scannability** — naming, structure, dead code, stale/misleading comments,
formatting, over-long functions, unclear control flow.
4. **Verify the bugs and security findings before reporting.** Double-check each 🐞 and 🔒
finding against the real code and keep only the ones you're confident are real and exploitable
/ will bite in practice. **Do not flag:**
- issues on lines the branch didn't change (pre-existing);
- theoretical vulnerabilities with no reachable exploit path in the changed code, or anything a
SAST / dependency scanner would own;
- anything a linter / type-checker / compiler / CI catches (imports, type errors, formatting) —
assume those run separately;
- pedantic nitpicks a senior engineer wouldn't raise, or "needs more tests/docs" unless the
repo's `CLAUDE.md` requires it;
- changes that are clearly intentional and part of the feature.
(This filter is for 🐞 bugs and 🔒 security. ⚡ optimizations and 📖 readability are the
deliberate polish pass — minor suggestions there are welcome, since the goal is PR-readiness.)
5. **Report**, grouped by the four lenses, most-severe-first, each finding one or two lines with
a `path:line` reference so it's scannable. If a category is clean, say so in one line. No walls
of text.
6. **Then stop and ask** whether to apply the fixes. On approval, apply **only** the readability
improvements and behavior-preserving optimizations. Anything that changes behavior, public API,
or semantics: list it separately and let me decide — do **not** apply it silently. Treat 🔒
security fixes the same way: propose them, but never apply them silently. After editing, re-run
the repo's formatter/linter if it has one.
7. **Point me at the relevant built-in follow-ups.** After reporting, list the Claude Code
commands worth running next as separate, deeper passes — tailored to what this review
surfaced, not a blanket dump. Only include the ones that actually fit:
- `/security-review` — a deeper, whole-branch security pass; suggest whenever the 🔒 lens
flagged something or the diff touches auth, crypto, input handling, secrets, or network I/O.
- `/code-review` (add `--fix` to apply, `--comment` for inline PR comments) — a second review
of the working-tree diff at a chosen effort level.
- `/simplify` — apply reuse / simplification / efficiency cleanups beyond the safe ones applied
here.
- `/verify` — run the app to confirm the change behaves, if this branch changed behavior.
- `/pr-description` — draft the PR text from this branch once the review is clean.
- `/review` — review the PR on GitHub after it's open.
8. **Never** run `git commit` or `git push` — I do that manually.
+517
View File
@@ -0,0 +1,517 @@
#!/usr/bin/env python3
"""Claude Code status line.
One line: vim mode | dir (repo-relative, hidden at the root) |
git(repo/branch +dirty +ahead/behind) | model (+effort) |
context% (+compact warn) | 5h usage (+reset eta) | cost (+burn rate) | disk. If the
rendered line would overflow the terminal, trailing (lowest-priority) segments are
dropped until it fits.
(Dormant, re-addable in main(): ram, cpu%, temp -- retired 2026-07-26 because the
status line only refreshes on message events, so system metrics sat visibly stale;
tmux's status bar owns those now -- plus velocity, cache hit %, api ratio, version,
style.)
Reads the status JSON from stdin (Claude Code statusLine command). Every segment
is wrapped defensively so the status line can never crash the UI -- on any error
a segment is simply omitted.
"""
import json
import os
import re
import shutil
import subprocess
import sys
import time
import unicodedata
# --- ANSI helpers -----------------------------------------------------------
RESET = "\033[0m"
DIM = "" # disabled: "\033[2m" blended the gray detail text into dark backgrounds
BOLD = "\033[1m"
C = {
"cyan": "\033[36m",
"green": "\033[32m",
"yellow": "\033[33m",
"red": "\033[31m",
"magenta": "\033[35m",
"blue": "\033[34m",
"gray": "\033[37m", # light gray (was "\033[90m" bright-black, too dark to read)
}
def color(text, name="", dim=False, bold=False):
pre = ""
if dim:
pre += DIM
if bold:
pre += BOLD
pre += C.get(name, "")
return f"{pre}{text}{RESET}" if pre else str(text)
def bucket(value, lo, hi, invert=False):
"""green/yellow/red by threshold. invert=True -> high is good."""
if invert:
return "green" if value >= hi else ("yellow" if value >= lo else "red")
return "green" if value < lo else ("yellow" if value < hi else "red")
DIV = color("", "gray", dim=True)
def join_line(segs):
return DIV.join(s for s in segs if s)
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
def vlen(text):
"""Visible width of a rendered string: ANSI stripped, emoji counted as 2."""
text = _ANSI_RE.sub("", text)
w = 0
for ch in text:
if ch in ("\u200d", "\ufe0f", "\ufe0e") or unicodedata.combining(ch):
continue # ZWJ / variation selectors / combining marks: zero width
o = ord(ch)
if o >= 0x1F000 or 0x2600 <= o <= 0x27BF or unicodedata.east_asian_width(ch) in ("W", "F"):
w += 2
else:
w += 1
return w
def term_cols():
"""Terminal width. Claude Code exports COLUMNS; 0 if unknown -> stay one line."""
try:
return shutil.get_terminal_size(fallback=(0, 0)).columns
except Exception:
return 0
# --- shared transcript usage ------------------------------------------------
def last_usage(data):
"""Most recent `message.usage` block from the transcript, or {}."""
try:
path = data.get("transcript_path")
if not path or not os.path.exists(path):
return {}
usage = {}
with open(path, "r", errors="ignore") as fh:
for line in fh:
line = line.strip()
if not line or '"usage"' not in line:
continue
try:
obj = json.loads(line)
except Exception:
continue
msg = obj.get("message")
if isinstance(msg, dict) and isinstance(msg.get("usage"), dict):
usage = msg["usage"]
return usage
except Exception:
return {}
# --- row 1: work ------------------------------------------------------------
def vim_segment(data):
"""Vim editor mode as a compact [I]/[N]/[V] (only present with editorMode: vim).
Pair with `"hideVimModeIndicator": true` in the statusLine settings block so
Claude's own built-in indicator doesn't duplicate this one.
"""
mode = (data.get("vim") or {}).get("mode")
if not mode:
return ""
m = mode.upper()
if m.startswith("INSERT"):
return color("[I]", "green", bold=True)
if m.startswith("VISUAL"):
return color("[V]", "magenta", bold=True)
if m.startswith("NORMAL"):
return color("[N]", "blue", bold=True)
return color(f"[{m[:1]}]", "gray")
def abbrev(path):
"""fish-style prompt_pwd: shrink every component but the last to one char,
keeping a leading dot (`.config` -> `.c`)."""
parts = path.split(os.sep)
out = []
for i, p in enumerate(parts):
if i == len(parts) - 1 or p in ("", "~"):
out.append(p)
elif p.startswith("."):
out.append(p[:2])
else:
out.append(p[:1])
return os.sep.join(out)
def dir_segment(cwd, top=None):
"""Path *inside* the repo -- git_segment already names the repo itself, so
repeating the full ~-path here was pure duplication. Empty at the repo root;
a fish-shortened ~-path when we're not in a repo at all."""
try:
rel = os.path.relpath(cwd, top) if top else None
if rel and not rel.startswith(os.pardir): # `..` -> cwd is outside `top` (symlinked in); use the ~-path
if rel == os.curdir:
return ""
shown = rel if len(rel) <= 28 else abbrev(rel)
else:
home = os.path.expanduser("~")
shown = cwd
if cwd == home:
shown = "~"
elif cwd.startswith(home + os.sep):
shown = "~" + cwd[len(home):]
shown = abbrev(shown)
return "📁 " + color(shown, "cyan")
except Exception:
return ""
def _git(cwd, *args):
return subprocess.run(
["git", "-C", cwd, *args], capture_output=True, text=True, timeout=1
)
def git_top(cwd):
"""Absolute path of the enclosing work tree, or None."""
try:
r = _git(cwd, "rev-parse", "--show-toplevel")
return r.stdout.strip() or None if r.returncode == 0 else None
except Exception:
return None
def git_segment(cwd, top):
try:
if not top:
return ""
repo = os.path.basename(top) or "repo"
branch = _git(cwd, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip() or "?"
dirty = bool(_git(cwd, "status", "--porcelain").stdout.strip())
# ahead/behind vs upstream (omitted when no upstream is configured)
ab = ""
rl = _git(cwd, "rev-list", "--left-right", "--count", "@{upstream}...HEAD")
if rl.returncode == 0 and rl.stdout.strip():
try:
behind, ahead = (int(x) for x in rl.stdout.split())
bits = []
if ahead:
bits.append(color(f"{ahead}", "cyan"))
if behind:
bits.append(color(f"{behind}", "yellow"))
if bits:
ab = " " + "".join(bits)
except Exception:
ab = ""
mark = color("*", "red") if dirty else ""
label = color(repo, "magenta", bold=True)
br = color(branch, "yellow" if dirty else "green")
return f"🌿 {label} {br}{mark}{ab}"
except Exception:
return ""
def model_segment(data):
name = (data.get("model") or {}).get("display_name")
if not name:
return ""
name = name.replace(" context)", ")") # "Opus 4.8 (1M context)" -> "Opus 4.8 (1M)"
out = "🤖 " + color(name, "blue")
level = (data.get("effort") or {}).get("level")
if level:
out += color(f" · {level}", "gray")
return out
def context_segment(data, usage):
try:
if not usage:
return ""
used = (
usage.get("input_tokens", 0)
+ usage.get("cache_creation_input_tokens", 0)
+ usage.get("cache_read_input_tokens", 0)
)
model_id = (data.get("model") or {}).get("id", "")
window = 1_000_000 if "1m" in model_id.lower() else 200_000
pct = used / window * 100 if window else 0
col = bucket(pct, 50, 80)
out = "📝 " + color(f"{pct:.0f}%", col) + color(f" ({used / 1000:.0f}k)", "gray", dim=True)
if pct >= 80:
out += color(" ⚠compact", "red", bold=True)
return out
except Exception:
return ""
def usage_segment(data):
"""5-hour rolling usage window: used % (+ time until it resets)."""
try:
five = (data.get("rate_limits") or {}).get("five_hour") or {}
pct = five.get("used_percentage")
if pct is None:
return ""
out = "📊 " + color(f"{pct:.0f}%", bucket(pct, 50, 80))
resets = five.get("resets_at")
if resets:
secs = int(resets) - int(time.time())
if secs > 0:
h, m = divmod(secs // 60, 60)
out += color(f" {h}h{m:02d}m" if h else f" {m}m", "gray")
return out
except Exception:
return ""
def cost_segment(data):
try:
cost = data.get("cost") or {}
usd = cost.get("total_cost_usd")
if usd is None:
return ""
out = "💰 " + color(f"${usd:.2f}", "yellow")
dur_ms = cost.get("total_duration_ms") or 0
if dur_ms > 30_000: # need a meaningful window before extrapolating
per_hr = usd / (dur_ms / 3_600_000)
out += color(f" ${per_hr:.2f}/h", "gray", dim=True)
return out
except Exception:
return ""
def velocity_segment(data):
try:
cost = data.get("cost") or {}
added = cost.get("total_lines_added", 0)
removed = cost.get("total_lines_removed", 0)
if not (added or removed):
return ""
out = "✏️ " + color(f"+{added}", "green") + color("/", "gray", dim=True) + color(f"-{removed}", "red")
dur_ms = cost.get("total_duration_ms") or 0
if dur_ms > 30_000:
per_min = added / (dur_ms / 60_000)
out += color(f" {per_min:.0f}/m", "gray", dim=True)
return out
except Exception:
return ""
def cache_segment(usage):
try:
if not usage:
return ""
read = usage.get("cache_read_input_tokens", 0)
total = (
usage.get("input_tokens", 0)
+ usage.get("cache_creation_input_tokens", 0)
+ read
)
if total <= 0:
return ""
pct = read / total * 100
return "♻️ " + color(f"{pct:.0f}%", bucket(pct, 50, 80, invert=True))
except Exception:
return ""
def api_segment(data):
"""Share of wall-clock time spent in API/inference (rough 'busy' ratio)."""
try:
cost = data.get("cost") or {}
wall = cost.get("total_duration_ms") or 0
api = cost.get("total_api_duration_ms") or 0
if wall <= 0 or api <= 0:
return ""
pct = min(api / wall * 100, 100)
return "⚙️ " + color(f"{pct:.0f}%", "blue")
except Exception:
return ""
# --- row 2: system ----------------------------------------------------------
def ram_segment():
try:
info = {}
with open("/proc/meminfo") as fh:
for line in fh:
k, _, v = line.partition(":")
info[k.strip()] = int(v.split()[0]) # kB
total = info.get("MemTotal", 0)
avail = info.get("MemAvailable", info.get("MemFree", 0))
if total <= 0:
return ""
used = total - avail
pct = used / total * 100
g = 1024 * 1024
return (
"🧠 " + color(f"{pct:.0f}%", bucket(pct, 70, 85))
+ color(f" {used / g:.1f}/{total / g:.0f}G", "gray", dim=True)
)
except Exception:
return ""
def disk_segment(cwd):
try:
st = os.statvfs(cwd)
total = st.f_blocks
if total <= 0:
return ""
used = total - st.f_bfree
pct = used / total * 100
return "💾 " + color(f"{pct:.0f}%", bucket(pct, 75, 90))
except Exception:
return ""
def _cpu_pct():
"""Live CPU% diffed against a cached /proc/stat snapshot (no sleeping)."""
try:
with open("/proc/stat") as fh:
parts = fh.readline().split()
if not parts or parts[0] != "cpu":
return None
vals = [int(x) for x in parts[1:]]
idle = vals[3] + (vals[4] if len(vals) > 4 else 0) # idle + iowait
total = sum(vals)
state = os.path.expanduser("~/.claude/cache/statusline.cpu")
prev = None
try:
with open(state) as fh:
pt, pi = fh.read().split()
prev = (int(pt), int(pi))
except Exception:
prev = None
try:
os.makedirs(os.path.dirname(state), exist_ok=True)
with open(state, "w") as fh:
fh.write(f"{total} {idle}")
except Exception:
pass
if not prev:
return None
dt = total - prev[0]
di = idle - prev[1]
if dt <= 0 or di < 0:
return None
return max(0.0, min(100.0, (1 - di / dt) * 100))
except Exception:
return None
def cpu_segment():
try:
load1 = os.getloadavg()[0]
cores = os.cpu_count() or 1
pct = _cpu_pct()
if pct is not None:
head = color(f"{pct:.0f}%", bucket(pct, 60, 85))
tail = color(f" {load1:.1f} {cores}c", "gray", dim=True)
else:
head = color(f"{load1:.2f}", bucket(load1 / cores, 0.7, 1.0))
tail = color(f" {cores}c", "gray", dim=True)
return "🖥️ " + head + tail
except Exception:
return ""
def temp_segment():
"""Hottest CPU-ish thermal zone in °C, falling back to the max zone."""
try:
base = "/sys/class/thermal"
if not os.path.isdir(base):
return ""
prefer = ("x86_pkg_temp", "coretemp", "cpu", "k10temp", "tctl")
chosen = None
fallback = None
for name in os.listdir(base):
if not name.startswith("thermal_zone"):
continue
d = os.path.join(base, name)
try:
with open(os.path.join(d, "temp")) as fh:
milli = int(fh.read().strip())
except Exception:
continue
ztype = ""
try:
with open(os.path.join(d, "type")) as fh:
ztype = fh.read().strip().lower()
except Exception:
pass
if fallback is None or milli > fallback:
fallback = milli
if any(p in ztype for p in prefer) and (chosen is None or milli > chosen):
chosen = milli
milli = chosen if chosen is not None else fallback
if milli is None:
return ""
c = milli / 1000.0
return "🌡️ " + color(f"{c:.0f}°C", bucket(c, 60, 80))
except Exception:
return ""
def version_segment(data):
v = data.get("version")
return color(f"v{v}", "gray", dim=True) if v else ""
def style_segment(data):
name = (data.get("output_style") or {}).get("name")
return color(f"🎨{name}", "gray", dim=True) if name else ""
def main():
try:
data = json.load(sys.stdin)
except Exception:
data = {}
cwd = (
(data.get("workspace") or {}).get("current_dir")
or data.get("cwd")
or os.getcwd()
)
usage = last_usage(data)
top = git_top(cwd)
# One row: work (left) then system (right), joined. If it would overflow the
# terminal, trailing (lowest-priority) segments are dropped so it stays one line.
# Dormant helpers kept above for easy re-add: ram_segment, cpu_segment,
# temp_segment (retired -- they only refresh on message events, so they sat
# stale; the tmux status bar owns system metrics now), velocity_segment,
# cache_segment, api_segment, version_segment, style_segment.
work = [s for s in (
vim_segment(data),
dir_segment(cwd, top),
git_segment(cwd, top),
model_segment(data),
context_segment(data, usage),
usage_segment(data),
cost_segment(data),
) if s]
system = [s for s in (
disk_segment(cwd),
) if s]
segs = work + system
cols = term_cols()
if cols: # trim from the right until it fits one line (cols=0 -> width unknown, keep all)
while len(segs) > 1 and vlen(join_line(segs)) > cols:
segs.pop()
sys.stdout.write(join_line(segs))
if __name__ == "__main__":
main()

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

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