@jaspermayone.com's dotfiles
at main 701 lines 21 kB view raw
1# Shell configuration 2{ 3 config, 4 lib, 5 pkgs, 6 hostname, 7 inputs, 8 ... 9}: 10 11let 12 # Strings CLI for uploading to pastebin 13 strings-cli = pkgs.writeShellScriptBin "strings" '' 14 #!/usr/bin/env bash 15 # strings - CLI for strings pastebin 16 # Usage: strings <file> or cat file | strings 17 18 set -e 19 20 STRINGS_HOST="''${STRINGS_HOST:-https://str.hogwarts.dev}" 21 STRINGS_USER="''${STRINGS_USER:-}" 22 STRINGS_PASS="''${STRINGS_PASS:-}" 23 24 # Try to load credentials from file if not set 25 if [[ -f "$HOME/.config/strings/credentials" ]]; then 26 source "$HOME/.config/strings/credentials" 27 fi 28 29 if [ -z "$STRINGS_USER" ] || [ -z "$STRINGS_PASS" ]; then 30 echo "Error: STRINGS_USER and STRINGS_PASS environment variables must be set" >&2 31 echo "Or create ~/.config/strings/credentials with:" >&2 32 echo " STRINGS_USER=youruser" >&2 33 echo " STRINGS_PASS=yourpass" >&2 34 exit 1 35 fi 36 37 # Determine filename and content 38 FILENAME="" 39 SLUG="" 40 41 while [[ $# -gt 0 ]]; do 42 case $1 in 43 -s|--slug) 44 SLUG="$2" 45 shift 2 46 ;; 47 -h|--host) 48 STRINGS_HOST="$2" 49 shift 2 50 ;; 51 *) 52 if [ -z "$FILENAME" ]; then 53 FILENAME="$1" 54 fi 55 shift 56 ;; 57 esac 58 done 59 60 if [ -n "$FILENAME" ]; then 61 if [ ! -f "$FILENAME" ]; then 62 echo "Error: File not found: $FILENAME" >&2 63 exit 1 64 fi 65 BASENAME=$(basename "$FILENAME") 66 CONTENT=$(cat "$FILENAME") 67 else 68 CONTENT=$(cat) 69 BASENAME="" 70 fi 71 72 # Build headers 73 HEADERS=(-H "Content-Type: text/plain") 74 [ -n "$BASENAME" ] && HEADERS+=(-H "X-Filename: $BASENAME") 75 [ -n "$SLUG" ] && HEADERS+=(-H "X-Slug: $SLUG") 76 77 # Make request 78 RESPONSE=$(${pkgs.curl}/bin/curl -s -X POST "$STRINGS_HOST/api/paste" \ 79 -u "$STRINGS_USER:$STRINGS_PASS" \ 80 "''${HEADERS[@]}" \ 81 --data-binary "$CONTENT") 82 83 # Parse response 84 URL=$(echo "$RESPONSE" | ${pkgs.gnugrep}/bin/grep -o '"url":"[^"]*"' | cut -d'"' -f4) 85 86 if [ -n "$URL" ]; then 87 echo "$URL" 88 89 # Copy to clipboard if available 90 if command -v pbcopy &> /dev/null; then 91 echo -n "$URL" | pbcopy 92 elif command -v xclip &> /dev/null; then 93 echo -n "$URL" | xclip -selection clipboard 94 elif command -v wl-copy &> /dev/null; then 95 echo -n "$URL" | wl-copy 96 fi 97 else 98 echo "Error: $RESPONSE" >&2 99 exit 1 100 fi 101 ''; 102 103 # Tangled setup script for configuring git remotes 104 tangled-setup = pkgs.writeShellScriptBin "tangled-setup" '' 105 # Configuration 106 default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul" 107 default_github_username="jaspermayone" 108 default_knot_host="knot.jaspermayone.com" 109 110 # Verify git repository 111 if ! ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then 112 ${pkgs.gum}/bin/gum style --foreground 196 "Not a git repository" 113 exit 1 114 fi 115 116 repo_name=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)") 117 ${pkgs.gum}/bin/gum style --bold --foreground 212 "Configuring tangled remotes for: $repo_name" 118 echo 119 120 # Check current remotes 121 origin_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null) 122 github_url=$(${pkgs.git}/bin/git remote get-url github 2>/dev/null) 123 origin_is_knot=false 124 github_username="$default_github_username" 125 126 # Extract GitHub username from existing origin if it's GitHub 127 if [[ "$origin_url" == *"github.com"* ]]; then 128 github_username=$(echo "$origin_url" | ${pkgs.gnused}/bin/sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/') 129 fi 130 131 # Check if origin points to knot 132 if [[ "$origin_url" == *"$default_knot_host"* ]] || [[ "$origin_url" == *"tangled"* ]]; then 133 origin_is_knot=true 134 ${pkgs.gum}/bin/gum style --foreground 35 " Origin knot ($origin_url)" 135 elif [[ -n "$origin_url" ]]; then 136 ${pkgs.gum}/bin/gum style --foreground 214 "! Origin $origin_url (not knot)" 137 else 138 ${pkgs.gum}/bin/gum style --foreground 214 "! Origin not configured" 139 fi 140 141 # Check github remote 142 if [[ -n "$github_url" ]]; then 143 ${pkgs.gum}/bin/gum style --foreground 35 " GitHub $github_url" 144 else 145 ${pkgs.gum}/bin/gum style --foreground 214 "! GitHub remote not configured" 146 fi 147 148 echo 149 150 # Configure origin remote if needed 151 if [[ "$origin_is_knot" = false ]]; then 152 should_migrate=true 153 if [[ -n "$origin_url" ]]; then 154 ${pkgs.gum}/bin/gum confirm "Migrate origin from $origin_url to knot?" || should_migrate=false 155 fi 156 157 if [[ "$should_migrate" = true ]]; then 158 plc_id=$(${pkgs.gum}/bin/gum input --placeholder "$default_plc_id" --prompt "PLC ID: " --value "$default_plc_id") 159 plc_id=''${plc_id:-$default_plc_id} 160 161 if ${pkgs.git}/bin/git remote get-url origin &>/dev/null; then 162 ${pkgs.git}/bin/git remote remove origin 163 fi 164 ${pkgs.git}/bin/git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}" 165 ${pkgs.gum}/bin/gum style --foreground 35 " Configured origin git@$default_knot_host:''${plc_id}/''${repo_name}" 166 fi 167 fi 168 169 # Configure github remote if needed 170 if [[ -z "$github_url" ]]; then 171 username=$(${pkgs.gum}/bin/gum input --placeholder "$github_username" --prompt "GitHub username: " --value "$github_username") 172 username=''${username:-$github_username} 173 174 ${pkgs.git}/bin/git remote add github "git@github.com:''${username}/''${repo_name}.git" 175 ${pkgs.gum}/bin/gum style --foreground 35 " Configured github git@github.com:''${username}/''${repo_name}.git" 176 fi 177 178 echo 179 180 # Configure default push remote 181 current_remote=$(${pkgs.git}/bin/git config --get branch.main.remote 2>/dev/null) 182 if [[ -z "$current_remote" ]]; then 183 if ${pkgs.gum}/bin/gum confirm "Set origin (knot) as default push remote?"; then 184 ${pkgs.git}/bin/git config branch.main.remote origin 185 ${pkgs.gum}/bin/gum style --foreground 35 " Default push remote origin" 186 fi 187 elif [[ "$current_remote" != "origin" ]]; then 188 ${pkgs.gum}/bin/gum style --foreground 117 "Current default: $current_remote" 189 if ${pkgs.gum}/bin/gum confirm "Change default push remote to origin (knot)?"; then 190 ${pkgs.git}/bin/git config branch.main.remote origin 191 ${pkgs.gum}/bin/gum style --foreground 35 " Default push remote origin" 192 fi 193 else 194 ${pkgs.gum}/bin/gum style --foreground 35 " Default push remote is origin" 195 fi 196 ''; 197 198in 199{ 200 # Starship prompt 201 programs.starship = { 202 enable = true; 203 enableZshIntegration = true; 204 settings = { 205 format = "$username$hostname$localip$directory$git_branch$git_commit$git_state$git_metrics$git_status$nix_shell$mise$bun$nodejs$python$ruby$rust$java$swift$direnv$shell$cmd_duration$line_break$character"; 206 207 # Hostname - always visible 208 hostname = { 209 ssh_only = false; 210 format = "[$hostname]($style) "; 211 style = "dimmed white"; 212 ssh_symbol = " "; 213 }; 214 215 # Local IP - show on SSH 216 localip = { 217 ssh_only = true; 218 format = "[@$localipv4]($style) "; 219 style = "dimmed white"; 220 disabled = false; 221 }; 222 223 # Directory 224 directory = { 225 style = "cyan"; 226 truncation_length = 3; 227 truncation_symbol = "/"; 228 truncate_to_repo = true; 229 read_only = " 󰌾"; 230 }; 231 232 # Git branch 233 git_branch = { 234 format = "[$symbol$branch(:$remote_branch)]($style) "; 235 symbol = " "; 236 style = "purple"; 237 }; 238 239 # Git commit - show hash when detached 240 git_commit = { 241 format = "[$hash$tag]($style) "; 242 style = "dimmed white"; 243 only_detached = true; 244 tag_disabled = false; 245 tag_symbol = " "; 246 }; 247 248 # Git state - show rebase/merge/etc 249 git_state = { 250 format = "[$state( $progress_current/$progress_total)]($style) "; 251 style = "yellow"; 252 }; 253 254 # Git metrics - show +/- lines 255 git_metrics = { 256 format = "([+$added]($added_style))([-$deleted]($deleted_style) )"; 257 added_style = "green"; 258 deleted_style = "red"; 259 disabled = false; 260 }; 261 262 # Git status - detailed symbols 263 git_status = { 264 format = "([$all_status$ahead_behind]($style) )"; 265 style = "dimmed red"; 266 conflicted = "="; 267 ahead = "$count"; 268 behind = "$count"; 269 diverged = "$ahead_count$behind_count"; 270 up_to_date = ""; 271 untracked = "?$count"; 272 stashed = "󰏗"; 273 modified = "!$count"; 274 staged = "+$count"; 275 renamed = "»$count"; 276 deleted = "$count"; 277 }; 278 279 # Bun 280 bun = { 281 format = "[$symbol($version)]($style) "; 282 symbol = " "; 283 style = "dimmed white"; 284 }; 285 286 # Direnv 287 direnv = { 288 format = "[$symbol$loaded/$allowed]($style) "; 289 symbol = "direnv "; 290 style = "dimmed white"; 291 disabled = false; 292 }; 293 294 # Command duration 295 cmd_duration = { 296 min_time = 2000; 297 format = "[$duration]($style) "; 298 style = "dimmed yellow"; 299 show_notifications = false; 300 }; 301 302 # Character 303 character = { 304 success_symbol = "[](cyan)"; 305 error_symbol = "[](red)"; 306 }; 307 308 # Username - show when root or SSH 309 username = { 310 style_root = "bold red"; 311 style_user = "dimmed white"; 312 format = "[$user]($style)@"; 313 show_always = false; 314 disabled = false; 315 }; 316 317 # Nix shell 318 nix_shell = { 319 format = "[$symbol$state( \\($name\\))]($style) "; 320 symbol = " "; 321 style = "dimmed blue"; 322 impure_msg = "impure"; 323 pure_msg = "pure"; 324 disabled = false; 325 heuristic = true; 326 }; 327 328 # Mise (version manager) 329 mise = { 330 format = "[$symbol$health]($style) "; 331 symbol = "mise "; 332 style = "dimmed white"; 333 disabled = false; 334 }; 335 336 # Node.js 337 nodejs = { 338 format = "[$symbol($version)]($style) "; 339 symbol = " "; 340 style = "dimmed green"; 341 not_capable_style = "red"; 342 }; 343 344 # Python 345 python = { 346 format = "[$symbol$pyenv_prefix($version )(\\($virtualenv\\))]($style) "; 347 symbol = " "; 348 style = "dimmed yellow"; 349 pyenv_version_name = false; 350 }; 351 352 # Ruby 353 ruby = { 354 format = "[$symbol($version)]($style) "; 355 symbol = " "; 356 style = "dimmed red"; 357 }; 358 359 # Rust 360 rust = { 361 format = "[$symbol($version)]($style) "; 362 symbol = "󱘗 "; 363 style = "dimmed red"; 364 }; 365 366 # Java 367 java = { 368 format = "[$symbol($version)]($style) "; 369 symbol = " "; 370 style = "dimmed red"; 371 }; 372 373 # Swift 374 swift = { 375 format = "[$symbol($version)]($style) "; 376 symbol = " "; 377 style = "dimmed white"; 378 }; 379 380 # Shell indicator 381 shell = { 382 format = "[$indicator]($style) "; 383 style = "white bold"; 384 zsh_indicator = ""; 385 bash_indicator = "bsh"; 386 fish_indicator = "fish"; 387 disabled = true; # enable if you switch shells often 388 }; 389 }; 390 }; 391 392 programs.zsh = { 393 enable = true; 394 enableCompletion = true; 395 autosuggestion.enable = true; 396 syntaxHighlighting.enable = true; 397 398 shellAliases = { 399 # Navigation 400 ll = "eza -l"; 401 la = "eza -la"; 402 l = "eza"; 403 ls = "eza"; 404 ".." = "cd .."; 405 "..." = "cd ../.."; 406 407 # Safety 408 rm = "rm -i"; 409 cp = "cp -i"; 410 mv = "mv -i"; 411 rr = "rm -Rf"; 412 413 # Enhanced commands 414 cd = "z"; 415 cat = "bat --paging=never"; 416 417 # Quick commands 418 e = "exit"; 419 c = "clear"; 420 421 # Directory shortcuts 422 dev = "z ~/dev"; 423 proj = "z ~/dev/projects"; 424 scripts = "z ~/dev/scripts"; 425 426 # Git shortcuts 427 g = "git"; 428 gs = "git status"; 429 gd = "git diff"; 430 ga = "git add"; 431 gc = "git commit"; 432 gp = "git push"; 433 gl = "git pull"; 434 s = "git status"; 435 push = "git push"; 436 pull = "git pull"; 437 goops = "git commit --amend --no-edit && git push --force-with-lease"; 438 439 # Python 440 pip = "pip3"; 441 python = "python3"; 442 443 cl = "claude --allow-dangerously-skip-permissions"; 444 clo = "ollama launch claude"; 445 446 # Project shortcuts 447 dns = "z dev/dns && source .env && source .venv/bin/activate"; 448 449 # Zsh config 450 zshedit = "code ~/.zshrc"; 451 zshreload = "source ~/.zshrc"; 452 453 # GPG key sync 454 keysync = "gpg --keyserver pgp.mit.edu --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --keyserver keyserver.ubuntu.com --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --keyserver keys.openpgp.org --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --export me@jaspermayone.com | curl -T - https://keys.openpgp.org"; 455 gpgend = "gpg --keyserver hkps://keys.openpgp.org --send-keys 14D0D45A1DADAAFA"; 456 457 path = "echo -e \${PATH//:/\\n}"; 458 459 # Vim 460 vi = "vim"; 461 462 afk = "/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend"; 463 reload = "exec \${SHELL} -l"; 464 }; 465 466 initContent = '' 467 # ============================================================================ 468 # HOMEBREW 469 # ============================================================================ 470 if [[ -f /opt/homebrew/bin/brew ]]; then 471 eval "$(/opt/homebrew/bin/brew shellenv)" 472 fi 473 474 # ============================================================================ 475 # ZINIT PLUGIN MANAGER 476 # ============================================================================ 477 if [[ ! -f $HOME/.local/share/zinit/zinit.git/zinit.zsh ]]; then 478 print -P "%F{33} %F{220}Installing %F{33}ZDHARMA-CONTINUUM%F{220} Initiative Plugin Manager (%F{33}zdharma-continuum/zinit%F{220})%f" 479 command mkdir -p "$HOME/.local/share/zinit" && command chmod g-rwX "$HOME/.local/share/zinit" 480 command git clone https://github.com/zdharma-continuum/zinit "$HOME/.local/share/zinit/zinit.git" && \ 481 print -P "%F{33} %F{34}Installation successful.%f%b" || \ 482 print -P "%F{160} The clone has failed.%f%b" 483 fi 484 485 source "$HOME/.local/share/zinit/zinit.git/zinit.zsh" 486 autoload -Uz _zinit 487 (( ''${+_comps} )) && _comps[zinit]=_zinit 488 489 # ============================================================================ 490 # ZINIT PLUGINS 491 # ============================================================================ 492 zinit light zsh-users/zsh-completions 493 zinit light zsh-users/zsh-autosuggestions 494 495 # Defer fzf-tab 496 zinit ice wait"0a" lucid 497 zinit light Aloxaf/fzf-tab 498 499 # Syntax highlighting loads in background 500 zinit ice wait"1" lucid 501 zinit light zsh-users/zsh-syntax-highlighting 502 503 # ============================================================================ 504 # OH-MY-ZSH SNIPPETS (deferred) 505 # ============================================================================ 506 zinit ice wait"0b" lucid 507 zinit snippet OMZP::git-commit 508 509 zinit ice wait"0b" lucid 510 zinit snippet OMZP::sudo 511 512 zinit ice wait"0b" lucid 513 zinit snippet OMZP::command-not-found 514 515 zinit ice wait"0b" lucid 516 zinit snippet OMZP::iterm2 517 518 # ============================================================================ 519 # COMPLETIONS 520 # ============================================================================ 521 autoload -Uz compinit 522 if [[ -n ''${HOME}/.zcompdump(#qN.mh+24) ]]; then 523 compinit 524 else 525 compinit -C 526 fi 527 zinit cdreplay -q 528 529 # ============================================================================ 530 # KEY BINDINGS 531 # ============================================================================ 532 bindkey '^f' autosuggest-accept 533 bindkey '^p' history-search-backward 534 bindkey '^n' history-search-forward 535 536 # ============================================================================ 537 # HISTORY CONFIGURATION 538 # ============================================================================ 539 HISTFILE=~/.zsh_history 540 HISTSIZE=999999999999 541 SAVEHIST=999999999999 542 HISTDUP=erase 543 setopt appendhistory 544 setopt extendedhistory 545 setopt hist_ignore_space 546 setopt hist_ignore_all_dups 547 setopt hist_save_no_dups 548 setopt hist_ignore_dups 549 setopt hist_find_no_dups 550 551 # ============================================================================ 552 # COMPLETION STYLING 553 # ============================================================================ 554 zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' 555 zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}" 556 zstyle ':completion:*' menu no 557 zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls --color $realpath' 558 zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' 559 560 # ============================================================================ 561 # PATH EXPORTS 562 # ============================================================================ 563 # Remus-specific: Run path_helper to setup PATH 564 if [[ $(hostname -s) == "remus" ]]; then 565 eval "$(/usr/libexec/path_helper -s)" 566 fi 567 568 # Note: flyctl, bun, pnpm are now managed by nix 569 export PATH="$HOME/bin:$PATH" 570 export PATH="$HOME/.local/bin:$PATH" 571 export PATH="/Users/jsp/.lmstudio/bin:$PATH" 572 export PATH="/Users/jsp/.codeium/windsurf/bin:$PATH" 573 export PATH="/opt/homebrew/opt/mysql@8.0/bin:$PATH" 574 export PATH="/Users/jsp/.antigravity/antigravity/bin:$PATH" 575 export PATH="$HOME/go/bin:$PATH" 576 export PATH="$HOME/toolchains/gcc-arm-none-eabi-10.3-2021.10/bin:$PATH" 577 578 # ============================================================================ 579 # ENVIRONMENT VARIABLES 580 # ============================================================================ 581 export PICO_SDK_PATH="$HOME/dev/pico-sdk" 582 export COMPOSE_BAKE=true 583 export VISUAL="code --wait" 584 export EDITOR="code --wait" 585 export LDFLAGS="-L/opt/homebrew/opt/postgresql@17/lib" 586 export CPPFLAGS="-I/opt/homebrew/opt/postgresql@17/include" 587 export ENABLE_BACKGROUND_TASKS=1 588 589 # Fix GPG issues with Homebrew install 590 export GPG_TTY=$(tty) 591 592 # Claude Code GitHub token (for MCP server) 593 if [[ -f "$HOME/.config/claude/github-token" ]]; then 594 export GITHUB_PERSONAL_ACCESS_TOKEN=$(cat "$HOME/.config/claude/github-token") 595 fi 596 597 # ============================================================================ 598 # SHELL INTEGRATIONS 599 # ============================================================================ 600 # Note: zoxide, fzf, atuin are initialized by home-manager programs.* 601 602 # Mise activation 603 eval "$(mise activate zsh)" 604 605 # Wut activation (workspace manager) 606 if command -v wut &> /dev/null; then 607 eval "$(wut init)" 608 fi 609 ''; 610 }; 611 612 # Common CLI tools 613 home.packages = with pkgs; [ 614 # Custom scripts 615 tangled-setup 616 strings-cli 617 618 # File management 619 tree 620 fd 621 ripgrep 622 bat 623 eza 624 unzip 625 626 # System monitoring 627 htop 628 btop 629 630 # Networking 631 curl 632 wget 633 httpie 634 635 # JSON/YAML 636 jq 637 yq 638 639 # Misc 640 fzf 641 tmux 642 watch 643 gum # Required for tangled-setup script 644 645 # Encryption 646 age # Modern encryption tool 647 648 # Dev tools 649 mise # Version manager (formerly rtx) 650 flyctl # Fly.io CLI 651 unstable.bun # JavaScript runtime 652 nodePackages.pnpm # Package manager 653 zmx-binary # Session persistence for terminal processes 654 655 # Deployment 656 inputs.deploy-rs.packages.${pkgs.stdenv.hostPlatform.system}.default 657 ]; 658 659 # Fuzzy finder integration 660 programs.fzf = { 661 enable = true; 662 enableZshIntegration = true; 663 colors = { 664 bg = lib.mkForce ""; 665 }; 666 }; 667 668 # Better cat 669 programs.bat = { 670 enable = true; 671 config = { 672 theme = "TwoDark"; 673 }; 674 }; 675 676 # Zoxide (better cd) 677 programs.zoxide = { 678 enable = true; 679 enableZshIntegration = true; 680 }; 681 682 # Atuin (shell history) with self-hosted sync server 683 programs.atuin = { 684 enable = true; 685 enableZshIntegration = true; 686 flags = [ 687 "--disable-up-arrow" 688 ]; 689 settings = { 690 auto_sync = true; 691 sync_frequency = "5m"; 692 sync_address = "https://atuin.hogwarts.dev"; 693 key_path = "~/.local/share/atuin/key"; 694 search_mode = "fuzzy"; 695 update_check = false; 696 style = "auto"; 697 inline_height = 30; 698 }; 699 }; 700 701}