Kieran's opinionated (and probably slightly dumb) nix config
1{ 2 lib, 3 pkgs, 4 config, 5 inputs, 6 ... 7}: 8{ 9 options.atelier.shell.enable = lib.mkEnableOption "Custom shell config"; 10 config = lib.mkIf config.atelier.shell.enable { 11 programs.oh-my-posh = { 12 enable = true; 13 enableZshIntegration = true; 14 settings = { 15 upgrade = { 16 notice = false; 17 interval = "2w"; 18 auto = false; 19 }; 20 version = 2; 21 final_space = true; 22 console_title_template = "{{ .Shell }} in {{ .Folder }}"; 23 blocks = [ 24 { 25 type = "prompt"; 26 alignment = "left"; 27 newline = true; 28 segments = [ 29 { 30 type = "session"; 31 background = "transparent"; 32 foreground = "yellow"; 33 template = "{{ if .SSHSession }}{{.HostName}} {{ end }}"; 34 } 35 { 36 type = "path"; 37 style = "plain"; 38 background = "transparent"; 39 foreground = "blue"; 40 template = "{{ .Path }} "; 41 properties = { 42 style = "full"; 43 }; 44 } 45 { 46 type = "git"; 47 style = "plain"; 48 foreground = "p:grey"; 49 background = "transparent"; 50 template = "{{if not .Detached}}{{ .HEAD }}{{else}}@{{ printf \"%.7s\" .Commit.Sha }}{{end}}{{ if .Staging.Changed }} ({{ .Staging.String }}){{ end }}{{ if .Working.Changed }}*{{ end }} <cyan>{{ if .BranchStatus }}{{ .BranchStatus }}{{ end }}</>"; 51 properties = { 52 branch_icon = ""; 53 branch_identical_icon = ""; 54 branch_gone_icon = ""; 55 branch_ahead_icon = ""; 56 branch_behind_icon = ""; 57 commit_icon = "@"; 58 fetch_status = true; 59 }; 60 } 61 ]; 62 } 63 { 64 type = "rprompt"; 65 overflow = "hidden"; 66 segments = [ 67 { 68 type = "executiontime"; 69 style = "plain"; 70 foreground = "yellow"; 71 background = "transparent"; 72 template = "{{ .FormattedMs }}"; 73 properties = { 74 threshold = 3000; 75 }; 76 } 77 { 78 type = "nix-shell"; 79 style = "plain"; 80 foreground = "red"; 81 background = "transparent"; 82 template = ''{{if ne .Type "unknown" }} {{ .Type }}{{ end }}''; 83 } 84 ]; 85 } 86 { 87 type = "prompt"; 88 alignment = "left"; 89 newline = true; 90 segments = [ 91 { 92 type = "text"; 93 style = "plain"; 94 foreground_templates = [ 95 "{{if gt .Code 0}}red{{end}}" 96 "{{if eq .Code 0}}magenta{{end}}" 97 ]; 98 background = "transparent"; 99 template = ""; 100 } 101 ]; 102 } 103 ]; 104 transient_prompt = { 105 foreground_templates = [ 106 "{{if gt .Code 0}}red{{end}}" 107 "{{if eq .Code 0}}magenta{{end}}" 108 ]; 109 background = "transparent"; 110 template = " "; 111 }; 112 secondary_prompt = { 113 foreground = "p:gray"; 114 background = "transparent"; 115 template = " "; 116 }; 117 palette = { 118 grey = "#6c6c6c"; 119 }; 120 }; 121 }; 122 123 programs.zsh = { 124 enable = true; 125 enableCompletion = true; 126 syntaxHighlighting.enable = true; 127 128 shellAliases = { 129 cat = "bat"; 130 ls = "eza"; 131 ll = "eza -l"; 132 la = "eza -la"; 133 gc = "git commit"; 134 gp = "git push"; 135 rr = "rm -Rf"; 136 ghrpc = "gh repo create -c"; 137 goops = "git commit --amend --no-edit && git push --force-with-lease"; 138 vi = "nvim"; 139 vim = "nvim"; 140 }; 141 initContent = '' 142 #ssh auto reconnect 143 assh() { 144 local host=$1 145 local port=$2 146 while true; do 147 ssh -p $port -o "BatchMode yes" $host || sleep 1 148 done 149 } 150 # hackatime summary 151 summary() { 152 local user_id=$1 153 curl -X 'GET' \ 154 "https://waka.hackclub.com/api/summary?user=''${user_id}&interval=month" \ 155 -H 'accept: application/json' \ 156 -H 'Authorization: Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608' | jq '. + { 157 "total_categories_sum": (.categories | map(.total) | add), 158 "total_categories_human_readable": ( 159 (.categories | map(.total) | add) as $total_seconds | 160 "\($total_seconds / 3600 | floor)h \(($total_seconds % 3600) / 60 | floor)m \($total_seconds % 60)s" 161 ), 162 "projectsKeys": ( 163 .projects | sort_by(-.total) | map(.key) 164 ) 165 }' 166 } 167 168 tangled() { 169 # Configuration variables - set these to your defaults 170 local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul" 171 local default_github_username="taciturnaxolotl" 172 local extracted_github_username="" 173 174 # Check if current directory is a git repository 175 if ! git rev-parse --is-inside-work-tree &>/dev/null; then 176 echo "Not a git repository" 177 return 1 178 fi 179 180 # Get the repository name from the current directory 181 local repo_name=$(basename "$(git rev-parse --show-toplevel)") 182 183 # Check if origin remote exists and points to ember 184 local origin_url=$(git remote get-url origin 2>/dev/null) 185 local origin_ember=false 186 187 if [[ -n "$origin_url" ]]; then 188 # Try to extract GitHub username if origin is a GitHub URL 189 if [[ "$origin_url" == *"github.com"* ]]; then 190 extracted_github_username=$(echo "$origin_url" | sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/') 191 # Override the default username with the extracted one 192 default_github_username=$extracted_github_username 193 fi 194 195 if [[ "$origin_url" == *"ember"* ]]; then 196 origin_ember=true 197 echo " Origin remote exists and points to ember" 198 else 199 echo " Origin remote exists but doesn't point to ember" 200 fi 201 else 202 echo " Origin remote doesn't exist" 203 fi 204 205 # Check if github remote exists 206 local github_exists=false 207 if git remote get-url github &>/dev/null; then 208 github_exists=true 209 echo " GitHub remote exists" 210 else 211 echo " GitHub remote doesn't exist" 212 fi 213 214 # Fix remotes if needed 215 if [[ "$origin_ember" = false || "$github_exists" = false ]]; then 216 echo "Setting up remotes..." 217 218 # Prompt for PLC identifier if needed 219 local plc_id="" 220 if [[ "$origin_ember" = false ]]; then 221 echo -n "Enter your PLC identifier [default: $default_plc_id]: " 222 read plc_input 223 plc_id=''${plc_input:-$default_plc_id} 224 fi 225 226 # Prompt for GitHub username with default from origin if available 227 local github_username="" 228 if [[ "$github_exists" = false ]]; then 229 echo -n "Enter your GitHub username [default: $default_github_username]: " 230 read github_input 231 github_username=''${github_input:-$default_github_username} 232 fi 233 234 # Set up origin remote if needed 235 if [[ "$origin_ember" = false && -n "$plc_id" ]]; then 236 if git remote get-url origin &>/dev/null; then 237 git remote remove origin 238 fi 239 git remote add origin "git@ember:''${plc_id}/''${repo_name}" 240 echo " Set up origin remote: git@ember:''${plc_id}/''${repo_name}" 241 fi 242 243 # Set up GitHub remote if needed 244 if [[ "$github_exists" = false && -n "$github_username" ]]; then 245 git remote add github "git@github.com:''${github_username}/''${repo_name}.git" 246 echo " Set up GitHub remote: git@github.com:''${github_username}/''${repo_name}.git" 247 fi 248 else 249 echo "Remotes are correctly configured" 250 fi 251 } 252 253 # Post AtProto status updates 254 now() { 255 local message="" 256 local prompt_message=true 257 local account1_name="" 258 local account2_name="" 259 local account1_jwt="" 260 local account2_jwt="" 261 262 # Load account information from agenix secrets 263 if [[ -f "/run/agenix/bluesky" ]]; then 264 source "/run/agenix/bluesky" 265 else 266 echo "Error: Bluesky credentials file not found at /run/agenix/bluesky" 267 return 1 268 fi 269 270 # Parse arguments 271 while [[ $# -gt 0 ]]; do 272 case "$1" in 273 -m|--message) 274 message="$2" 275 prompt_message=false 276 shift 2 277 ;; 278 *) 279 echo "Usage: now [-m|--message \"your message\"]" 280 return 1 281 ;; 282 esac 283 done 284 285 # Prompt for message if none provided 286 if [[ "$prompt_message" = true ]]; then 287 echo -n "$ACCOUNT1 is: " 288 read message 289 290 if [[ -z "$message" ]]; then 291 echo "No message provided. Aborting." 292 return 1 293 fi 294 fi 295 296 # Generate JWT for ACCOUNT1 297 local account1_response=$(curl -s -X POST \ 298 -H "Content-Type: application/json" \ 299 -d '{ 300 "identifier": "'$ACCOUNT1'", 301 "password": "'$ACCOUNT1_PASSWORD'" 302 }' \ 303 "https://bsky.social/xrpc/com.atproto.server.createSession") 304 305 account1_jwt=$(echo "$account1_response" | jq -r '.accessJwt') 306 307 if [[ -z "$account1_jwt" || "$account1_jwt" == "null" ]]; then 308 echo "Failed to authenticate account $ACCOUNT1" 309 echo "Response: $account1_response" 310 return 1 311 fi 312 313 # Generate JWT for ACCOUNT2 314 local account2_response=$(curl -s -X POST \ 315 -H "Content-Type: application/json" \ 316 -d '{ 317 "identifier": "'$ACCOUNT2'", 318 "password": "'$ACCOUNT2_PASSWORD'" 319 }' \ 320 "https://bsky.social/xrpc/com.atproto.server.createSession") 321 322 account2_jwt=$(echo "$account2_response" | jq -r '.accessJwt') 323 324 if [[ -z "$account2_jwt" || "$account2_jwt" == "null" ]]; then 325 echo "Failed to authenticate account $ACCOUNT2" 326 echo "Response: $account2_response" 327 return 1 328 fi 329 330 # Post to ACCOUNT1 as a.status.updates 331 local account1_post_response=$(curl -s -X POST \ 332 -H "Content-Type: application/json" \ 333 -H "Authorization: Bearer $account1_jwt" \ 334 -d '{ 335 "collection": "a.status.update", 336 "repo": "'$ACCOUNT1'", 337 "record": { 338 "$type": "a.status.update", 339 "text": "'"$message"'", 340 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 341 } 342 }' \ 343 "https://bsky.social/xrpc/com.atproto.repo.createRecord") 344 345 if [[ $(echo "$account1_post_response" | jq -r 'has("error")') == "true" ]]; then 346 echo "Error posting to $ACCOUNT1:" 347 echo "$account1_post_response" | jq 348 return 1 349 fi 350 351 # Post to ACCOUNT2 as normal post 352 local account2_post_response=$(curl -s -X POST \ 353 -H "Content-Type: application/json" \ 354 -H "Authorization: Bearer $account2_jwt" \ 355 -d '{ 356 "collection": "app.bsky.feed.post", 357 "repo": "'$ACCOUNT2'", 358 "record": { 359 "$type": "app.bsky.feed.post", 360 "text": "'"$message"'", 361 "createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'" 362 } 363 }' \ 364 "https://bsky.social/xrpc/com.atproto.repo.createRecord") 365 366 if [[ $(echo "$account2_post_response" | jq -r 'has("error")') == "true" ]]; then 367 echo "Error posting to $ACCOUNT2:" 368 echo "$account2_post_response" | jq 369 return 1 370 fi 371 372 echo "done" 373 } 374 375 ghostty_setup() { 376 local target="$1" 377 378 if [[ -z "$target" ]]; then 379 echo "Usage: ghostty_setup <user@host>" 380 return 1 381 fi 382 383 # Copy SSH key 384 echo "Copying SSH key to $target..." 385 ssh-copy-id "$target" || { echo "ssh-copy-id failed"; return 2; } 386 387 # Pipe infocmp output to tic on remote host 388 echo "Sending xterm-ghostty terminfo to $target..." 389 infocmp -x xterm-ghostty | ssh "$target" 'tic -x -' || { echo "Terminfo transfer failed"; return 3; } 390 391 echo "Done." 392 } 393 394 zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' 395 zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" 396 zstyle ':completion:*' menu no 397 zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' 398 zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' 399 400 eval "$(terminal-wakatime init)" 401 ''; 402 history = { 403 size = 10000; 404 path = "${config.xdg.dataHome}/zsh/history"; 405 ignoreDups = true; 406 ignoreAllDups = true; 407 ignoreSpace = true; 408 expireDuplicatesFirst = true; 409 share = true; 410 extended = true; 411 append = true; 412 }; 413 414 oh-my-zsh = { 415 enable = true; 416 plugins = [ 417 "git" 418 "sudo" 419 "docker" 420 "git" 421 "command-not-found" 422 "colored-man-pages" 423 ]; 424 }; 425 426 plugins = [ 427 { 428 # will source zsh-autosuggestions.plugin.zsh 429 name = "zsh-autosuggestions"; 430 src = pkgs.fetchFromGitHub { 431 owner = "zsh-users"; 432 repo = "zsh-autosuggestions"; 433 rev = "v0.7.0"; 434 sha256 = "sha256-KLUYpUu4DHRumQZ3w59m9aTW6TBKMCXl2UcKi4uMd7w="; 435 }; 436 } 437 { 438 # will source zsh-sytax-highlighting 439 name = "zsh-sytax-highlighting"; 440 src = pkgs.fetchFromGitHub { 441 owner = "zsh-users"; 442 repo = "zsh-syntax-highlighting"; 443 rev = "0.8.0"; 444 sha256 = "sha256-iJdWopZwHpSyYl5/FQXEW7gl/SrKaYDEtTH9cGP7iPo="; 445 }; 446 } 447 { 448 # fzf tab completion 449 name = "fzf-tab"; 450 src = pkgs.fetchFromGitHub { 451 owner = "aloxaf"; 452 repo = "fzf-tab"; 453 rev = "v1.1.2"; 454 sha256 = "sha256-Qv8zAiMtrr67CbLRrFjGaPzFZcOiMVEFLg1Z+N6VMhg="; 455 }; 456 } 457 ]; 458 }; 459 460 programs.zoxide = { 461 enable = true; 462 enableZshIntegration = true; 463 }; 464 programs.fzf = { 465 enable = true; 466 enableZshIntegration = true; 467 colors = { 468 bg = lib.mkForce ""; 469 }; 470 }; 471 programs.atuin = { 472 enable = true; 473 settings = { 474 auto_sync = true; 475 sync_frequency = "5m"; 476 sync_address = "https://api.atuin.sh"; 477 search_mode = "fuzzy"; 478 update_check = false; 479 style = "auto"; 480 sync.records = true; 481 dotfiles.enabled = false; 482 }; 483 }; 484 programs.yazi = { 485 enable = true; 486 enableZshIntegration = true; 487 }; 488 489 home.packages = with pkgs; [ 490 pkgs.unstable.wakatime-cli 491 inputs.terminal-wakatime.packages.${pkgs.system}.default 492 unzip 493 dog 494 dust 495 wget 496 curl 497 jq 498 fd 499 eza 500 bat 501 ripgrep 502 ripgrep-all 503 neofetch 504 glow 505 ]; 506 507 atelier.shell.git.enable = lib.mkDefault true; 508 }; 509}