Skip to content

πŸ‘€ ZSHAND User Configuration GuideΒΆ

πŸ“‹ This guide covers how to configure your shell, where files go, how overrides work, and how to extend the framework with your own code.

🧠 Philosophy

The framework is opinionated by default but fully overridable. You should never need to edit framework files β€” everything is customizable from your config directory.


πŸ“ Config DirectoryΒΆ

All user configuration lives under a single XDG-compliant directory:

~/.config/zshand/                   ($ZSHAND_CONFIG_DIR)

Override the location by setting ZSHAND_CONFIG_DIR in your environment before the shell starts (e.g., in ~/.zshenv).

πŸ“‚ Directory StructureΒΆ

~/.config/zshand/
β”œβ”€β”€ 🌍 env.zsh           Environment variables (auto-exported)
β”œβ”€β”€ πŸ”’ secrets.zsh       API keys and tokens (chmod 600)
β”œβ”€β”€ πŸ“ aliases.zsh       Custom aliases (loaded after framework)
β”œβ”€β”€ πŸ›€οΈ path.txt          Additional PATH entries (one per line)
β”œβ”€β”€ βš™οΈ config.toml       Framework behavior settings
β”œβ”€β”€ πŸ“Ÿ bin/              Personal scripts (added to $PATH)
β”œβ”€β”€ πŸ› οΈ functions/        Custom zsh functions
β”œβ”€β”€ ⌨️ widgets/          Custom ZLE widgets
β”œβ”€β”€ πŸͺ¨ hooks/            Custom integration hooks
β”œβ”€β”€ βš™οΈ rc.d/             Core config overrides (numbered like core/)
β”œβ”€β”€ πŸš€ init.d/           Pre-initialization scripts
β”œβ”€β”€ 🏁 post.d/           Post-initialization scripts
└── πŸ”Œ plugins/          Plugin configuration (e.g., P10k theme)

First-time setup

Run zsh setup.zsh to create this directory structure with helpful template files. You can also copy from samples/user-config/.


πŸ“Š Load OrderΒΆ

Understanding when each file loads helps you decide where to put things:

β”Œβ”€ Shell starts ─────────────────────────────────────────────┐
β”‚                                                             β”‚
β”‚  1. πŸš€ init.d/*.zsh        Early overrides (before core)   β”‚
β”‚  2. 🌍 env.zsh             Environment variables            β”‚
β”‚  3. πŸ”’ secrets.zsh         API keys (auto-exported)         β”‚
β”‚  4. βš™οΈ config.toml         Framework behavior flags         β”‚
β”‚  5. πŸ›€οΈ path.txt + bin/     PATH configuration               β”‚
β”‚  6. βš™οΈ rc.d/*.zsh          Core overrides (merged with core)β”‚
β”‚  7. βš™οΈ Framework core      02_vars β†’ 04_path β†’ ... β†’ 90    β”‚
β”‚  8. πŸͺ¨ Framework hooks     Tool integrations                β”‚
β”‚  9. πŸ› οΈ functions/          Your custom functions            β”‚
β”‚ 10. ⌨️ widgets/            Your custom widgets              β”‚
β”‚ 11. πŸͺ¨ hooks/              Your custom hooks                β”‚
β”‚ 12. πŸ“ aliases.zsh         Your aliases (can override)      β”‚
β”‚ 13. 🏁 post.d/*.zsh        Final tweaks (after everything)  β”‚
β”‚                                                             β”‚
└─ Shell ready β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🌍 Environment Variables (env.zsh)¢

Machine-specific environment variables. This file is sourced with set -a (auto-export), so every variable is automatically exported.

# env.zsh β€” Machine-specific environment variables

EDITOR="nvim"
BROWSER="firefox"
MACHINE_ID="workstation"

# Framework controls
ZSHAND_DEBUG=0
ZSHAND_LAZY_LOAD=1
ZSHAND_AUTO_FULL=1

# Tool-specific
DOCKER_HOST="unix:///run/user/1000/docker.sock"

πŸ’¬ The framework also writes ZSHAND="/path/to/zshand" into this file during setup. Don't remove it.


πŸ”’ Secrets (secrets.zsh)ΒΆ

API keys, tokens, and sensitive credentials. Automatically secured with chmod 600 during setup. Never commit this file.

# secrets.zsh β€” Sensitive credentials (chmod 600)

GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
OPENAI_API_KEY="sk-xxxxxxxxxxxx"
AWS_ACCESS_KEY_ID="AKIAXXXXXXXX"
AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxx"
CLOUDFLARE_API_TOKEN="xxxxxxxxxxxxxxxx"

Security

This file is protected by chmod 600 (owner-read-only). The framework re-enforces permissions on every startup via core/06_engine.zsh. Gitleaks scans also check that secrets never leak into the repo.


πŸ“ Aliases (aliases.zsh)ΒΆ

Custom aliases loaded after framework aliases, so they can override:

# aliases.zsh β€” Custom aliases

alias ll='eza -lahF --icons'
alias gst='git status'
alias dc='docker compose'
alias k='kubectl'
alias tf='terraform'

# Override a framework alias
alias ls='eza --icons --group-directories-first'

πŸ›€οΈ PATH (path.txt)ΒΆ

Additional directories to prepend to $PATH, one per line. Lines starting with # are ignored.

# path.txt β€” Additional PATH entries

/opt/custom/bin
~/.cargo/bin
~/.npm-global/bin
~/.local/share/mise/shims

Your bin/ directory is also automatically added to $PATH.


βš™οΈ Framework Settings (config.toml)ΒΆ

TOML-based configuration for framework behavior:

# config.toml β€” Framework behavior

[general]
lazy_load = true          # Defer non-critical hooks
auto_full = true          # Auto-compile full bundle
health_check = true       # Validate deps at startup
perf_budget = 100         # Max ms per file before warning

[notices]
quiet = false             # Suppress non-critical notices
show_tips = true          # Show tips on startup

[keybindings]
# Custom keybindings (loaded by startup/17_az_bindkey_from_toml.zsh)
"ctrl+g" = "aicmit"      # AI commit widget
"ctrl+d" = "aidebug"     # AI debug widget

πŸš€ Pre-Initialization (init.d/)ΒΆ

Scripts that run before the framework loads. Use this for:

  • Machine-specific overrides that must be set early
  • Environment variables that affect framework behavior
  • Workarounds that need to run before core logic
# init.d/00_machine_overrides.zsh
export ZSHAND_LAZY_LOAD=1
export ZSHAND_AUTO_FULL=1

# init.d/10_work_vpn.zsh
[[ -f /etc/vpn-active ]] && export HTTP_PROXY="http://proxy:8080"

Naming: Use NN_ prefix for load order (00 = first, 99 = last).


🏁 Post-Initialization (post.d/)¢

Scripts that run after everything else. Use this for:

  • Final overrides that need all framework functions available
  • Customizations that depend on hooks being loaded
  • Prompt tweaks that should be the absolute last thing
# post.d/00_final_env.zsh
# Set variables that depend on framework functions
export MY_PROJECT_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"

# post.d/99_prompt_tweaks.zsh
# Final prompt customization (after P10k loads)
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs)

βš™οΈ Core Overrides (rc.d/)ΒΆ

Replace any framework core file by placing a file with the same basename in rc.d/. The framework merges both directories and user files win:

Framework: $ZSHAND/core/14_aliases.zsh       ← default
User:      ~/.config/zshand/rc.d/14_aliases.zsh  ← loaded INSTEAD

Framework: $ZSHAND/core/10_options.zsh       ← default
User:      ~/.config/zshand/rc.d/11_my_options.zsh  ← loaded ALONGSIDE (odd number)

Rules:

  • Same basename β†’ replaces framework file entirely
  • Different basename β†’ adds to load sequence (use odd numbers)

πŸ› οΈ Custom Functions (functions/)ΒΆ

Drop .zsh files here to add your own functions:

# functions/greet.zsh
greet() {
    print -P "%F{green}Hello, ${1:-world}!%f"
}

These are loaded after framework functions and can override them.


⌨️ Custom Widgets (widgets/)¢

ZLE widgets for keyboard shortcuts:

# widgets/my_widget.zsh
my_widget() {
    BUFFER="echo hello"
    zle accept-line
}
zle -N my_widget

Bind in config.toml or post.d/:

[keybindings]
"ctrl+h" = "my_widget"

πŸͺ¨ Custom Hooks (hooks/)ΒΆ

Integration hooks for tools not covered by the framework:

# hooks/50_my_tool.zsh
if (( $+commands[my_tool] )); then
    eval "$(my_tool init zsh)"
fi

πŸ“Ÿ Personal Scripts (bin/)ΒΆ

Executable scripts placed here are automatically added to $PATH:

#!/usr/bin/env zsh
# bin/my_script
echo "Hello from my_script"

Make them executable: chmod +x ~/.config/zshand/bin/my_script


πŸ”„ Complete Override (zshrc.zsh)ΒΆ

For total control, create ~/.config/zshand/zshrc.zsh. This completely replaces the framework β€” nothing else loads:

# zshrc.zsh β€” Complete framework replacement
# WARNING: This disables ALL framework features
source ~/.config/zshand/env.zsh
source ~/.config/zshand/aliases.zsh
# ... your custom setup

Nuclear option

This is a full replacement β€” no framework code runs at all. Only use this if you need complete control over your shell setup.


🌍 Key Environment Variables¢

Variable Default Purpose
ZSHAND_CONFIG_DIR ~/.config/zshand Your config directory
ZSHAND_CACHE_DIR ~/.cache/zshand Compiled bundles and cache
ZSHAND_LOG_DIR ~/logs/zshand Log files
ZSHAND_DEV_MODE 0 Development mode (no bundles)
ZSHAND_AUTO_FULL 0 Auto-compile full bundle
ZSHAND_DEBUG 0 Verbose debug output
ZSHAND_LAZY_LOAD 0 Defer non-critical hooks
ZSHAND_SAFE_MODE 0 Emergency recovery
ZSHAND_PERF_BUDGET 100 Max ms per file

Document Purpose
πŸ—οΈ ARCHITECTURE.md Override system and boot sequence
🎨 STYLE_GUIDE.md Conventions for custom code
πŸ“ HEADER_STANDARD.md How to document your custom files
πŸ“‚ samples/user-config/README.md Example templates