Bash Script Installer for Zsh
The "Bash Script Installer" simplifies the setup of Zsh.
Zsh
Ubuntu
bash -c "$(curl -fsSL https://tinyurl.com/y6fhb594)"
WSL
bash -c "$(curl -fsSL https://tinyurl.com/ycyct6wp)"
MacOS
bash -c "$(curl -fsSL https://tinyurl.com/yptybtpa)"
Configuration
bash -c "$(curl -fsSL https://tinyurl.com/y8vf753j)"
Shell framework and prompt
The installers use Oh My Zsh for framework features and plugins, with the OMZ theme disabled:
ZSH_THEME=""
source "$ZSH/oh-my-zsh.sh"
eval "$(starship init zsh)"
Starship owns the prompt and loads after Oh My Zsh. The managed Starship config is installed at ~/.config/starship.toml. A Nerd Font is required for the Powerline symbols.
| 1 | # ============================================================================= |
| 2 | # OS-SPECIFIC ALIASES |
| 3 | # ============================================================================= |
| 4 | if [[ "$OSTYPE" == "linux-gnu"* ]] && command -v apt-get >/dev/null 2>&1; then |
| 5 | # Linux Only System update & cleanup |
| 6 | alias uu='sudo apt-get update && \ |
| 7 | sudo apt-get upgrade -y && \ |
| 8 | sudo apt-get full-upgrade -y && \ |
| 9 | sudo apt-get autoremove -y && \ |
| 10 | sudo apt-get autoclean -y && \ |
| 11 | sudo apt-get clean' |
| 12 | elif [[ "$OSTYPE" == "darwin"* ]]; then |
| 13 | # macOS Only |
| 14 | alias flushdns='sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder' |
| 15 | fi |
| 16 | |
| 17 | # ============================================================================= |
| 18 | # UNIVERSAL GIT ALIASES (Fixed: Removed broken "$@" from aliases) |
| 19 | # ============================================================================= |
| 20 | alias gph='git push' |
| 21 | alias gco='git checkout' |
| 22 | alias gbh='git branch' |
| 23 | alias gmt='git commit' |
| 24 | alias gpl='git pull' |
| 25 | alias grb='git rebase' |
| 26 | alias grt='git reset' |
| 27 | alias gst='git status' |
| 28 | alias grmrf='git checkout -- . && git clean -fd' |
| 1 | #!/bin/bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # Configuration |
| 5 | GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD" |
| 6 | CONFIG_FILES=( |
| 7 | ".alias" |
| 8 | ".func" |
| 9 | ".pathrc" |
| 10 | ".sourcerc" |
| 11 | ".vimrc" |
| 12 | ".zshrc" |
| 13 | ".config/starship.toml" |
| 14 | ) |
| 15 | |
| 16 | echo "Starting configuration download..." |
| 17 | |
| 18 | for f in "${CONFIG_FILES[@]}"; do |
| 19 | # Remove the leading dot for the URL path |
| 20 | remote_name="${f#.}" |
| 21 | [[ "$f" == ".config/starship.toml" ]] && remote_name="starship.toml" |
| 22 | url="$GIST_RAW_BASE/$remote_name" |
| 23 | target="$HOME/$f" |
| 24 | |
| 25 | echo "Downloading $f..." |
| 26 | mkdir -p "$(dirname "$target")" |
| 27 | |
| 28 | # Use -f to fail silently on server errors, -s for silent, -L to follow redirects |
| 29 | if curl -fsSL "$url" -o "$target"; then |
| 30 | echo "Successfully updated $target" |
| 31 | else |
| 32 | echo "Error: Failed to download $f from $url" >&2 |
| 33 | fi |
| 34 | done |
| 35 | |
| 36 | echo "Done! All configuration files have been replaced." |
| 37 |
| 1 | # ============================================================================= |
| 2 | # CUSTOM FUNCTIONS |
| 3 | # ============================================================================= |
| 4 | |
| 5 | # ----------------------------------------------------------------------------- |
| 6 | # Function: funcs (List available .func functions) |
| 7 | # ----------------------------------------------------------------------------- |
| 8 | funcs() { |
| 9 | local func_file="${1:-$HOME/.func}" |
| 10 | |
| 11 | if [ ! -f "$func_file" ]; then |
| 12 | printf "Error: Function file '%s' not found.\n" "$func_file" >&2 |
| 13 | return 1 |
| 14 | fi |
| 15 | |
| 16 | printf "Available functions in %s:\n" "$func_file" |
| 17 | awk ' |
| 18 | /^# Function: / { |
| 19 | line = $0 |
| 20 | sub(/^# Function: /, "", line) |
| 21 | |
| 22 | name = line |
| 23 | sub(/ .*/, "", name) |
| 24 | |
| 25 | desc = "" |
| 26 | if (line ~ / \(.+\)$/) { |
| 27 | desc = line |
| 28 | sub(/^[^ ]+ \(/, "", desc) |
| 29 | sub(/\)$/, "", desc) |
| 30 | } |
| 31 | |
| 32 | if (desc != "") { |
| 33 | printf " %-22s %s\n", name, desc |
| 34 | } else { |
| 35 | printf " %s\n", name |
| 36 | } |
| 37 | } |
| 38 | ' "$func_file" |
| 39 | } |
| 40 | |
| 41 | # ----------------------------------------------------------------------------- |
| 42 | # Function: clip (Cross-platform clipboard) |
| 43 | # ----------------------------------------------------------------------------- |
| 44 | clip() { |
| 45 | local cmd |
| 46 | local args=() |
| 47 | |
| 48 | if command -v pbcopy >/dev/null 2>&1; then |
| 49 | cmd="pbcopy" # macOS |
| 50 | elif grep -qi "microsoft" /proc/version 2>/dev/null && command -v clip.exe >/dev/null 2>&1; then |
| 51 | cmd="clip.exe" # WSL |
| 52 | elif [ "$XDG_SESSION_TYPE" = "wayland" ] && command -v wl-copy >/dev/null 2>&1; then |
| 53 | cmd="wl-copy" # Linux Wayland |
| 54 | elif command -v xclip >/dev/null 2>&1; then |
| 55 | cmd="xclip" # Linux X11 (Fallback 1) |
| 56 | args=("-selection" "clipboard") |
| 57 | elif command -v xsel >/dev/null 2>&1; then |
| 58 | cmd="xsel" # Linux X11 (Fallback 2) |
| 59 | args=("--clipboard" "--input") |
| 60 | else |
| 61 | printf "Error: No supported clipboard utility found.\n" >&2 |
| 62 | return 1 |
| 63 | fi |
| 64 | |
| 65 | if [ $# -gt 0 ]; then |
| 66 | if [ -f "$1" ]; then |
| 67 | "$cmd" "${args[@]}" < "$1" |
| 68 | echo "Copied contents of '$1' to clipboard." |
| 69 | else |
| 70 | printf "Error: File '%s' not found.\n" "$1" >&2 |
| 71 | return 1 |
| 72 | fi |
| 73 | else |
| 74 | "$cmd" "${args[@]}" |
| 75 | fi |
| 76 | } |
| 77 | |
| 78 | # ----------------------------------------------------------------------------- |
| 79 | # Function: open_file (Cross-platform file/directory opener) |
| 80 | # ----------------------------------------------------------------------------- |
| 81 | open_file() { |
| 82 | local target="${1:-.}" |
| 83 | |
| 84 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 85 | command open "$target" |
| 86 | elif grep -qi "microsoft" /proc/version 2>/dev/null; then |
| 87 | if command -v wslpath >/dev/null 2>&1 && command -v explorer.exe >/dev/null 2>&1; then |
| 88 | explorer.exe "$(wslpath -w "$target")" |
| 89 | else |
| 90 | printf "Error: 'wslpath' or 'explorer.exe' not found.\n" >&2 |
| 91 | return 1 |
| 92 | fi |
| 93 | elif [[ "$OSTYPE" == "linux-gnu"* ]]; then |
| 94 | if command -v xdg-open >/dev/null 2>&1; then |
| 95 | xdg-open "$target" |
| 96 | else |
| 97 | printf "Error: 'xdg-open' not found.\n" >&2 |
| 98 | return 1 |
| 99 | fi |
| 100 | fi |
| 101 | } |
| 102 | alias open='open_file' |
| 103 | |
| 104 | # ----------------------------------------------------------------------------- |
| 105 | # Function: clear_history (Supports shell, Claude, Codex, and OpenCode) |
| 106 | # ----------------------------------------------------------------------------- |
| 107 | clear_history() { |
| 108 | case "${1:-}" in |
| 109 | claude) |
| 110 | if [ -d "$HOME/.claude/projects" ]; then |
| 111 | # Use 'yes' to skip prompts and -f to ignore non-existent files |
| 112 | yes | rm -rf "$HOME/.claude/projects"/* |
| 113 | echo "Claude project history/cache cleared." |
| 114 | fi |
| 115 | ;; |
| 116 | |
| 117 | codex) |
| 118 | local codex_home="${CODEX_HOME:-$HOME/.codex}" |
| 119 | local codex_session_file |
| 120 | local codex_session_id |
| 121 | |
| 122 | mkdir -p "$codex_home" |
| 123 | : > "$codex_home/history.jsonl" |
| 124 | |
| 125 | if command -v codex >/dev/null 2>&1; then |
| 126 | find "$codex_home/sessions" "$codex_home/archived_sessions" -type f -name '*.jsonl' -print 2>/dev/null | while IFS= read -r codex_session_file; do |
| 127 | codex_session_id="$(sed -n 's/.*"session_id":"\([^"]*\)".*/\1/p; q' "$codex_session_file")" |
| 128 | if [ -n "$codex_session_id" ]; then |
| 129 | codex delete --force "$codex_session_id" >/dev/null 2>&1 || true |
| 130 | fi |
| 131 | done |
| 132 | fi |
| 133 | |
| 134 | rm -rf "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" |
| 135 | mkdir -p "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" |
| 136 | |
| 137 | echo "Codex history cleared." |
| 138 | ;; |
| 139 | |
| 140 | opencode) |
| 141 | local opencode_state_home="${XDG_STATE_HOME:-$HOME/.local/state}/opencode" |
| 142 | local opencode_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/opencode" |
| 143 | |
| 144 | mkdir -p "$opencode_state_home" "$opencode_data_home" |
| 145 | : > "$opencode_state_home/prompt-history.jsonl" |
| 146 | rm -f \ |
| 147 | "$opencode_data_home/opencode.db" \ |
| 148 | "$opencode_data_home/opencode.db-shm" \ |
| 149 | "$opencode_data_home/opencode.db-wal" |
| 150 | rm -rf "$opencode_data_home/repos" "$opencode_data_home/log" |
| 151 | mkdir -p "$opencode_data_home/repos" "$opencode_data_home/log" |
| 152 | |
| 153 | echo "OpenCode history cleared." |
| 154 | ;; |
| 155 | |
| 156 | *) |
| 157 | # 1. Truncate the file |
| 158 | : > "$HISTFILE" |
| 159 | |
| 160 | # 2. Clear RAM by briefly setting history size to 0 |
| 161 | local old_histsize=$HISTSIZE |
| 162 | HISTSIZE=0 |
| 163 | HISTSIZE=$old_histsize |
| 164 | |
| 165 | echo "Shell history cleared." |
| 166 | ;; |
| 167 | esac |
| 168 | } |
| 169 | |
| 170 | # ----------------------------------------------------------------------------- |
| 171 | # Function: require_cli (Check external CLI availability) |
| 172 | # ----------------------------------------------------------------------------- |
| 173 | require_cli() { |
| 174 | local binary="$1" |
| 175 | local label="$2" |
| 176 | local resolved |
| 177 | |
| 178 | if command -v whence >/dev/null 2>&1; then |
| 179 | if whence -p "$binary" >/dev/null 2>&1; then |
| 180 | return 0 |
| 181 | fi |
| 182 | else |
| 183 | resolved="$(command -v "$binary" 2>/dev/null)" || resolved="" |
| 184 | if [ -n "$resolved" ] && [ -x "$resolved" ]; then |
| 185 | return 0 |
| 186 | fi |
| 187 | fi |
| 188 | |
| 189 | printf "Error: %s ('%s') is not installed or not in your PATH.\n" "$label" "$binary" >&2 |
| 190 | return 1 |
| 191 | } |
| 192 | |
| 193 | # ----------------------------------------------------------------------------- |
| 194 | # Function: claude (Includes --yolo and clear shortcuts) |
| 195 | # ----------------------------------------------------------------------------- |
| 196 | claude() { |
| 197 | # Shortcut for clearing cache |
| 198 | if [[ "$1" == "clear" ]]; then |
| 199 | clear_history claude |
| 200 | return 0 |
| 201 | fi |
| 202 | |
| 203 | # Check if binary exists |
| 204 | require_cli claude "Claude Code CLI" || return 1 |
| 205 | |
| 206 | # Handle --yolo mode |
| 207 | if [[ "$1" == "--yolo" ]]; then |
| 208 | shift |
| 209 | command claude --dangerously-skip-permissions "$@" |
| 210 | return $? |
| 211 | fi |
| 212 | |
| 213 | command claude "$@" |
| 214 | } |
| 215 | |
| 216 | # ----------------------------------------------------------------------------- |
| 217 | # Internal: _prompt_confirm_indexing (Confirm project indexing via the terminal) |
| 218 | # ----------------------------------------------------------------------------- |
| 219 | _prompt_confirm_indexing() { |
| 220 | local project_root="$1" |
| 221 | local reply |
| 222 | |
| 223 | if ! (: </dev/tty) 2>/dev/null; then |
| 224 | printf "Notice: No interactive terminal available; enhancing without project indexing.\n" >&2 |
| 225 | return 1 |
| 226 | fi |
| 227 | |
| 228 | printf "Allow Auggie to index and use project context from '%s'? (y/N) " "$project_root" >/dev/tty |
| 229 | if ! IFS= read -r reply </dev/tty; then |
| 230 | printf "\nNotice: Unable to read confirmation; enhancing without project indexing.\n" >&2 |
| 231 | return 1 |
| 232 | fi |
| 233 | |
| 234 | case "$reply" in |
| 235 | y|Y|yes|YES|Yes) |
| 236 | return 0 |
| 237 | ;; |
| 238 | *) |
| 239 | return 1 |
| 240 | ;; |
| 241 | esac |
| 242 | } |
| 243 | |
| 244 | # ----------------------------------------------------------------------------- |
| 245 | # Internal: _enhance_prompt (Run Auggie and write only the enhanced prompt) |
| 246 | # ----------------------------------------------------------------------------- |
| 247 | _enhance_prompt() { |
| 248 | local prompt="$1" |
| 249 | local output_file="$2" |
| 250 | local run_dir |
| 251 | local prompt_file |
| 252 | local workspace |
| 253 | local cache_dir |
| 254 | local auth_file |
| 255 | local log_file |
| 256 | local parsed_file |
| 257 | local project_root |
| 258 | local use_project_context |
| 259 | local script_pid |
| 260 | local waited |
| 261 | local timeout_seconds |
| 262 | local monitor_status |
| 263 | |
| 264 | if [ -z "$prompt" ]; then |
| 265 | printf "Error: Prompt was empty.\n" >&2 |
| 266 | return 1 |
| 267 | fi |
| 268 | |
| 269 | if [ ! -s "$HOME/.augment/session.json" ]; then |
| 270 | printf "Error: Auggie session file not found. Run 'auggie login' first.\n" >&2 |
| 271 | return 1 |
| 272 | fi |
| 273 | |
| 274 | run_dir="$(mktemp -d "${TMPDIR:-/tmp}/auggie-enhance.XXXXXX")" || return 1 |
| 275 | prompt_file="$run_dir/prompt.txt" |
| 276 | workspace="$run_dir/workspace" |
| 277 | cache_dir="$run_dir/cache" |
| 278 | auth_file="$HOME/.augment/session.json" |
| 279 | log_file="$run_dir/auggie.log" |
| 280 | parsed_file="$run_dir/enhanced.txt" |
| 281 | mkdir -p "$workspace" "$cache_dir" |
| 282 | printf "%s\n" "$prompt" > "$prompt_file" |
| 283 | |
| 284 | project_root="$(git rev-parse --show-toplevel 2>/dev/null)" || project_root="$PWD" |
| 285 | use_project_context=0 |
| 286 | timeout_seconds=90 |
| 287 | |
| 288 | if _prompt_confirm_indexing "$project_root"; then |
| 289 | use_project_context=1 |
| 290 | timeout_seconds=300 |
| 291 | workspace="$project_root" |
| 292 | cache_dir="$HOME/.augment" |
| 293 | printf "Indexing approved; enhancing with project context from '%s'.\n" "$project_root" >&2 |
| 294 | else |
| 295 | printf "Enhancing without project indexing.\n" >&2 |
| 296 | fi |
| 297 | |
| 298 | ( |
| 299 | if [ "$use_project_context" -eq 1 ]; then |
| 300 | AUGGIE_PROMPT_FILE="$prompt_file" \ |
| 301 | AUGGIE_WORKSPACE="$workspace" \ |
| 302 | AUGGIE_CACHE_DIR="$cache_dir" \ |
| 303 | AUGGIE_AUTH_FILE="$auth_file" \ |
| 304 | script -q -f -O "$log_file" -c 'auggie --print --enhance-prompt --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --allow-indexing --wait-for-indexing --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE"' </dev/null >/dev/null 2>&1 & |
| 305 | else |
| 306 | AUGGIE_PROMPT_FILE="$prompt_file" \ |
| 307 | AUGGIE_WORKSPACE="$workspace" \ |
| 308 | AUGGIE_CACHE_DIR="$cache_dir" \ |
| 309 | AUGGIE_AUTH_FILE="$auth_file" \ |
| 310 | script -q -f -O "$log_file" -c 'auggie --print --enhance-prompt --no-discover-workspaces --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE"' </dev/null >/dev/null 2>&1 & |
| 311 | fi |
| 312 | |
| 313 | script_pid=$! |
| 314 | waited=0 |
| 315 | |
| 316 | while kill -0 "$script_pid" >/dev/null 2>&1; do |
| 317 | if grep -aq "🤖" "$log_file" 2>/dev/null || grep -aq "Tool call:" "$log_file" 2>/dev/null; then |
| 318 | kill "$script_pid" >/dev/null 2>&1 |
| 319 | wait "$script_pid" >/dev/null 2>&1 |
| 320 | exit 0 |
| 321 | fi |
| 322 | |
| 323 | if [ "$waited" -ge "$timeout_seconds" ]; then |
| 324 | kill "$script_pid" >/dev/null 2>&1 |
| 325 | wait "$script_pid" >/dev/null 2>&1 |
| 326 | exit 124 |
| 327 | fi |
| 328 | |
| 329 | sleep 1 |
| 330 | waited=$((waited + 1)) |
| 331 | done |
| 332 | |
| 333 | wait "$script_pid" >/dev/null 2>&1 || true |
| 334 | exit 0 |
| 335 | ) |
| 336 | monitor_status=$? |
| 337 | |
| 338 | if [ "$monitor_status" -eq 124 ]; then |
| 339 | printf "Error: Timed out waiting for Auggie to enhance the prompt.\n" >&2 |
| 340 | rm -rf "$run_dir" |
| 341 | return 124 |
| 342 | fi |
| 343 | |
| 344 | perl -ne ' |
| 345 | s/\r$//; |
| 346 | s/\e\][^\a]*(?:\a|\e\\)//g; |
| 347 | s/\e\[[0-?]*[ -\/]*[@-~]//g; |
| 348 | next if /Script started on/ || /Script done on/; |
| 349 | if (/^(?:✨\s*)?Enhanced prompt:\s*(.*)$/) { |
| 350 | $capturing = 1; |
| 351 | $output .= "$1\n" if length $1; |
| 352 | next; |
| 353 | } |
| 354 | next unless $capturing; |
| 355 | exit if /^🤖/ || /Tool call:/ || /Session terminated/; |
| 356 | $output .= $_; |
| 357 | END { |
| 358 | $output =~ s/^\s*\n//; |
| 359 | $output =~ s/\s+\z//; |
| 360 | print "$output\n" if length $output; |
| 361 | } |
| 362 | ' "$log_file" > "$parsed_file" |
| 363 | |
| 364 | if [ ! -s "$parsed_file" ]; then |
| 365 | printf "Error: Auggie did not return an enhanced prompt.\n" >&2 |
| 366 | rm -rf "$run_dir" |
| 367 | return 1 |
| 368 | fi |
| 369 | |
| 370 | if ! command cp "$parsed_file" "$output_file"; then |
| 371 | printf "Error: Unable to save the enhanced prompt.\n" >&2 |
| 372 | rm -rf "$run_dir" |
| 373 | return 1 |
| 374 | fi |
| 375 | rm -rf "$run_dir" |
| 376 | } |
| 377 | |
| 378 | # ----------------------------------------------------------------------------- |
| 379 | # Function: prompt (Enhance a prompt with Auggie and optional project context) |
| 380 | # ----------------------------------------------------------------------------- |
| 381 | prompt() { |
| 382 | local prompt |
| 383 | local line |
| 384 | local output_file |
| 385 | |
| 386 | require_cli auggie "Auggie CLI" || return 1 |
| 387 | require_cli script "script utility" || return 1 |
| 388 | |
| 389 | if [ $# -gt 0 ]; then |
| 390 | prompt="$*" |
| 391 | elif [ ! -t 0 ]; then |
| 392 | prompt="$(cat)" |
| 393 | else |
| 394 | echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished." |
| 395 | prompt="" |
| 396 | |
| 397 | while IFS= read -r line; do |
| 398 | [ "$line" = "EOF" ] && break |
| 399 | if [ -z "$prompt" ]; then |
| 400 | prompt="$line" |
| 401 | else |
| 402 | prompt="${prompt}"$'\n'"${line}" |
| 403 | fi |
| 404 | done |
| 405 | fi |
| 406 | |
| 407 | if [ -z "$prompt" ]; then |
| 408 | printf "Error: Prompt was empty.\n" >&2 |
| 409 | return 1 |
| 410 | fi |
| 411 | |
| 412 | output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1 |
| 413 | if ! _enhance_prompt "$prompt" "$output_file"; then |
| 414 | rm -f "$output_file" |
| 415 | return 1 |
| 416 | fi |
| 417 | |
| 418 | printf "Enhanced prompt:\n" |
| 419 | command cat "$output_file" |
| 420 | rm -f "$output_file" |
| 421 | |
| 422 | command auggie account status |
| 423 | } |
| 424 | |
| 425 | # ----------------------------------------------------------------------------- |
| 426 | # Function: plan_build (Claude Code multi-agent workflow orchestrator) |
| 427 | # ----------------------------------------------------------------------------- |
| 428 | plan_build() { |
| 429 | local claude_args=() |
| 430 | local use_yolo=0 |
| 431 | local enhance_payload=0 |
| 432 | |
| 433 | while [ $# -gt 0 ]; do |
| 434 | case "$1" in |
| 435 | --yolo) |
| 436 | if [ "$use_yolo" -eq 1 ]; then |
| 437 | echo "Error: Duplicate argument: --yolo" |
| 438 | echo "Usage: plan_build [--yolo] [--prompt]" |
| 439 | return 1 |
| 440 | fi |
| 441 | use_yolo=1 |
| 442 | claude_args=(--yolo) |
| 443 | ;; |
| 444 | --prompt) |
| 445 | if [ "$enhance_payload" -eq 1 ]; then |
| 446 | echo "Error: Duplicate argument: --prompt" |
| 447 | echo "Usage: plan_build [--yolo] [--prompt]" |
| 448 | return 1 |
| 449 | fi |
| 450 | enhance_payload=1 |
| 451 | ;; |
| 452 | *) |
| 453 | echo "Error: Unknown argument: $1" |
| 454 | echo "Usage: plan_build [--yolo] [--prompt]" |
| 455 | return 1 |
| 456 | ;; |
| 457 | esac |
| 458 | shift |
| 459 | done |
| 460 | |
| 461 | require_cli claude "Claude Code CLI" || return 1 |
| 462 | require_cli codex "Codex CLI" || return 1 |
| 463 | require_cli coderabbit "CodeRabbit CLI" || return 1 |
| 464 | if [ "$enhance_payload" -eq 1 ]; then |
| 465 | require_cli auggie "Auggie CLI" || return 1 |
| 466 | require_cli script "script utility" || return 1 |
| 467 | fi |
| 468 | |
| 469 | echo "📥 Reading payload... Type 'EOF' on a new line and press Enter when finished." |
| 470 | |
| 471 | local payload |
| 472 | local line |
| 473 | payload="" |
| 474 | |
| 475 | while IFS= read -r line; do |
| 476 | [ "$line" = "EOF" ] && break |
| 477 | if [ -z "$payload" ]; then |
| 478 | payload="$line" |
| 479 | else |
| 480 | payload="${payload}"$'\n'"${line}" |
| 481 | fi |
| 482 | done |
| 483 | |
| 484 | if [ -z "$payload" ]; then |
| 485 | echo "❌ Error: Payload was empty." |
| 486 | return 1 |
| 487 | fi |
| 488 | |
| 489 | if [ "$enhance_payload" -eq 1 ]; then |
| 490 | local enhanced_file |
| 491 | enhanced_file="$(mktemp "${TMPDIR:-/tmp}/plan-build-enhanced.XXXXXX")" || return 1 |
| 492 | |
| 493 | echo "✨ Enhancing payload with Auggie..." |
| 494 | if ! _enhance_prompt "$payload" "$enhanced_file"; then |
| 495 | rm -f "$enhanced_file" |
| 496 | echo "❌ Error: Prompt enhancement failed; Claude was not launched." |
| 497 | return 1 |
| 498 | fi |
| 499 | |
| 500 | payload="$(cat "$enhanced_file")" |
| 501 | rm -f "$enhanced_file" |
| 502 | |
| 503 | if [ -z "$payload" ]; then |
| 504 | echo "❌ Error: Enhanced payload was empty; Claude was not launched." |
| 505 | return 1 |
| 506 | fi |
| 507 | fi |
| 508 | |
| 509 | echo "🚀 Launching Claude Code with your multi-agent workflow..." |
| 510 | |
| 511 | claude "${claude_args[@]}" "Please read \`~/.claude/skills/plan-build/SKILL.md\` and strictly follow the 7-step multi-agent workflow to implement the following task: |
| 512 | |
| 513 | $payload" |
| 514 | } |
| 515 | |
| 516 | # ----------------------------------------------------------------------------- |
| 517 | # Function: aicommit (Automated Conventional Commit Engine Wrapper) |
| 518 | # ----------------------------------------------------------------------------- |
| 519 | aicommit() { |
| 520 | local diff engine prompt |
| 521 | |
| 522 | # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes |
| 523 | diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000) |
| 524 | |
| 525 | if [ -z "$diff" ]; then |
| 526 | printf "❌ Error: Nothing staged to commit.\n" >&2 |
| 527 | return 1 |
| 528 | fi |
| 529 | |
| 530 | if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then |
| 531 | printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2 |
| 532 | printf "Please create it and paste your system prompt inside.\n" >&2 |
| 533 | return 1 |
| 534 | fi |
| 535 | |
| 536 | engine=${1:-claude} |
| 537 | prompt=$(cat "$HOME/.config/ai-commit-prompt.txt") |
| 538 | |
| 539 | case "$engine" in |
| 540 | claude) |
| 541 | echo "🤖 Claude is analyzing staged changes..." |
| 542 | cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | command claude -p "Read the provided instructions and diff, then generate and execute the commit." |
| 543 | ;; |
| 544 | |
| 545 | copilot) |
| 546 | echo "🤖 Copilot is analyzing staged changes..." |
| 547 | copilot -p "$prompt |
| 548 | |
| 549 | Here is the git diff: |
| 550 | $diff" -s --no-ask-user --allow-tool='shell(git:*)' |
| 551 | ;; |
| 552 | |
| 553 | opencode) |
| 554 | echo "🤖 OpenCode is analyzing staged changes..." |
| 555 | # 2. Fixed: Piping standard input to opencode bypasses OS argument length limits |
| 556 | cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | opencode run |
| 557 | ;; |
| 558 | |
| 559 | *) |
| 560 | printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2 |
| 561 | printf "Usage: aicommit [claude | copilot | opencode]\n" >&2 |
| 562 | return 1 |
| 563 | ;; |
| 564 | esac |
| 565 | } |
| 566 | |
| 567 | # ----------------------------------------------------------------------------- |
| 568 | # Function: create (Efficiently create file + parent dirs) |
| 569 | # ----------------------------------------------------------------------------- |
| 570 | create() { |
| 571 | if ! command -v install >/dev/null 2>&1; then |
| 572 | printf "Error: 'install' coreutil is not available.\n" >&2 |
| 573 | return 1 |
| 574 | fi |
| 575 | |
| 576 | if [ $# -eq 0 ]; then |
| 577 | printf "Usage: create <file> [file ...]\n" >&2 |
| 578 | return 2 |
| 579 | fi |
| 580 | |
| 581 | for p in "$@"; do |
| 582 | # Expand tilde manually if shell doesn't |
| 583 | [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}" |
| 584 | |
| 585 | if [ -z "$p" ] || [ "$p" = "/" ]; then |
| 586 | printf "create: refusing to operate on '%s'\n" "$p" >&2 |
| 587 | continue |
| 588 | fi |
| 589 | |
| 590 | if [ -e "$p" ]; then |
| 591 | printf "create: '%s' already exists. Skipping.\n" "$p" >&2 |
| 592 | continue |
| 593 | fi |
| 594 | |
| 595 | if install -D /dev/null "$p"; then |
| 596 | echo "Created '$p'" |
| 597 | fi |
| 598 | done |
| 599 | } |
| 600 | |
| 601 | # ----------------------------------------------------------------------------- |
| 602 | # Function: kill_port (Kill or check the process using a port) |
| 603 | # ----------------------------------------------------------------------------- |
| 604 | kill_port() { |
| 605 | local action="kill" |
| 606 | local signal="TERM" |
| 607 | local port |
| 608 | local pids |
| 609 | local pid |
| 610 | |
| 611 | case "${1:-}" in |
| 612 | -c|--check) |
| 613 | action="check" |
| 614 | shift |
| 615 | ;; |
| 616 | -k|--kill) |
| 617 | action="kill" |
| 618 | shift |
| 619 | ;; |
| 620 | -f|--force) |
| 621 | action="kill" |
| 622 | signal="KILL" |
| 623 | shift |
| 624 | ;; |
| 625 | -h|--help|"") |
| 626 | printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2 |
| 627 | return 1 |
| 628 | ;; |
| 629 | esac |
| 630 | |
| 631 | port="${1:-}" |
| 632 | if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then |
| 633 | printf "Error: port must be a number between 1 and 65535.\n" >&2 |
| 634 | printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2 |
| 635 | return 1 |
| 636 | fi |
| 637 | |
| 638 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 639 | pids="$( |
| 640 | { |
| 641 | lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null |
| 642 | lsof -nP -tiUDP:"$port" 2>/dev/null |
| 643 | } | sort -u |
| 644 | )" |
| 645 | |
| 646 | if [ -z "$pids" ]; then |
| 647 | printf "Port %s is FREE\n" "$port" |
| 648 | return 0 |
| 649 | fi |
| 650 | |
| 651 | printf "Port %s is in use:\n" "$port" |
| 652 | lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null |
| 653 | else |
| 654 | local port_hex |
| 655 | local inodes |
| 656 | |
| 657 | port_hex="$(printf "%04X" "$port")" |
| 658 | inodes="$( |
| 659 | awk -v port="$port_hex" ' |
| 660 | NR > 1 { |
| 661 | split($2, local_addr, ":") |
| 662 | if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) { |
| 663 | print $10 |
| 664 | } |
| 665 | } |
| 666 | ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u |
| 667 | )" |
| 668 | |
| 669 | if [ -z "$inodes" ]; then |
| 670 | printf "Port %s is FREE\n" "$port" |
| 671 | return 0 |
| 672 | fi |
| 673 | |
| 674 | pids="" |
| 675 | pids="$( |
| 676 | find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null | |
| 677 | awk -v inodes="$inodes" ' |
| 678 | BEGIN { |
| 679 | split(inodes, inode_list, " ") |
| 680 | for (i in inode_list) { |
| 681 | wanted["socket:[" inode_list[i] "]"] = 1 |
| 682 | } |
| 683 | } |
| 684 | $2 in wanted { |
| 685 | split($1, path, "/") |
| 686 | print path[3] |
| 687 | } |
| 688 | ' | sort -u |
| 689 | )" |
| 690 | |
| 691 | if [ -z "$pids" ]; then |
| 692 | printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2 |
| 693 | return 1 |
| 694 | fi |
| 695 | |
| 696 | printf "Port %s is in use:\n" "$port" |
| 697 | for pid in $pids; do |
| 698 | if [ -r "/proc/$pid/cmdline" ]; then |
| 699 | printf " PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")" |
| 700 | else |
| 701 | printf " PID %s\n" "$pid" |
| 702 | fi |
| 703 | done |
| 704 | fi |
| 705 | |
| 706 | if [ "$action" = "kill" ]; then |
| 707 | for pid in $pids; do |
| 708 | if kill "-$signal" "$pid" 2>/dev/null; then |
| 709 | printf "Killed PID %s with SIG%s.\n" "$pid" "$signal" |
| 710 | else |
| 711 | printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2 |
| 712 | return 1 |
| 713 | fi |
| 714 | done |
| 715 | fi |
| 716 | } |
| 717 | |
| 718 | # ----------------------------------------------------------------------------- |
| 719 | # Function: append_path (Adds to session PATH if exists) |
| 720 | # ----------------------------------------------------------------------------- |
| 721 | append_path() { |
| 722 | local dir="$1" |
| 723 | if [ -d "$dir" ]; then |
| 724 | case ":$PATH:" in |
| 725 | *":$dir:"*) ;; |
| 726 | *) PATH="$PATH:$dir" ;; |
| 727 | esac |
| 728 | elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then |
| 729 | echo "Warning: $dir does not exist." >&2 |
| 730 | fi |
| 731 | } |
| 732 | |
| 733 | # ----------------------------------------------------------------------------- |
| 734 | # Function: add_path_to_config (Fixed awk/sed injection) |
| 735 | # ----------------------------------------------------------------------------- |
| 736 | add_path_to_config() { |
| 737 | local new_path="$1" |
| 738 | local config_file="$HOME/.bashrc" |
| 739 | [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc" |
| 740 | |
| 741 | if [ -z "$new_path" ]; then |
| 742 | printf "Usage: add_path_to_config <directory>\n" >&2 |
| 743 | return 1 |
| 744 | fi |
| 745 | |
| 746 | new_path="${new_path/#\~/$HOME}" |
| 747 | |
| 748 | if [ ! -d "$new_path" ]; then |
| 749 | printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path" |
| 750 | read -r REPLY |
| 751 | [[ ! $REPLY =~ ^[Yy]$ ]] && return 1 |
| 752 | fi |
| 753 | |
| 754 | # Check if already exists in file |
| 755 | if grep -Fq "append_path \"$new_path\"" "$config_file"; then |
| 756 | printf "Notice: Already in %s.\n" "$config_file" |
| 757 | return 0 |
| 758 | fi |
| 759 | |
| 760 | # Robust Injection: Use sed to insert before export PATH or just append |
| 761 | if grep -q "^export PATH" "$config_file"; then |
| 762 | sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file" |
| 763 | else |
| 764 | echo "append_path \"$new_path\"" >> "$config_file" |
| 765 | echo "export PATH" >> "$config_file" |
| 766 | fi |
| 767 | |
| 768 | printf "✅ Success: Added to %s\n" "$config_file" |
| 769 | append_path "$new_path" && export PATH |
| 770 | } |
| 771 |
Plan-Build Orchestrate Loop
Use this workflow when plan_build hands Claude Code an implementation task. The goal is to keep Claude as the orchestrator while using Codex and CodeRabbit as independent review and validation agents.
Operating Rules
- Run from the project root. Treat the current working directory as the project to modify.
- Preserve user work. Check
git statusbefore edits and do not revert unrelated changes. - Keep implementation scoped to the payload unless repository context proves a wider change is required.
- Prefer existing project conventions, scripts, test commands, and dependency managers.
- Do not call the task complete until validation has run or the reason it cannot run is documented.
- If any agent reports a plausible correctness, security, data-loss, migration, or test risk, resolve it or explicitly document why it is not applicable.
The 7-Step Workflow
1. Intake
Read the user payload fully. Identify:
- Objective and expected user-visible behavior.
- Files, modules, commands, and frameworks likely involved.
- Constraints from repository docs, package scripts, CI config, and existing patterns.
- Any ambiguity that blocks safe execution.
Only ask the user a question when no reasonable project-local assumption is safe.
2. Baseline
Inspect the repository before changing files:
git status --short
rg --files
Then read the smallest useful set of files. Prefer rg, package manifests, tests, routing files, and nearby implementations over broad file dumps.
3. Plan
Create a short implementation plan with concrete steps. Include validation commands. If the task touches behavior, data, auth, payments, destructive actions, or shared infrastructure, include a rollback or compatibility note.
4. Implement
Make the change in small, reviewable edits:
- Follow existing style and abstractions.
- Add or update tests when behavior changes.
- Update docs only when user-facing usage changes.
- Avoid unrelated refactors and formatting churn.
After each meaningful edit group, re-check the diff for accidental changes.
5. Codex Review Pass
Ask Codex for an independent review of the local diff before finalizing. Provide the task, constraints, and current diff. Ask it to focus on bugs, edge cases, missing tests, regressions, and simpler project-native alternatives.
Recommended prompt shape:
Review this change for correctness and risk. Prioritize bugs, regressions, missing tests, and mismatches with existing project patterns. Do not rewrite the whole solution unless a specific issue requires it.
Task:
<payload>
Diff:
<git diff>
Apply fixes for valid findings, then repeat this review pass if the fixes are non-trivial.
6. CodeRabbit Review Pass
Run CodeRabbit on the branch or diff when available. Treat its output as advisory but investigate every concrete finding.
If CodeRabbit cannot run locally, record the command attempted and the failure. Continue with manual validation rather than blocking indefinitely.
7. Validate And Close
Run the planned validation commands, such as:
npm test
npm run lint
pytest
cargo test
go test ./...
Use the commands that actually exist in the project. If validation fails, fix the issue and rerun the relevant command. If a failure is unrelated or environmental, capture the evidence.
Before final response:
- Confirm
git diffcontains only intended changes. - Summarize what changed.
- Report validation run and result.
- Note any remaining risks or commands that could not run.
| 1 | # ============================================================================= |
| 2 | # ENVIRONMENT & PATH CONFIGURATION |
| 3 | # ============================================================================= |
| 4 | |
| 5 | # 1. Source functions first |
| 6 | if [[ -f "$HOME/.func" && -r "$HOME/.func" ]]; then |
| 7 | source "$HOME/.func" |
| 8 | fi |
| 9 | |
| 10 | # 2. Define Root Variables |
| 11 | export DOTNET_ROOT="$HOME/.dotnet" |
| 12 | |
| 13 | # 3. PATH INITIALIZATION |
| 14 | # ----------------------------------------------------------------------------- |
| 15 | append_path "$HOME/.local/bin" |
| 16 | append_path "$DOTNET_ROOT" |
| 17 | append_path "$DOTNET_ROOT/tools" |
| 18 | append_path "$HOME/.opencode/bin" |
| 19 | append_path "$HOME/Flutter/bin" |
| 20 | |
| 21 | # macOS specific paths |
| 22 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 23 | append_path "/Applications/Espanso.app/Contents/MacOS" |
| 24 | fi |
| 25 | |
| 26 | # Finalize PATH |
| 27 | export PATH |
| 1 | # ============================================================================= |
| 2 | # FILE: ~/.sourcerc |
| 3 | # Description: Initializes third-party package managers and external tools. |
| 4 | # ============================================================================= |
| 5 | |
| 6 | # ============================================================================= |
| 7 | # GENERAL ENV |
| 8 | # ============================================================================= |
| 9 | if [[ -f "$HOME/.local/bin/env" && -r "$HOME/.local/bin/env" ]]; then |
| 10 | source "$HOME/.local/bin/env" |
| 11 | fi |
| 12 | |
| 13 | # ============================================================================= |
| 14 | # CLAUDE CODE / AI GATEWAY |
| 15 | # ============================================================================= |
| 16 | export ANTHROPIC_BASE_URL="https://gateway.ai.cloudflare.com/v1/9a71825e3842e918e0dff9ad84f50484/claude-code-gateway/anthropic" |
| 17 | |
| 18 | # ============================================================================= |
| 19 | # SDKMAN (Java/Kotlin/Scala Version Manager) |
| 20 | # ============================================================================= |
| 21 | export SDKMAN_DIR="$HOME/.sdkman" |
| 22 | if [[ -f "$SDKMAN_DIR/bin/sdkman-init.sh" && -r "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then |
| 23 | source "$SDKMAN_DIR/bin/sdkman-init.sh" |
| 24 | fi |
| 25 | |
| 26 | # ============================================================================= |
| 27 | # NVM (Node Version Manager) |
| 28 | # ============================================================================= |
| 29 | export NVM_DIR="$HOME/.nvm" |
| 30 | if [[ -f "$NVM_DIR/nvm.sh" && -r "$NVM_DIR/nvm.sh" ]]; then |
| 31 | source "$NVM_DIR/nvm.sh" |
| 32 | fi |
| 33 | if [[ -f "$NVM_DIR/bash_completion" && -r "$NVM_DIR/bash_completion" ]]; then |
| 34 | source "$NVM_DIR/bash_completion" |
| 35 | fi |
| 36 | |
| 37 | # ============================================================================= |
| 38 | # GOOGLE CLOUD SDK |
| 39 | # ============================================================================= |
| 40 | if [[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ]]; then |
| 41 | source "$HOME/google-cloud-sdk/path.zsh.inc" |
| 42 | fi |
| 43 | |
| 44 | if [[ -f "$HOME/google-cloud-sdk/completion.zsh.inc" ]]; then |
| 45 | source "$HOME/google-cloud-sdk/completion.zsh.inc" |
| 46 | fi |
| 1 | "$schema" = 'https://starship.rs/config-schema.json' |
| 2 | |
| 3 | format = """ |
| 4 | [](red)\ |
| 5 | $os\ |
| 6 | $username\ |
| 7 | [](bg:peach fg:red)\ |
| 8 | $directory\ |
| 9 | [](bg:yellow fg:peach)\ |
| 10 | $git_branch\ |
| 11 | $git_status\ |
| 12 | [](fg:yellow bg:green)\ |
| 13 | $c\ |
| 14 | $rust\ |
| 15 | $golang\ |
| 16 | $nodejs\ |
| 17 | $bun\ |
| 18 | $php\ |
| 19 | $java\ |
| 20 | $kotlin\ |
| 21 | $haskell\ |
| 22 | $python\ |
| 23 | [](fg:green bg:sapphire)\ |
| 24 | $conda\ |
| 25 | [](fg:sapphire bg:lavender)\ |
| 26 | $time\ |
| 27 | [ ](fg:lavender)\ |
| 28 | $cmd_duration\ |
| 29 | $line_break\ |
| 30 | $character""" |
| 31 | |
| 32 | palette = 'catppuccin_mocha' |
| 33 | |
| 34 | [os] |
| 35 | disabled = false |
| 36 | style = "bg:red fg:crust" |
| 37 | |
| 38 | [os.symbols] |
| 39 | Windows = "" |
| 40 | Ubuntu = "" |
| 41 | SUSE = "" |
| 42 | Raspbian = "" |
| 43 | Mint = "" |
| 44 | Macos = "" |
| 45 | Manjaro = "" |
| 46 | Linux = "" |
| 47 | Gentoo = "" |
| 48 | Fedora = "" |
| 49 | Alpine = "" |
| 50 | Amazon = "" |
| 51 | Android = "" |
| 52 | AOSC = "" |
| 53 | Arch = "" |
| 54 | Artix = "" |
| 55 | CentOS = "" |
| 56 | Debian = "" |
| 57 | Redhat = "" |
| 58 | RedHatEnterprise = "" |
| 59 | |
| 60 | [username] |
| 61 | show_always = true |
| 62 | style_user = "bg:red fg:crust" |
| 63 | style_root = "bg:red fg:crust" |
| 64 | format = '[ $user]($style)' |
| 65 | |
| 66 | [directory] |
| 67 | style = "bg:peach fg:crust" |
| 68 | format = "[ $path ]($style)" |
| 69 | truncation_length = 3 |
| 70 | truncation_symbol = "…/" |
| 71 | |
| 72 | [directory.substitutions] |
| 73 | "Documents" = " " |
| 74 | "Downloads" = " " |
| 75 | "Music" = " " |
| 76 | "Pictures" = " " |
| 77 | "Developer" = " " |
| 78 | |
| 79 | [git_branch] |
| 80 | symbol = "" |
| 81 | style = "bg:yellow" |
| 82 | format = '[[ $symbol $branch ](fg:crust bg:yellow)]($style)' |
| 83 | |
| 84 | [git_status] |
| 85 | style = "bg:yellow" |
| 86 | format = '[[($all_status$ahead_behind )](fg:crust bg:yellow)]($style)' |
| 87 | |
| 88 | [nodejs] |
| 89 | symbol = "" |
| 90 | style = "bg:green" |
| 91 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 92 | |
| 93 | [bun] |
| 94 | symbol = "" |
| 95 | style = "bg:green" |
| 96 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 97 | |
| 98 | [c] |
| 99 | symbol = " " |
| 100 | style = "bg:green" |
| 101 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 102 | |
| 103 | [rust] |
| 104 | symbol = "" |
| 105 | style = "bg:green" |
| 106 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 107 | |
| 108 | [golang] |
| 109 | symbol = "" |
| 110 | style = "bg:green" |
| 111 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 112 | |
| 113 | [php] |
| 114 | symbol = "" |
| 115 | style = "bg:green" |
| 116 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 117 | |
| 118 | [java] |
| 119 | symbol = " " |
| 120 | style = "bg:green" |
| 121 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 122 | |
| 123 | [kotlin] |
| 124 | symbol = "" |
| 125 | style = "bg:green" |
| 126 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 127 | |
| 128 | [haskell] |
| 129 | symbol = "" |
| 130 | style = "bg:green" |
| 131 | format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)' |
| 132 | |
| 133 | [python] |
| 134 | symbol = "" |
| 135 | style = "bg:green" |
| 136 | format = '[[ $symbol( $version)(\(#$virtualenv\)) ](fg:crust bg:green)]($style)' |
| 137 | |
| 138 | [docker_context] |
| 139 | symbol = "" |
| 140 | style = "bg:sapphire" |
| 141 | format = '[[ $symbol( $context) ](fg:crust bg:sapphire)]($style)' |
| 142 | |
| 143 | [conda] |
| 144 | symbol = " " |
| 145 | style = "fg:crust bg:sapphire" |
| 146 | format = '[$symbol$environment ]($style)' |
| 147 | ignore_base = false |
| 148 | |
| 149 | [time] |
| 150 | disabled = false |
| 151 | time_format = "%R" |
| 152 | style = "bg:lavender" |
| 153 | format = '[[ $time ](fg:crust bg:lavender)]($style)' |
| 154 | |
| 155 | [line_break] |
| 156 | disabled = false |
| 157 | |
| 158 | [character] |
| 159 | disabled = false |
| 160 | success_symbol = '[❯](bold fg:green)' |
| 161 | error_symbol = '[❯](bold fg:red)' |
| 162 | vimcmd_symbol = '[❮](bold fg:green)' |
| 163 | vimcmd_replace_one_symbol = '[❮](bold fg:lavender)' |
| 164 | vimcmd_replace_symbol = '[❮](bold fg:lavender)' |
| 165 | vimcmd_visual_symbol = '[❮](bold fg:yellow)' |
| 166 | |
| 167 | [cmd_duration] |
| 168 | show_milliseconds = true |
| 169 | format = " in $duration " |
| 170 | style = "bg:lavender" |
| 171 | disabled = false |
| 172 | show_notifications = true |
| 173 | min_time_to_notify = 45000 |
| 174 | |
| 175 | [palettes.catppuccin_mocha] |
| 176 | rosewater = "#f5e0dc" |
| 177 | flamingo = "#f2cdcd" |
| 178 | pink = "#f5c2e7" |
| 179 | mauve = "#cba6f7" |
| 180 | red = "#f38ba8" |
| 181 | maroon = "#eba0ac" |
| 182 | peach = "#fab387" |
| 183 | yellow = "#f9e2af" |
| 184 | green = "#a6e3a1" |
| 185 | teal = "#94e2d5" |
| 186 | sky = "#89dceb" |
| 187 | sapphire = "#74c7ec" |
| 188 | blue = "#89b4fa" |
| 189 | lavender = "#b4befe" |
| 190 | text = "#cdd6f4" |
| 191 | subtext1 = "#bac2de" |
| 192 | subtext0 = "#a6adc8" |
| 193 | overlay2 = "#9399b2" |
| 194 | overlay1 = "#7f849c" |
| 195 | overlay0 = "#6c7086" |
| 196 | surface2 = "#585b70" |
| 197 | surface1 = "#45475a" |
| 198 | surface0 = "#313244" |
| 199 | base = "#1e1e2e" |
| 200 | mantle = "#181825" |
| 201 | crust = "#11111b" |
| 202 | |
| 203 | [palettes.catppuccin_frappe] |
| 204 | rosewater = "#f2d5cf" |
| 205 | flamingo = "#eebebe" |
| 206 | pink = "#f4b8e4" |
| 207 | mauve = "#ca9ee6" |
| 208 | red = "#e78284" |
| 209 | maroon = "#ea999c" |
| 210 | peach = "#ef9f76" |
| 211 | yellow = "#e5c890" |
| 212 | green = "#a6d189" |
| 213 | teal = "#81c8be" |
| 214 | sky = "#99d1db" |
| 215 | sapphire = "#85c1dc" |
| 216 | blue = "#8caaee" |
| 217 | lavender = "#babbf1" |
| 218 | text = "#c6d0f5" |
| 219 | subtext1 = "#b5bfe2" |
| 220 | subtext0 = "#a5adce" |
| 221 | overlay2 = "#949cbb" |
| 222 | overlay1 = "#838ba7" |
| 223 | overlay0 = "#737994" |
| 224 | surface2 = "#626880" |
| 225 | surface1 = "#51576d" |
| 226 | surface0 = "#414559" |
| 227 | base = "#303446" |
| 228 | mantle = "#292c3c" |
| 229 | crust = "#232634" |
| 230 | |
| 231 | [palettes.catppuccin_latte] |
| 232 | rosewater = "#dc8a78" |
| 233 | flamingo = "#dd7878" |
| 234 | pink = "#ea76cb" |
| 235 | mauve = "#8839ef" |
| 236 | red = "#d20f39" |
| 237 | maroon = "#e64553" |
| 238 | peach = "#fe640b" |
| 239 | yellow = "#df8e1d" |
| 240 | green = "#40a02b" |
| 241 | teal = "#179299" |
| 242 | sky = "#04a5e5" |
| 243 | sapphire = "#209fb5" |
| 244 | blue = "#1e66f5" |
| 245 | lavender = "#7287fd" |
| 246 | text = "#4c4f69" |
| 247 | subtext1 = "#5c5f77" |
| 248 | subtext0 = "#6c6f85" |
| 249 | overlay2 = "#7c7f93" |
| 250 | overlay1 = "#8c8fa1" |
| 251 | overlay0 = "#9ca0b0" |
| 252 | surface2 = "#acb0be" |
| 253 | surface1 = "#bcc0cc" |
| 254 | surface0 = "#ccd0da" |
| 255 | base = "#eff1f5" |
| 256 | mantle = "#e6e9ef" |
| 257 | crust = "#dce0e8" |
| 258 | |
| 259 | [palettes.catppuccin_macchiato] |
| 260 | rosewater = "#f4dbd6" |
| 261 | flamingo = "#f0c6c6" |
| 262 | pink = "#f5bde6" |
| 263 | mauve = "#c6a0f6" |
| 264 | red = "#ed8796" |
| 265 | maroon = "#ee99a0" |
| 266 | peach = "#f5a97f" |
| 267 | yellow = "#eed49f" |
| 268 | green = "#a6da95" |
| 269 | teal = "#8bd5ca" |
| 270 | sky = "#91d7e3" |
| 271 | sapphire = "#7dc4e4" |
| 272 | blue = "#8aadf4" |
| 273 | lavender = "#b7bdf8" |
| 274 | text = "#cad3f5" |
| 275 | subtext1 = "#b8c0e0" |
| 276 | subtext0 = "#a5adcb" |
| 277 | overlay2 = "#939ab7" |
| 278 | overlay1 = "#8087a2" |
| 279 | overlay0 = "#6e738d" |
| 280 | surface2 = "#5b6078" |
| 281 | surface1 = "#494d64" |
| 282 | surface0 = "#363a4f" |
| 283 | base = "#24273a" |
| 284 | mantle = "#1e2030" |
| 285 | crust = "#181926" |
| 286 |
| 1 | " Enable line numbers |
| 2 | set number |
| 3 | |
| 4 | " Enable relative line numbers |
| 5 | set relativenumber |
| 6 | |
| 7 | " Enable syntax highlighting |
| 8 | syntax on |
| 9 | |
| 10 | " Set colorscheme |
| 11 | colorscheme slate |
| 12 | |
| 13 | " Enable file type detection and plugins |
| 14 | filetype plugin indent on |
| 15 | |
| 16 | " Set the tab width to 4 spaces |
| 17 | set tabstop=4 |
| 18 | set shiftwidth=4 |
| 19 | set expandtab |
| 20 | |
| 21 | " Enable auto-indentation |
| 22 | set autoindent |
| 23 | set smartindent |
| 24 | |
| 25 | " Highlight current line |
| 26 | set cursorline |
| 27 | |
| 28 | " Show matching parentheses |
| 29 | set showmatch |
| 30 | |
| 31 | " Enable line wrapping |
| 32 | set wrap |
| 33 | |
| 34 | " Enable mouse support |
| 35 | set mouse=a |
| 36 | |
| 37 | " Enable clipboard access |
| 38 | set clipboard=unnamedplus |
| 39 | |
| 40 | " Disable swap file |
| 41 | set noswapfile |
| 42 | |
| 43 | " Enable incremental search |
| 44 | set incsearch |
| 45 | |
| 46 | " Ignore case in search |
| 47 | set ignorecase |
| 48 | |
| 49 | " Override ignorecase if search contains capital letters |
| 50 | set smartcase |
| 51 | |
| 52 | " Display line and column number of the cursor position |
| 53 | set ruler |
| 54 | |
| 55 | " Set the status line at the bottom |
| 56 | set laststatus=2 |
| 57 | |
| 58 | " Show command in bottom bar |
| 59 | set showcmd |
| 60 | |
| 61 | " Set command height |
| 62 | set cmdheight=2 |
| 63 | |
| 64 | " Set history lines |
| 65 | set history=1000 |
| 66 | |
| 67 | " Disable backup file |
| 68 | set nobackup |
| 69 | |
| 70 | " Enable persistent undo |
| 71 | set undofile |
| 72 | |
| 73 | " Set maximum number of undo levels |
| 74 | set undolevels=1000 |
| 75 | |
| 76 | " Set undo directory |
| 77 | if has("persistent_undo") |
| 78 | silent !mkdir ~/.vim/undodir > /dev/null 2>&1 |
| 79 | set undodir=~/.vim/undodir |
| 80 | endif |
| 81 | |
| 82 | " Set search highlighting |
| 83 | set hlsearch |
| 84 | |
| 85 | " Enable visual bell |
| 86 | set visualbell |
| 87 | |
| 88 | " Set default file encoding |
| 89 | set encoding=utf-8 |
| 90 | |
| 91 | " Set the leader key to space |
| 92 | let mapleader = " " |
| 93 | |
| 94 | " Map <Leader>w to save the file |
| 95 | nnoremap <Leader>w :w<CR> |
| 96 | |
| 97 | " Map <Leader>q to quit |
| 98 | nnoremap <Leader>q :q<CR> |
| 99 | |
| 100 | " Map <Leader>x to save and quit |
| 101 | nnoremap <Leader>x :wq<CR> |
| 102 | |
| 103 | " Enable folding |
| 104 | set foldmethod=syntax |
| 105 | set foldlevelstart=99 |
| 106 | |
| 107 | " Enable line wrapping at 80 characters |
| 108 | set textwidth=80 |
| 109 | set colorcolumn=80 |
| 110 | |
| 111 | " Add some basic key mappings |
| 112 | " Map jj to escape insert mode |
| 113 | inoremap jj <Esc> |
| 114 | |
| 115 | " Map <Leader>n to toggle line numbers |
| 116 | nnoremap <Leader>n :set number!<CR> |
| 117 | |
| 118 | " Map <Leader>r to toggle relative line numbers |
| 119 | nnoremap <Leader>r :set relativenumber!<CR> |
| 120 | |
| 121 | " Configure plugins (if you use a plugin manager like vim-plug) |
| 122 | " Example with vim-plug: |
| 123 | " call plug#begin('~/.vim/plugged') |
| 124 | " Plug 'tpope/vim-sensible' |
| 125 | " Plug 'preservim/nerdtree' |
| 126 | " Plug 'junegunn/fzf', { 'do': { -> fzf#install() } } |
| 127 | " Plug 'airblade/vim-gitgutter' |
| 128 | " call plug#end() |
| 129 | |
| 130 | " NERDTree key mappings |
| 131 | " nnoremap <C-n> :NERDTreeToggle<CR> |
| 132 | |
| 133 | " Enable automatic hard wrapping at textwidth (80 chars) |
| 134 | set formatoptions+=t " Auto-wrap text using textwidth |
| 135 | set formatoptions+=c " Auto-wrap comments using textwidth |
| 136 | set formatoptions+=r " Continue comments when pressing Enter |
| 137 | set formatoptions+=o " Continue comments when using 'o' or 'O' |
| 138 | set formatoptions+=q " Allow formatting of comments with 'gq' |
| 139 | set formatoptions+=n " Recognize numbered lists |
| 140 | set formatoptions+=l " Don't break lines that were already long |
| 141 | |
| 142 | " Enable syntax highlighting |
| 143 | syntax on |
| 144 | |
| 145 | " Increase memory limit for complex syntax parsing (Prevents E363) |
| 146 | set maxmempattern=20000 |
| 1 | #!/bin/bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # ============================= |
| 5 | # COLORS & LOGGING |
| 6 | # ============================= |
| 7 | RED='\033[0;31m' |
| 8 | GREEN='\033[0;32m' |
| 9 | YELLOW='\033[1;33m' |
| 10 | BLUE='\033[0;34m' |
| 11 | NC='\033[0m' |
| 12 | |
| 13 | log() { echo -e "${BLUE}[INFO]${NC} $*"; } |
| 14 | ok() { echo -e "${GREEN}[OK]${NC} $*"; } |
| 15 | warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } |
| 16 | err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } |
| 17 | |
| 18 | # ============================= |
| 19 | # CONFIG |
| 20 | # ============================= |
| 21 | GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD" |
| 22 | |
| 23 | CONFIG_FILES=( |
| 24 | ".alias" |
| 25 | ".func" |
| 26 | ".pathrc" |
| 27 | ".sourcerc" |
| 28 | ".vimrc" |
| 29 | ".zshrc" |
| 30 | ".config/starship.toml" |
| 31 | ) |
| 32 | |
| 33 | # ============================= |
| 34 | # REQUIREMENTS |
| 35 | # ============================= |
| 36 | check_requirements() { |
| 37 | if [[ $EUID -eq 0 ]]; then |
| 38 | err "Do not run as root on macOS" |
| 39 | exit 1 |
| 40 | fi |
| 41 | } |
| 42 | |
| 43 | # ============================= |
| 44 | # INSTALLATION FUNCTIONS |
| 45 | # ============================= |
| 46 | install_oh_my_zsh() { |
| 47 | log "Installing Oh My Zsh and plugins..." |
| 48 | export RUNZSH=no |
| 49 | export CHSH=no |
| 50 | export KEEP_ZSHRC=yes |
| 51 | |
| 52 | if [[ ! -d "$HOME/.oh-my-zsh" ]]; then |
| 53 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended |
| 54 | else |
| 55 | ok "Oh My Zsh already installed" |
| 56 | fi |
| 57 | |
| 58 | local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" |
| 59 | local plugin_dir="$custom/plugins" |
| 60 | mkdir -p "$plugin_dir" |
| 61 | |
| 62 | [[ -d "$plugin_dir/zsh-autosuggestions" ]] || \ |
| 63 | git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions" |
| 64 | |
| 65 | [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || \ |
| 66 | git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting" |
| 67 | |
| 68 | ok "Oh My Zsh plugins installed to $plugin_dir" |
| 69 | } |
| 70 | |
| 71 | install_starship() { |
| 72 | if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then |
| 73 | ok "Starship already installed" |
| 74 | elif command -v brew >/dev/null 2>&1; then |
| 75 | log "Installing Starship with Homebrew..." |
| 76 | brew install starship |
| 77 | ok "Starship installed" |
| 78 | else |
| 79 | log "Installing Starship..." |
| 80 | mkdir -p "$HOME/.local/bin" |
| 81 | curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin" |
| 82 | ok "Starship installed" |
| 83 | fi |
| 84 | } |
| 85 | |
| 86 | download_configs() { |
| 87 | log "Downloading custom config files..." |
| 88 | local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)" |
| 89 | mkdir -p "$backup" |
| 90 | |
| 91 | for f in "${CONFIG_FILES[@]}"; do |
| 92 | # Strip the leading dot for the download URL |
| 93 | local remote_file="${f#.}" |
| 94 | [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml" |
| 95 | local url="$GIST_RAW_BASE/$remote_file" |
| 96 | local target="$HOME/$f" |
| 97 | local tmp="${target}.tmp.$$" |
| 98 | |
| 99 | mkdir -p "$(dirname "$target")" |
| 100 | |
| 101 | if [[ -f "$target" ]]; then |
| 102 | cp "$target" "$backup/" |
| 103 | fi |
| 104 | |
| 105 | log "Fetching $remote_file -> $f ..." |
| 106 | if curl -fsSL "$url" -o "$tmp"; then |
| 107 | mv "$tmp" "$target" |
| 108 | else |
| 109 | rm -f "$tmp" |
| 110 | warn "Failed to download $remote_file" |
| 111 | fi |
| 112 | done |
| 113 | |
| 114 | ok "Configs downloaded (Backup at $backup)" |
| 115 | } |
| 116 | |
| 117 | configure_zshrc() { |
| 118 | local zshrc="$HOME/.zshrc" |
| 119 | log "Configuring .zshrc for Oh My Zsh and Starship..." |
| 120 | |
| 121 | touch "$zshrc" |
| 122 | cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)" |
| 123 | |
| 124 | cat > "$zshrc" <<'EOF' |
| 125 | # ============================================================================= |
| 126 | # 1. HELPER FUNCTIONS & PATH |
| 127 | # ============================================================================= |
| 128 | source_if_readable() { |
| 129 | local file="$1" |
| 130 | if [[ -f "$file" && -r "$file" ]]; then |
| 131 | source "$file" |
| 132 | fi |
| 133 | } |
| 134 | |
| 135 | export PATH="$HOME/.local/bin:$PATH" |
| 136 | |
| 137 | # ============================================================================= |
| 138 | # 2. OH MY ZSH FRAMEWORK |
| 139 | # ============================================================================= |
| 140 | export ZSH="${ZSH:-$HOME/.oh-my-zsh}" |
| 141 | ZSH_THEME="" |
| 142 | |
| 143 | plugins=( |
| 144 | git |
| 145 | zsh-autosuggestions |
| 146 | zsh-syntax-highlighting |
| 147 | ) |
| 148 | |
| 149 | source_if_readable "$ZSH/oh-my-zsh.sh" |
| 150 | |
| 151 | # ============================================================================= |
| 152 | # 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS |
| 153 | # ============================================================================= |
| 154 | source_if_readable "$HOME/.sourcerc" |
| 155 | source_if_readable "$HOME/.func" |
| 156 | source_if_readable "$HOME/.pathrc" |
| 157 | source_if_readable "$HOME/.alias" |
| 158 | |
| 159 | # ============================================================================= |
| 160 | # 4. STARSHIP PROMPT |
| 161 | # ============================================================================= |
| 162 | if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then |
| 163 | export STARSHIP_CONFIG="$HOME/.config/starship.toml" |
| 164 | fi |
| 165 | |
| 166 | if command -v starship >/dev/null 2>&1; then |
| 167 | eval "$(starship init zsh)" |
| 168 | elif [[ -x "$HOME/.local/bin/starship" ]]; then |
| 169 | eval "$("$HOME/.local/bin/starship" init zsh)" |
| 170 | fi |
| 171 | EOF |
| 172 | |
| 173 | ok ".zshrc configured for Oh My Zsh framework with Starship prompt" |
| 174 | } |
| 175 | |
| 176 | switch_shell() { |
| 177 | log "Starting Zsh session..." |
| 178 | echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}" |
| 179 | echo "----------------------------------------" |
| 180 | zsh -l |
| 181 | echo "----------------------------------------" |
| 182 | ok "Returned from Zsh session" |
| 183 | } |
| 184 | |
| 185 | # ============================= |
| 186 | # INTERACTIVE MENU |
| 187 | # ============================= |
| 188 | show_menu() { |
| 189 | echo "===========================================" |
| 190 | echo "macOS Minimal Zsh Setup - Choose what to do" |
| 191 | echo "===========================================" |
| 192 | echo " 0) Run ALL steps (1-5)" |
| 193 | echo " 1) Install Oh My Zsh + plugins" |
| 194 | echo " 2) Install Starship prompt" |
| 195 | echo " 3) Download custom configs (~/.alias, .func, .vimrc, etc.)" |
| 196 | echo " 4) Configure ~/.zshrc (Oh My Zsh + Starship)" |
| 197 | echo " 5) Switch to Zsh (Temporary Sub-shell)" |
| 198 | echo " 6) Quit" |
| 199 | echo "===========================================" |
| 200 | } |
| 201 | |
| 202 | run_choices() { |
| 203 | local input |
| 204 | read -p "Select: " input |
| 205 | input="${input//,/ }" |
| 206 | |
| 207 | local -a to_run=() |
| 208 | local -a to_exclude=() |
| 209 | |
| 210 | for item in $input; do |
| 211 | if [[ "$item" == !* ]]; then |
| 212 | to_exclude+=("${item:1}") |
| 213 | elif [[ "$item" == "0" ]]; then |
| 214 | to_run+=(1 2 3 4 5) |
| 215 | else |
| 216 | to_run+=("$item") |
| 217 | fi |
| 218 | done |
| 219 | |
| 220 | if [[ ${#to_run[@]} -gt 0 ]]; then |
| 221 | for choice in "${to_run[@]}"; do |
| 222 | local skip=false |
| 223 | |
| 224 | if [[ ${#to_exclude[@]} -gt 0 ]]; then |
| 225 | for ex in "${to_exclude[@]}"; do |
| 226 | if [[ "$choice" == "$ex" ]]; then |
| 227 | skip=true |
| 228 | break |
| 229 | fi |
| 230 | done |
| 231 | fi |
| 232 | |
| 233 | $skip && continue |
| 234 | |
| 235 | case "$choice" in |
| 236 | 1) install_oh_my_zsh ;; |
| 237 | 2) install_starship ;; |
| 238 | 3) download_configs ;; |
| 239 | 4) configure_zshrc ;; |
| 240 | 5) switch_shell ;; |
| 241 | 6) exit 0 ;; |
| 242 | *) warn "Skipping invalid option: $choice" ;; |
| 243 | esac |
| 244 | echo |
| 245 | done |
| 246 | fi |
| 247 | } |
| 248 | |
| 249 | # ============================= |
| 250 | # MAIN |
| 251 | # ============================= |
| 252 | main() { |
| 253 | check_requirements |
| 254 | while true; do |
| 255 | show_menu |
| 256 | run_choices |
| 257 | read -p "Do you want to run more options? (y/n): " again |
| 258 | [[ "$again" =~ ^[Yy]$ ]] || break |
| 259 | done |
| 260 | ok "macOS minimal Zsh configuration complete!" |
| 261 | } |
| 262 | |
| 263 | main "$@" |
| 264 |
| 1 | #!/bin/bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # ============================= |
| 5 | # COLORS & LOGGING |
| 6 | # ============================= |
| 7 | RED='\033[0;31m' |
| 8 | GREEN='\033[0;32m' |
| 9 | YELLOW='\033[1;33m' |
| 10 | BLUE='\033[0;34m' |
| 11 | NC='\033[0m' |
| 12 | |
| 13 | log() { echo -e "${BLUE}[INFO]${NC} $*"; } |
| 14 | ok() { echo -e "${GREEN}[OK]${NC} $*"; } |
| 15 | warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } |
| 16 | err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } |
| 17 | |
| 18 | # ============================= |
| 19 | # FLAGS & CONFIG |
| 20 | # ============================= |
| 21 | SKIP_PACKAGES=false |
| 22 | SKIP_SHELL_CHANGE=false |
| 23 | INSTALL_HOMEBREW=false |
| 24 | |
| 25 | GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD" |
| 26 | CONFIG_FILES=( |
| 27 | ".alias" |
| 28 | ".func" |
| 29 | ".pathrc" |
| 30 | ".sourcerc" |
| 31 | ".vimrc" |
| 32 | ".zshrc" |
| 33 | ".config/starship.toml" |
| 34 | ) |
| 35 | |
| 36 | # ============================= |
| 37 | # OS DETECTION |
| 38 | # ============================= |
| 39 | detect_os() { |
| 40 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 41 | echo "macos" |
| 42 | elif [ -f /etc/os-release ]; then |
| 43 | . /etc/os-release |
| 44 | echo "$ID" |
| 45 | else |
| 46 | echo "unknown" |
| 47 | fi |
| 48 | } |
| 49 | |
| 50 | # ============================= |
| 51 | # REQUIREMENTS |
| 52 | # ============================= |
| 53 | check_requirements() { |
| 54 | if [[ $EUID -eq 0 ]]; then |
| 55 | err "Do not run as root. The script will request sudo when necessary." |
| 56 | exit 1 |
| 57 | fi |
| 58 | if [[ "$(detect_os)" != "macos" ]] && ! command -v sudo >/dev/null 2>&1; then |
| 59 | err "sudo required on Linux" |
| 60 | exit 1 |
| 61 | fi |
| 62 | } |
| 63 | |
| 64 | # ============================= |
| 65 | # INSTALLATION FUNCTIONS |
| 66 | # ============================= |
| 67 | update_system() { |
| 68 | $SKIP_PACKAGES && return |
| 69 | log "Updating system..." |
| 70 | local os=$(detect_os) |
| 71 | |
| 72 | case "$os" in |
| 73 | macos) |
| 74 | brew update || warn "Homebrew update failed; continuing installer" |
| 75 | ;; |
| 76 | ubuntu|debian) |
| 77 | sudo apt-get update -y |
| 78 | if ! sudo apt-get upgrade -y; then |
| 79 | warn "System upgrade did not complete. This can happen when apt wants to downgrade a package." |
| 80 | warn "Continuing because the Zsh setup does not require OS package upgrades to finish." |
| 81 | fi |
| 82 | ;; |
| 83 | fedora) |
| 84 | sudo dnf upgrade -y || warn "System upgrade failed; continuing installer" |
| 85 | ;; |
| 86 | arch) |
| 87 | sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer" |
| 88 | ;; |
| 89 | *) warn "Auto-update not supported for OS: $os" ;; |
| 90 | esac |
| 91 | ok "System update step finished" |
| 92 | } |
| 93 | |
| 94 | install_packages() { |
| 95 | $SKIP_PACKAGES && return |
| 96 | log "Installing core packages..." |
| 97 | local os=$(detect_os) |
| 98 | |
| 99 | case "$os" in |
| 100 | macos) brew install zsh git vim curl wget unzip xz ;; |
| 101 | ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;; |
| 102 | fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;; |
| 103 | arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;; |
| 104 | *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;; |
| 105 | esac |
| 106 | ok "Packages installed" |
| 107 | } |
| 108 | |
| 109 | set_timezone() { |
| 110 | log "Setting timezone to Asia/Singapore..." |
| 111 | local os=$(detect_os) |
| 112 | if [[ "$os" == "macos" ]]; then |
| 113 | sudo systemsetup -settimezone Asia/Singapore >/dev/null |
| 114 | else |
| 115 | sudo timedatectl set-timezone Asia/Singapore |
| 116 | fi |
| 117 | ok "Timezone set to Asia/Singapore" |
| 118 | } |
| 119 | |
| 120 | install_homebrew() { |
| 121 | ! $INSTALL_HOMEBREW && return |
| 122 | command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return |
| 123 | log "Installing Homebrew..." |
| 124 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 125 | ok "Homebrew installed" |
| 126 | } |
| 127 | |
| 128 | configure_shell() { |
| 129 | $SKIP_SHELL_CHANGE && return |
| 130 | log "Changing default shell to zsh..." |
| 131 | local zsh_path |
| 132 | zsh_path="$(command -v zsh)" |
| 133 | |
| 134 | if ! grep -qx "$zsh_path" /etc/shells; then |
| 135 | echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null |
| 136 | fi |
| 137 | chsh -s "$zsh_path" |
| 138 | ok "Shell changed (requires logout/login to take effect)" |
| 139 | } |
| 140 | |
| 141 | install_oh_my_zsh() { |
| 142 | log "Installing Oh My Zsh and plugins..." |
| 143 | export RUNZSH=no |
| 144 | export CHSH=no |
| 145 | export KEEP_ZSHRC=yes |
| 146 | |
| 147 | if [[ ! -d "$HOME/.oh-my-zsh" ]]; then |
| 148 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended |
| 149 | else |
| 150 | ok "Oh My Zsh already installed" |
| 151 | fi |
| 152 | |
| 153 | local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" |
| 154 | local plugin_dir="$custom/plugins" |
| 155 | mkdir -p "$plugin_dir" |
| 156 | |
| 157 | [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions" |
| 158 | [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting" |
| 159 | |
| 160 | ok "Oh My Zsh plugins installed to $plugin_dir" |
| 161 | } |
| 162 | |
| 163 | install_starship() { |
| 164 | if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then |
| 165 | ok "Starship already installed" |
| 166 | else |
| 167 | log "Installing Starship prompt..." |
| 168 | if command -v brew >/dev/null 2>&1; then |
| 169 | brew install starship |
| 170 | else |
| 171 | mkdir -p "$HOME/.local/bin" |
| 172 | curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin" |
| 173 | fi |
| 174 | ok "Starship installed" |
| 175 | fi |
| 176 | } |
| 177 | |
| 178 | download_configs() { |
| 179 | log "Downloading custom config files from OpenGist..." |
| 180 | local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)" |
| 181 | mkdir -p "$backup" |
| 182 | |
| 183 | for f in "${CONFIG_FILES[@]}"; do |
| 184 | local remote_file="${f#.}" |
| 185 | [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml" |
| 186 | local url="$GIST_RAW_BASE/$remote_file" |
| 187 | local target="$HOME/$f" |
| 188 | local tmp="${target}.tmp.$$" |
| 189 | |
| 190 | mkdir -p "$(dirname "$target")" |
| 191 | |
| 192 | if [[ -f "$target" ]]; then |
| 193 | cp "$target" "$backup/" |
| 194 | fi |
| 195 | |
| 196 | log "Fetching $remote_file -> $f ..." |
| 197 | if curl -fsSL "$url" -o "$tmp"; then |
| 198 | mv "$tmp" "$target" |
| 199 | else |
| 200 | rm -f "$tmp" |
| 201 | warn "Failed to download $remote_file" |
| 202 | fi |
| 203 | done |
| 204 | |
| 205 | ok "Configs downloaded (Backup at $backup)" |
| 206 | } |
| 207 | |
| 208 | configure_zshrc() { |
| 209 | local zshrc="$HOME/.zshrc" |
| 210 | log "Configuring .zshrc for Oh My Zsh and Starship..." |
| 211 | |
| 212 | touch "$zshrc" |
| 213 | cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)" |
| 214 | |
| 215 | cat > "$zshrc" <<'EOF' |
| 216 | # ============================================================================= |
| 217 | # 1. HELPER FUNCTIONS & PATH |
| 218 | # ============================================================================= |
| 219 | source_if_readable() { |
| 220 | local file="$1" |
| 221 | if [[ -f "$file" && -r "$file" ]]; then |
| 222 | source "$file" |
| 223 | fi |
| 224 | } |
| 225 | |
| 226 | export PATH="$HOME/.local/bin:$PATH" |
| 227 | |
| 228 | # ============================================================================= |
| 229 | # 2. OH MY ZSH FRAMEWORK |
| 230 | # ============================================================================= |
| 231 | export ZSH="${ZSH:-$HOME/.oh-my-zsh}" |
| 232 | ZSH_THEME="" |
| 233 | |
| 234 | plugins=( |
| 235 | git |
| 236 | zsh-autosuggestions |
| 237 | zsh-syntax-highlighting |
| 238 | ) |
| 239 | |
| 240 | source_if_readable "$ZSH/oh-my-zsh.sh" |
| 241 | |
| 242 | # ============================================================================= |
| 243 | # 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS |
| 244 | # ============================================================================= |
| 245 | source_if_readable "$HOME/.sourcerc" |
| 246 | source_if_readable "$HOME/.func" |
| 247 | source_if_readable "$HOME/.pathrc" |
| 248 | source_if_readable "$HOME/.alias" |
| 249 | |
| 250 | # ============================================================================= |
| 251 | # 4. STARSHIP PROMPT |
| 252 | # ============================================================================= |
| 253 | if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then |
| 254 | export STARSHIP_CONFIG="$HOME/.config/starship.toml" |
| 255 | fi |
| 256 | |
| 257 | if command -v starship >/dev/null 2>&1; then |
| 258 | eval "$(starship init zsh)" |
| 259 | elif [[ -x "$HOME/.local/bin/starship" ]]; then |
| 260 | eval "$("$HOME/.local/bin/starship" init zsh)" |
| 261 | fi |
| 262 | EOF |
| 263 | |
| 264 | ok ".zshrc configured for Oh My Zsh framework with Starship prompt" |
| 265 | } |
| 266 | |
| 267 | switch_shell() { |
| 268 | log "Starting Zsh session..." |
| 269 | echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}" |
| 270 | echo "----------------------------------------" |
| 271 | zsh -l |
| 272 | echo "----------------------------------------" |
| 273 | ok "Returned from Zsh session" |
| 274 | } |
| 275 | |
| 276 | # ============================= |
| 277 | # INTERACTIVE MENU |
| 278 | # ============================= |
| 279 | show_menu() { |
| 280 | echo "===========================================" |
| 281 | echo "Minimal Zsh Installer - Choose what to do" |
| 282 | echo "===========================================" |
| 283 | echo " 0) Run ALL steps (1-10)" |
| 284 | echo " 1) Update system packages" |
| 285 | echo " 2) Install core packages (zsh, git, vim, etc.)" |
| 286 | echo " 3) Set Timezone (Asia/Singapore)" |
| 287 | echo " 4) Install Homebrew" |
| 288 | echo " 5) Configure shell (chsh - sets default shell)" |
| 289 | echo " 6) Install Oh My Zsh + plugins" |
| 290 | echo " 7) Install Starship prompt" |
| 291 | echo " 8) Download custom configs (from OpenGist)" |
| 292 | echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)" |
| 293 | echo "10) Switch to Zsh (Temporary Sub-shell)" |
| 294 | echo "11) Quit" |
| 295 | echo "===========================================" |
| 296 | } |
| 297 | |
| 298 | run_choices() { |
| 299 | local input |
| 300 | read -p "Select: " input |
| 301 | input="${input//,/ }" |
| 302 | |
| 303 | local -a to_run=() |
| 304 | local -a to_exclude=() |
| 305 | |
| 306 | for item in $input; do |
| 307 | if [[ "$item" == !* ]]; then |
| 308 | to_exclude+=("${item:1}") |
| 309 | elif [[ "$item" == "0" ]]; then |
| 310 | to_run+=(1 2 3 4 5 6 7 8 9 10) |
| 311 | else |
| 312 | to_run+=("$item") |
| 313 | fi |
| 314 | done |
| 315 | |
| 316 | for choice in "${to_run[@]}"; do |
| 317 | local skip=false |
| 318 | |
| 319 | for ex in "${to_exclude[@]}"; do |
| 320 | if [[ "$choice" == "$ex" ]]; then |
| 321 | skip=true |
| 322 | break |
| 323 | fi |
| 324 | done |
| 325 | |
| 326 | $skip && continue |
| 327 | |
| 328 | case "$choice" in |
| 329 | 1) update_system ;; |
| 330 | 2) install_packages ;; |
| 331 | 3) set_timezone ;; |
| 332 | 4) install_homebrew ;; |
| 333 | 5) configure_shell ;; |
| 334 | 6) install_oh_my_zsh ;; |
| 335 | 7) install_starship ;; |
| 336 | 8) download_configs ;; |
| 337 | 9) configure_zshrc ;; |
| 338 | 10) switch_shell ;; |
| 339 | 11) log "Exiting..."; exit 0 ;; |
| 340 | *) warn "Skipping invalid option: $choice" ;; |
| 341 | esac |
| 342 | echo |
| 343 | done |
| 344 | } |
| 345 | |
| 346 | # ============================= |
| 347 | # MAIN |
| 348 | # ============================= |
| 349 | main() { |
| 350 | check_requirements |
| 351 | while true; do |
| 352 | show_menu |
| 353 | run_choices |
| 354 | read -p "Do you want to run more options? (y/n): " again |
| 355 | [[ "$again" =~ ^[Yy]$ ]] || break |
| 356 | done |
| 357 | ok "Zsh installation/configuration complete!" |
| 358 | } |
| 359 | |
| 360 | main "$@" |
| 361 |
| 1 | #!/bin/bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | # ============================= |
| 5 | # COLORS & LOGGING |
| 6 | # ============================= |
| 7 | RED='\033[0;31m' |
| 8 | GREEN='\033[0;32m' |
| 9 | YELLOW='\033[1;33m' |
| 10 | BLUE='\033[0;34m' |
| 11 | NC='\033[0m' |
| 12 | |
| 13 | log() { echo -e "${BLUE}[INFO]${NC} $*"; } |
| 14 | ok() { echo -e "${GREEN}[OK]${NC} $*"; } |
| 15 | warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } |
| 16 | err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } |
| 17 | |
| 18 | # ============================= |
| 19 | # FLAGS & CONFIG |
| 20 | # ============================= |
| 21 | SKIP_PACKAGES=false |
| 22 | SKIP_SHELL_CHANGE=false |
| 23 | INSTALL_HOMEBREW=false |
| 24 | |
| 25 | GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD" |
| 26 | CONFIG_FILES=( |
| 27 | ".alias" |
| 28 | ".func" |
| 29 | ".pathrc" |
| 30 | ".sourcerc" |
| 31 | ".vimrc" |
| 32 | ".zshrc" |
| 33 | ".config/starship.toml" |
| 34 | ) |
| 35 | |
| 36 | # ============================= |
| 37 | # PLATFORM DETECTION |
| 38 | # ============================= |
| 39 | detect_os() { |
| 40 | if [ -f /etc/os-release ]; then |
| 41 | . /etc/os-release |
| 42 | echo "$ID" |
| 43 | else |
| 44 | echo "unknown" |
| 45 | fi |
| 46 | } |
| 47 | |
| 48 | is_wsl() { |
| 49 | grep -qi "microsoft" /proc/version 2>/dev/null |
| 50 | } |
| 51 | |
| 52 | # ============================= |
| 53 | # REQUIREMENTS |
| 54 | # ============================= |
| 55 | check_requirements() { |
| 56 | if [[ $EUID -eq 0 ]]; then |
| 57 | err "Do not run as root. The script will request sudo when necessary." |
| 58 | exit 1 |
| 59 | fi |
| 60 | if ! command -v sudo >/dev/null 2>&1; then |
| 61 | err "sudo required" |
| 62 | exit 1 |
| 63 | fi |
| 64 | if ! is_wsl; then |
| 65 | err "This installer is intended for WSL" |
| 66 | exit 1 |
| 67 | fi |
| 68 | } |
| 69 | |
| 70 | # ============================= |
| 71 | # INSTALLATION FUNCTIONS |
| 72 | # ============================= |
| 73 | update_system() { |
| 74 | $SKIP_PACKAGES && return |
| 75 | log "Updating system..." |
| 76 | local os=$(detect_os) |
| 77 | |
| 78 | case "$os" in |
| 79 | ubuntu|debian) |
| 80 | sudo apt-get update -y |
| 81 | if ! sudo apt-get upgrade -y; then |
| 82 | warn "System upgrade did not complete. This can happen when apt wants to downgrade a package." |
| 83 | warn "Continuing because the Zsh setup does not require OS package upgrades to finish." |
| 84 | fi |
| 85 | ;; |
| 86 | fedora) |
| 87 | sudo dnf upgrade -y || warn "System upgrade failed; continuing installer" |
| 88 | ;; |
| 89 | arch) |
| 90 | sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer" |
| 91 | ;; |
| 92 | *) warn "Auto-update not supported for OS: $os" ;; |
| 93 | esac |
| 94 | ok "System update step finished" |
| 95 | } |
| 96 | |
| 97 | install_packages() { |
| 98 | $SKIP_PACKAGES && return |
| 99 | log "Installing core packages..." |
| 100 | local os=$(detect_os) |
| 101 | |
| 102 | case "$os" in |
| 103 | ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;; |
| 104 | fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;; |
| 105 | arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;; |
| 106 | *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;; |
| 107 | esac |
| 108 | ok "Packages installed" |
| 109 | } |
| 110 | |
| 111 | set_timezone() { |
| 112 | log "Checking timezone configuration..." |
| 113 | if command -v timedatectl >/dev/null 2>&1 && timedatectl status >/dev/null 2>&1; then |
| 114 | sudo timedatectl set-timezone Asia/Singapore |
| 115 | ok "Timezone set to Asia/Singapore" |
| 116 | return |
| 117 | fi |
| 118 | |
| 119 | warn "Skipping timezone change: timedatectl is not available in this WSL environment" |
| 120 | } |
| 121 | |
| 122 | install_homebrew() { |
| 123 | ! $INSTALL_HOMEBREW && return |
| 124 | command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return |
| 125 | log "Installing Homebrew..." |
| 126 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 127 | ok "Homebrew installed" |
| 128 | } |
| 129 | |
| 130 | configure_shell() { |
| 131 | $SKIP_SHELL_CHANGE && return |
| 132 | log "Changing default shell to zsh..." |
| 133 | local zsh_path |
| 134 | zsh_path="$(command -v zsh)" |
| 135 | |
| 136 | if ! grep -qx "$zsh_path" /etc/shells; then |
| 137 | echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null |
| 138 | fi |
| 139 | chsh -s "$zsh_path" |
| 140 | ok "Shell changed (open a new WSL session for it to take effect)" |
| 141 | } |
| 142 | |
| 143 | install_oh_my_zsh() { |
| 144 | log "Installing Oh My Zsh and plugins..." |
| 145 | export RUNZSH=no |
| 146 | export CHSH=no |
| 147 | export KEEP_ZSHRC=yes |
| 148 | |
| 149 | if [[ ! -d "$HOME/.oh-my-zsh" ]]; then |
| 150 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended |
| 151 | else |
| 152 | ok "Oh My Zsh already installed" |
| 153 | fi |
| 154 | |
| 155 | local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" |
| 156 | local plugin_dir="$custom/plugins" |
| 157 | mkdir -p "$plugin_dir" |
| 158 | |
| 159 | [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions" |
| 160 | [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting" |
| 161 | |
| 162 | ok "Oh My Zsh plugins installed to $plugin_dir" |
| 163 | } |
| 164 | |
| 165 | install_starship() { |
| 166 | if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then |
| 167 | ok "Starship already installed" |
| 168 | else |
| 169 | log "Installing Starship prompt..." |
| 170 | if command -v brew >/dev/null 2>&1; then |
| 171 | brew install starship |
| 172 | else |
| 173 | mkdir -p "$HOME/.local/bin" |
| 174 | curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin" |
| 175 | fi |
| 176 | ok "Starship installed" |
| 177 | fi |
| 178 | } |
| 179 | |
| 180 | download_configs() { |
| 181 | log "Downloading custom config files from OpenGist..." |
| 182 | local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)" |
| 183 | mkdir -p "$backup" |
| 184 | |
| 185 | for f in "${CONFIG_FILES[@]}"; do |
| 186 | local remote_file="${f#.}" |
| 187 | [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml" |
| 188 | local url="$GIST_RAW_BASE/$remote_file" |
| 189 | local target="$HOME/$f" |
| 190 | local tmp="${target}.tmp.$$" |
| 191 | |
| 192 | mkdir -p "$(dirname "$target")" |
| 193 | |
| 194 | if [[ -f "$target" ]]; then |
| 195 | cp "$target" "$backup/" |
| 196 | fi |
| 197 | |
| 198 | log "Fetching $remote_file -> $f ..." |
| 199 | if curl -fsSL "$url" -o "$tmp"; then |
| 200 | mv "$tmp" "$target" |
| 201 | else |
| 202 | rm -f "$tmp" |
| 203 | warn "Failed to download $remote_file" |
| 204 | fi |
| 205 | done |
| 206 | |
| 207 | ok "Configs downloaded (Backup at $backup)" |
| 208 | } |
| 209 | |
| 210 | configure_zshrc() { |
| 211 | local zshrc="$HOME/.zshrc" |
| 212 | log "Configuring .zshrc for Oh My Zsh and Starship..." |
| 213 | |
| 214 | touch "$zshrc" |
| 215 | cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)" |
| 216 | |
| 217 | cat > "$zshrc" <<'EOF' |
| 218 | # ============================================================================= |
| 219 | # 1. HELPER FUNCTIONS & PATH |
| 220 | # ============================================================================= |
| 221 | source_if_readable() { |
| 222 | local file="$1" |
| 223 | if [[ -f "$file" && -r "$file" ]]; then |
| 224 | source "$file" |
| 225 | fi |
| 226 | } |
| 227 | |
| 228 | export PATH="$HOME/.local/bin:$PATH" |
| 229 | |
| 230 | # ============================================================================= |
| 231 | # 2. OH MY ZSH FRAMEWORK |
| 232 | # ============================================================================= |
| 233 | export ZSH="${ZSH:-$HOME/.oh-my-zsh}" |
| 234 | ZSH_THEME="" |
| 235 | |
| 236 | plugins=( |
| 237 | git |
| 238 | zsh-autosuggestions |
| 239 | zsh-syntax-highlighting |
| 240 | ) |
| 241 | |
| 242 | source_if_readable "$ZSH/oh-my-zsh.sh" |
| 243 | |
| 244 | # ============================================================================= |
| 245 | # 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS |
| 246 | # ============================================================================= |
| 247 | source_if_readable "$HOME/.sourcerc" |
| 248 | source_if_readable "$HOME/.func" |
| 249 | source_if_readable "$HOME/.pathrc" |
| 250 | source_if_readable "$HOME/.alias" |
| 251 | |
| 252 | # ============================================================================= |
| 253 | # 4. STARSHIP PROMPT |
| 254 | # ============================================================================= |
| 255 | if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then |
| 256 | export STARSHIP_CONFIG="$HOME/.config/starship.toml" |
| 257 | fi |
| 258 | |
| 259 | if command -v starship >/dev/null 2>&1; then |
| 260 | eval "$(starship init zsh)" |
| 261 | elif [[ -x "$HOME/.local/bin/starship" ]]; then |
| 262 | eval "$("$HOME/.local/bin/starship" init zsh)" |
| 263 | fi |
| 264 | EOF |
| 265 | |
| 266 | ok ".zshrc configured for Oh My Zsh framework with Starship prompt" |
| 267 | } |
| 268 | |
| 269 | switch_shell() { |
| 270 | log "Starting Zsh session..." |
| 271 | echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}" |
| 272 | echo "----------------------------------------" |
| 273 | zsh -l |
| 274 | echo "----------------------------------------" |
| 275 | ok "Returned from Zsh session" |
| 276 | } |
| 277 | |
| 278 | # ============================= |
| 279 | # INTERACTIVE MENU |
| 280 | # ============================= |
| 281 | show_menu() { |
| 282 | echo "===========================================" |
| 283 | echo "WSL Minimal Zsh Installer - Choose what to do" |
| 284 | echo "===========================================" |
| 285 | echo " 0) Run ALL steps (1-10)" |
| 286 | echo " 1) Update system packages" |
| 287 | echo " 2) Install core packages (zsh, git, vim, etc.)" |
| 288 | echo " 3) Set Timezone (best effort)" |
| 289 | echo " 4) Install Homebrew" |
| 290 | echo " 5) Configure shell (chsh - sets default shell)" |
| 291 | echo " 6) Install Oh My Zsh + plugins" |
| 292 | echo " 7) Install Starship prompt" |
| 293 | echo " 8) Download custom configs (from OpenGist)" |
| 294 | echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)" |
| 295 | echo "10) Switch to Zsh (Temporary Sub-shell)" |
| 296 | echo "11) Quit" |
| 297 | echo "===========================================" |
| 298 | } |
| 299 | |
| 300 | run_choices() { |
| 301 | local input |
| 302 | read -p "Select: " input |
| 303 | input="${input//,/ }" |
| 304 | |
| 305 | local -a to_run=() |
| 306 | local -a to_exclude=() |
| 307 | |
| 308 | for item in $input; do |
| 309 | if [[ "$item" == !* ]]; then |
| 310 | to_exclude+=("${item:1}") |
| 311 | elif [[ "$item" == "0" ]]; then |
| 312 | to_run+=(1 2 3 4 5 6 7 8 9 10) |
| 313 | else |
| 314 | to_run+=("$item") |
| 315 | fi |
| 316 | done |
| 317 | |
| 318 | for choice in "${to_run[@]}"; do |
| 319 | local skip=false |
| 320 | |
| 321 | for ex in "${to_exclude[@]}"; do |
| 322 | if [[ "$choice" == "$ex" ]]; then |
| 323 | skip=true |
| 324 | break |
| 325 | fi |
| 326 | done |
| 327 | |
| 328 | $skip && continue |
| 329 | |
| 330 | case "$choice" in |
| 331 | 1) update_system ;; |
| 332 | 2) install_packages ;; |
| 333 | 3) set_timezone ;; |
| 334 | 4) install_homebrew ;; |
| 335 | 5) configure_shell ;; |
| 336 | 6) install_oh_my_zsh ;; |
| 337 | 7) install_starship ;; |
| 338 | 8) download_configs ;; |
| 339 | 9) configure_zshrc ;; |
| 340 | 10) switch_shell ;; |
| 341 | 11) log "Exiting..."; exit 0 ;; |
| 342 | *) warn "Skipping invalid option: $choice" ;; |
| 343 | esac |
| 344 | echo |
| 345 | done |
| 346 | } |
| 347 | |
| 348 | # ============================= |
| 349 | # MAIN |
| 350 | # ============================= |
| 351 | main() { |
| 352 | check_requirements |
| 353 | while true; do |
| 354 | show_menu |
| 355 | run_choices |
| 356 | read -p "Do you want to run more options? (y/n): " again |
| 357 | [[ "$again" =~ ^[Yy]$ ]] || break |
| 358 | done |
| 359 | ok "WSL Zsh installation/configuration complete!" |
| 360 | } |
| 361 | |
| 362 | main "$@" |
| 363 |
| 1 | # ============================================================================= |
| 2 | # 1. HELPER FUNCTIONS & PATH |
| 3 | # ============================================================================= |
| 4 | source_if_readable() { |
| 5 | local file="$1" |
| 6 | if [[ -f "$file" && -r "$file" ]]; then |
| 7 | source "$file" |
| 8 | fi |
| 9 | } |
| 10 | |
| 11 | # Ensure local bin is in PATH early (catches manual Starship installations) |
| 12 | export PATH="$HOME/.local/bin:$PATH" |
| 13 | |
| 14 | # ============================================================================= |
| 15 | # 2. OH MY ZSH FRAMEWORK |
| 16 | # ============================================================================= |
| 17 | export ZSH="${ZSH:-$HOME/.oh-my-zsh}" |
| 18 | ZSH_THEME="" |
| 19 | |
| 20 | plugins=( |
| 21 | git |
| 22 | zsh-autosuggestions |
| 23 | zsh-syntax-highlighting |
| 24 | ) |
| 25 | |
| 26 | source_if_readable "$ZSH/oh-my-zsh.sh" |
| 27 | |
| 28 | # ============================================================================= |
| 29 | # 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS |
| 30 | # ============================================================================= |
| 31 | # Load external initializers (SDKMAN, NVM, etc.) |
| 32 | source_if_readable "$HOME/.sourcerc" |
| 33 | |
| 34 | # Custom Functions (must load before Path Management) |
| 35 | source_if_readable "$HOME/.func" |
| 36 | |
| 37 | # Path Management (Relies on append_path from .func) |
| 38 | source_if_readable "$HOME/.pathrc" |
| 39 | |
| 40 | # Aliases (Loaded late so they override framework/system defaults) |
| 41 | source_if_readable "$HOME/.alias" |
| 42 | |
| 43 | # ============================================================================= |
| 44 | # 4. STARSHIP PROMPT |
| 45 | # ============================================================================= |
| 46 | if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then |
| 47 | export STARSHIP_CONFIG="$HOME/.config/starship.toml" |
| 48 | fi |
| 49 | |
| 50 | # Starship owns the prompt. It must initialize after Oh My Zsh. |
| 51 | if command -v starship >/dev/null 2>&1; then |
| 52 | eval "$(starship init zsh)" |
| 53 | elif [[ -x "$HOME/.local/bin/starship" ]]; then |
| 54 | eval "$("$HOME/.local/bin/starship" init zsh)" |
| 55 | fi |
| 56 |