Naposledy aktivní 1 week ago

Cross-platform Zsh setup scripts and managed dotfiles with Oh My Zsh framework, Starship prompt, aliases, functions, and path configuration.

Revize fedf67e9d94200a92ca63040590debe0301861ef

README.md Raw

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.

AI commits

The managed aicommit function can use Claude, Codex, Copilot, or OpenCode to create a commit from staged changes:

aicommit opencode

The installers place the required OpenCode agent at ~/.config/opencode/agents/ai-commit.md. The agent may inspect Git status, the cached diff, and recent history, and may execute git commit; all unrelated tools and shell commands are denied. The shared commit instructions must exist at ~/.config/ai-commit-prompt.txt.

Plan-build workflow

The local plan_build function is a thin cached launcher for the canonical public plan-build gist. When curl is available, each invocation stages plan_build.zsh, SKILL.md, and ARCHITECT.md, requires all three to be non-empty, and syntax-checks the executable. It then moves the complete bundle into a unique immutable release directory and atomically switches one current symlink. Refresh activation is serialized with a kernel-backed Zsh file lock so concurrent callers cannot mix releases and crashes cannot leave a stale lock. If refresh fails, the launcher preserves and uses the last complete, validated release and repairs its skill links. Zsh is always required; curl is required only when no valid cached release exists.

plan_build
plan_build --prompt
plan_build --writing-plan
plan_build --prompt --brainstorm
plan_build --yolo --writing-plan
plan_build --architect
plan_build --architect --new
plan_build --architect --yolo

The canonical executable defines the current command behavior and options. Each invocation binds the executable and both skills to its selected immutable release. The launcher also points the installed Claude Code skill paths through the active current release:

  • SKILL.md~/.claude/skills/plan-build/SKILL.md
  • ARCHITECT.md~/.claude/skills/plan-build-architect/SKILL.md

The launcher defaults to https://opengist.resetrix.work/weehong/plan-build/raw/HEAD and ${XDG_CACHE_HOME:-$HOME/.cache}/plan-build. Override these with PLAN_BUILD_BASE_URL and PLAN_BUILD_CACHE_DIR. Installed skill paths remain fixed at ~/.claude/skills; cached invocations receive immutable release paths through environment variables. The downloaded script is remote executable code: syntax validation catches malformed Zsh but is not a security sandbox, so only point PLAN_BUILD_BASE_URL at a source you trust. Remote code is executed in a child Zsh process and is never sourced into the caller shell.

Invoking plan_build once installs or repairs both skill links. To install standalone files without the cached launcher, follow the installation instructions in the canonical gist instead.

ai-commit.md Raw

description: Creates one conventional commit from the staged diff supplied in the prompt. mode: primary permission: "": deny bash: "": deny "git status ": allow "git diff --cached": allow "git log *": allow "git commit *": allow

Follow the supplied commit instructions and staged diff. Generate a concise conventional commit message, then execute exactly one git commit with that message. You may inspect Git status, the cached diff, and recent log. Do not modify files or change the staged set.

alias Raw
1# =============================================================================
2# OS-SPECIFIC ALIASES
3# =============================================================================
4if [[ "$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'
12elif [[ "$OSTYPE" == "darwin"* ]]; then
13 # macOS Only
14 alias flushdns='sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder'
15fi
16
17# =============================================================================
18# UNIVERSAL GIT ALIASES (Fixed: Removed broken "$@" from aliases)
19# =============================================================================
20alias gph='git push'
21alias gco='git checkout'
22alias gbh='git branch'
23alias gmt='git commit'
24alias gpl='git pull'
25alias grb='git rebase'
26alias grt='git reset'
27alias gst='git status'
28alias grmrf='git checkout -- . && git clean -fd'
config.sh Raw
1#!/bin/bash
2set -euo pipefail
3
4# Configuration
5GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD"
6CONFIG_FILES=(
7 ".alias"
8 ".func"
9 ".pathrc"
10 ".sourcerc"
11 ".vimrc"
12 ".zshrc"
13 ".config/starship.toml"
14 ".config/opencode/agents/ai-commit.md"
15)
16
17echo "Starting configuration download..."
18download_failed=0
19
20for f in "${CONFIG_FILES[@]}"; do
21 # Remove the leading dot for the URL path
22 remote_name="${f#.}"
23 [[ "$f" == ".config/starship.toml" ]] && remote_name="starship.toml"
24 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_name="ai-commit.md"
25 url="$GIST_RAW_BASE/$remote_name"
26 target="$HOME/$f"
27 tmp="${target}.tmp.$$"
28
29 echo "Downloading $f..."
30 mkdir -p "$(dirname "$target")"
31
32 # Use -f to fail silently on server errors, -s for silent, -L to follow redirects
33 if curl -fsSL "$url" -o "$tmp" && mv "$tmp" "$target"; then
34 echo "Successfully updated $target"
35 else
36 rm -f "$tmp"
37 echo "Error: Failed to download $f from $url" >&2
38 download_failed=1
39 fi
40done
41
42if [ "$download_failed" -ne 0 ]; then
43 echo "Configuration download completed with errors." >&2
44 exit 1
45fi
46
47echo "Done! All configuration files have been replaced."
48
func Raw
1# =============================================================================
2# CUSTOM FUNCTIONS
3# =============================================================================
4
5# -----------------------------------------------------------------------------
6# Function: funcs (List available .func functions)
7# -----------------------------------------------------------------------------
8funcs() {
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# -----------------------------------------------------------------------------
44clip() {
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# -----------------------------------------------------------------------------
81open_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}
102alias open='open_file'
103
104# -----------------------------------------------------------------------------
105# Function: clear_history (Supports shell, Claude, Codex, and OpenCode)
106# -----------------------------------------------------------------------------
107clear_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# -----------------------------------------------------------------------------
173require_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# -----------------------------------------------------------------------------
196claude() {
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 || true
319 wait "$script_pid" >/dev/null 2>&1 || true
320 exit 0
321 fi
322
323 if [ "$waited" -ge "$timeout_seconds" ]; then
324 kill "$script_pid" >/dev/null 2>&1 || true
325 wait "$script_pid" >/dev/null 2>&1 || true
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 # Auggie redraws streamed terminal lines with carriage returns. Keep only
346 # the final rendered segment so stale text cannot corrupt the prompt.
347 s/\r$//;
348 s/.*\r//;
349 1 while s/[^\x08]\x08//g;
350 s/\x08//g;
351 s/\e\][^\a]*(?:\a|\e\\)//g;
352 s/\e\[[0-?]*[ -\/]*[@-~]//g;
353 next if /Script started on/ || /Script done on/;
354 if (/^(?:✨\s*)?Enhanced prompt:\s*(.*)$/) {
355 $capturing = 1;
356 $output .= "$1\n" if length $1;
357 next;
358 }
359 next unless $capturing;
360 exit if /^🤖/ || /Tool call:/ || /Session terminated/;
361 $output .= $_;
362 END {
363 $output =~ s/^\s*\n//;
364 $output =~ s/\s+\z//;
365 print "$output\n" if length $output;
366 }
367 ' "$log_file" > "$parsed_file"
368
369 if [ ! -s "$parsed_file" ]; then
370 printf "Error: Auggie did not return an enhanced prompt.\n" >&2
371 rm -rf "$run_dir"
372 return 1
373 fi
374
375 if ! command cp "$parsed_file" "$output_file"; then
376 printf "Error: Unable to save the enhanced prompt.\n" >&2
377 rm -rf "$run_dir"
378 return 1
379 fi
380 rm -rf "$run_dir"
381}
382
383# -----------------------------------------------------------------------------
384# Function: prompt (Enhance a prompt with Auggie and optional project context)
385# -----------------------------------------------------------------------------
386prompt() {
387 local prompt
388 local line
389 local output_file
390
391 require_cli auggie "Auggie CLI" || return 1
392 require_cli script "script utility" || return 1
393
394 if [ $# -gt 0 ]; then
395 prompt="$*"
396 elif [ ! -t 0 ]; then
397 prompt="$(cat)"
398 else
399 echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished."
400 prompt=""
401
402 while IFS= read -r line; do
403 [ "$line" = "EOF" ] && break
404 if [ -z "$prompt" ]; then
405 prompt="$line"
406 else
407 prompt="${prompt}"$'\n'"${line}"
408 fi
409 done
410 fi
411
412 if [ -z "$prompt" ]; then
413 printf "Error: Prompt was empty.\n" >&2
414 return 1
415 fi
416
417 output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1
418 if ! _enhance_prompt "$prompt" "$output_file"; then
419 rm -f "$output_file"
420 return 1
421 fi
422
423 if [ -t 1 ]; then
424 clear
425 fi
426
427 printf "Enhanced prompt:\n"
428 command cat "$output_file"
429 rm -f "$output_file"
430
431 command auggie account status
432}
433
434# -----------------------------------------------------------------------------
435# Function: plan_build (Cached canonical plan-build launcher)
436# -----------------------------------------------------------------------------
437plan_build() {
438 emulate -L zsh
439 setopt localtraps
440 local base_url="${PLAN_BUILD_BASE_URL:-https://opengist.resetrix.work/weehong/plan-build/raw/HEAD}"
441 local cache_dir="${PLAN_BUILD_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/plan-build}"
442 local curl_command="${PLAN_BUILD_CURL_COMMAND:-curl}"
443 local skill_file="$HOME/.claude/skills/plan-build/SKILL.md"
444 local architect_file="$HOME/.claude/skills/plan-build-architect/SKILL.md"
445 local stage_dir="" release_dir="" current_tmp=""
446 local skill_tmp="" architect_tmp="" skill_backup="" architect_backup=""
447 local activation_lock="$cache_dir/.activate.lock"
448 local activation_lock_fd=""
449 local selected_release="" active_pid="" refresh_rc=1 call_rc=0 interrupted_rc=0
450
451 if ! zmodload zsh/system 2>/dev/null; then
452 printf "Error: Unable to load the Zsh system module required by plan-build.\n" >&2
453 return 1
454 fi
455
456 require_cli zsh "Zsh" || return 1
457
458 if ! command mkdir -p "$cache_dir" "${skill_file:h}" "${architect_file:h}"; then
459 printf "Error: Unable to create plan-build cache or skill directories.\n" >&2
460 return 1
461 fi
462
463 _plan_build_cleanup() {
464 if [[ -n "${active_pid:-}" ]] && command kill -0 "$active_pid" 2>/dev/null; then
465 command kill "$active_pid" 2>/dev/null || true
466 wait "$active_pid" 2>/dev/null || true
467 fi
468 active_pid=""
469 [[ -n "$stage_dir" && -d "$stage_dir" ]] && command rm -rf -- "$stage_dir"
470 command rm -f -- "$current_tmp" "$skill_tmp" "$architect_tmp"
471 command rm -f -- "$skill_backup" "$architect_backup"
472 if [[ -n "${activation_lock_fd:-}" ]]; then
473 zsystem flock -u "$activation_lock_fd" 2>/dev/null || true
474 activation_lock_fd=""
475 fi
476 }
477 _plan_build_cancel() {
478 interrupted_rc="$1"
479 [[ -n "${active_pid:-}" ]] && command kill "$active_pid" 2>/dev/null || true
480 }
481 trap '_plan_build_cleanup' EXIT
482 trap '_plan_build_cancel 129' HUP
483 trap '_plan_build_cancel 130' INT
484 trap '_plan_build_cancel 143' TERM
485
486 _plan_build_validate_release() {
487 local candidate="$1"
488 [[ -d "$candidate" &&
489 -s "$candidate/plan_build.zsh" &&
490 -s "$candidate/SKILL.md" &&
491 -s "$candidate/ARCHITECT.md" ]] &&
492 command zsh -n "$candidate/plan_build.zsh"
493 }
494
495 _plan_build_replace_path() {
496 local replacement="$1" destination="$2"
497 if command mv -fT "$replacement" "$destination" 2>/dev/null; then
498 return 0
499 fi
500 command mv -fh "$replacement" "$destination"
501 }
502
503 _plan_build_select_current() {
504 local resolved
505 [[ -L "$cache_dir/current" ]] || return 1
506 resolved="$cache_dir/current"
507 resolved="${resolved:A}"
508 _plan_build_validate_release "$resolved" || return 1
509 selected_release="$resolved"
510 }
511
512 _plan_build_acquire_activation_lock() {
513 (( interrupted_rc )) && return 1
514 command touch "$activation_lock" || return 1
515 zsystem flock -t 10 -f activation_lock_fd "$activation_lock"
516 }
517
518 _plan_build_release_activation_lock() {
519 [[ -n "$activation_lock_fd" ]] || return 0
520 zsystem flock -u "$activation_lock_fd" || return 1
521 activation_lock_fd=""
522 }
523
524 _plan_build_download() {
525 local remote="$1" destination="$2" download_rc
526 command "$curl_command" -fsSL "$remote" -o "$destination" &
527 active_pid=$!
528 wait "$active_pid"
529 download_rc=$?
530 active_pid=""
531 (( interrupted_rc )) && return "$interrupted_rc"
532 return "$download_rc"
533 }
534
535 _plan_build_backup_path() {
536 local source="$1" backup="$2"
537 [[ -e "$source" || -L "$source" ]] || return 0
538 command rm -f -- "$backup" && command cp -RP "$source" "$backup"
539 }
540
541 _plan_build_restore_path() {
542 local destination="$1" backup="$2" had_old="$3"
543 if (( had_old )); then
544 _plan_build_replace_path "$backup" "$destination" || return 1
545 else
546 command rm -f -- "$destination"
547 fi
548 }
549
550 _plan_build_install_skill_links() {
551 local activation_ok=1
552 local skill_had_old=0 architect_had_old=0
553
554 skill_tmp="$(command mktemp "${skill_file}.link.XXXXXX")" &&
555 command rm -f "$skill_tmp" &&
556 command ln -s "$cache_dir/current/SKILL.md" "$skill_tmp" || skill_tmp=""
557 architect_tmp="$(command mktemp "${architect_file}.link.XXXXXX")" &&
558 command rm -f "$architect_tmp" &&
559 command ln -s "$cache_dir/current/ARCHITECT.md" "$architect_tmp" || architect_tmp=""
560 [[ -n "$skill_tmp" && -L "$skill_tmp" &&
561 -n "$architect_tmp" && -L "$architect_tmp" ]] || return 1
562
563 skill_backup="${skill_file}.backup.$$.$RANDOM"
564 architect_backup="${architect_file}.backup.$$.$RANDOM"
565 [[ -e "$skill_file" || -L "$skill_file" ]] && skill_had_old=1
566 [[ -e "$architect_file" || -L "$architect_file" ]] && architect_had_old=1
567 _plan_build_backup_path "$skill_file" "$skill_backup" || activation_ok=0
568 (( activation_ok )) && _plan_build_backup_path "$architect_file" "$architect_backup" || activation_ok=0
569 (( activation_ok && ! interrupted_rc )) &&
570 _plan_build_replace_path "$skill_tmp" "$skill_file" || activation_ok=0
571 (( activation_ok )) && skill_tmp=""
572 (( activation_ok && ! interrupted_rc )) &&
573 _plan_build_replace_path "$architect_tmp" "$architect_file" || activation_ok=0
574 (( activation_ok )) && architect_tmp=""
575
576 if (( activation_ok )); then
577 command rm -f -- "$skill_backup" "$architect_backup"
578 skill_backup="" architect_backup=""
579 return 0
580 fi
581
582 if ! _plan_build_restore_path "$skill_file" "$skill_backup" "$skill_had_old"; then
583 printf "Error: Unable to restore plan-build skill; backup preserved at %s.\n" "$skill_backup" >&2
584 skill_backup=""
585 fi
586 if ! _plan_build_restore_path "$architect_file" "$architect_backup" "$architect_had_old"; then
587 printf "Error: Unable to restore architect skill; backup preserved at %s.\n" "$architect_backup" >&2
588 architect_backup=""
589 fi
590 return 1
591 }
592
593 if (( $+commands[$curl_command] )) || [[ "$curl_command" == */* && -x "$curl_command" ]]; then
594 stage_dir="$(command mktemp -d "$cache_dir/.stage.XXXXXX")" || stage_dir=""
595 if [[ -n "$stage_dir" ]]; then
596 _plan_build_download "$base_url/plan_build.zsh" "$stage_dir/plan_build.zsh" &&
597 _plan_build_download "$base_url/SKILL.md" "$stage_dir/SKILL.md" &&
598 _plan_build_download "$base_url/ARCHITECT.md" "$stage_dir/ARCHITECT.md"
599 refresh_rc=$?
600 if (( interrupted_rc )); then
601 _plan_build_cleanup
602 trap - EXIT HUP INT TERM
603 return "$interrupted_rc"
604 fi
605 else
606 refresh_rc=1
607 fi
608 if (( refresh_rc == 0 )) &&
609 _plan_build_validate_release "$stage_dir"; then
610 release_dir="$(command mktemp -d "$cache_dir/release.XXXXXX")" || release_dir=""
611 if [[ -n "$release_dir" ]] &&
612 command rmdir "$release_dir" &&
613 command mv "$stage_dir" "$release_dir" &&
614 _plan_build_acquire_activation_lock; then
615 stage_dir=""
616 release_dir="${release_dir:A}"
617 current_tmp="$(command mktemp "$cache_dir/.current.XXXXXX")" &&
618 command rm -f "$current_tmp" &&
619 command ln -s "$release_dir" "$current_tmp" || current_tmp=""
620
621 if [[ -n "$current_tmp" && -L "$current_tmp" ]] &&
622 _plan_build_install_skill_links &&
623 _plan_build_replace_path "$current_tmp" "$cache_dir/current"; then
624 current_tmp=""
625 selected_release="$release_dir"
626 fi
627 _plan_build_release_activation_lock || true
628 fi
629 fi
630 fi
631
632 if (( interrupted_rc )); then
633 _plan_build_cleanup
634 trap - EXIT HUP INT TERM
635 return "$interrupted_rc"
636 fi
637
638 if [[ -z "$selected_release" ]]; then
639 if ! _plan_build_select_current; then
640 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
641 printf "Error: curl is required because no valid cached plan-build bundle is available.\n" >&2
642 else
643 printf "Error: Unable to refresh plan-build and no valid cached bundle is available.\n" >&2
644 fi
645 return 1
646 fi
647 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
648 printf "Warning: curl is unavailable; using the validated cached plan-build bundle.\n" >&2
649 else
650 printf "Warning: Unable to refresh plan-build; using the validated cached bundle.\n" >&2
651 fi
652 fi
653
654 if [[ "$(command readlink "$skill_file" 2>/dev/null)" != "$cache_dir/current/SKILL.md" ||
655 "$(command readlink "$architect_file" 2>/dev/null)" != "$cache_dir/current/ARCHITECT.md" ]]; then
656 if ! _plan_build_acquire_activation_lock || ! _plan_build_install_skill_links; then
657 _plan_build_release_activation_lock 2>/dev/null || true
658 if (( interrupted_rc )); then
659 _plan_build_cleanup
660 trap - EXIT HUP INT TERM
661 return "$interrupted_rc"
662 fi
663 printf "Error: Unable to install the plan-build skill links.\n" >&2
664 return 1
665 fi
666 _plan_build_release_activation_lock || true
667 fi
668
669 if (( interrupted_rc )); then
670 _plan_build_cleanup
671 trap - EXIT HUP INT TERM
672 return "$interrupted_rc"
673 fi
674
675 PLAN_BUILD_SKILL_PATH="$selected_release/SKILL.md" \
676 PLAN_BUILD_ARCHITECT_SKILL_PATH="$selected_release/ARCHITECT.md" \
677 command zsh "$selected_release/plan_build.zsh" "$@" &
678 active_pid=$!
679 wait "$active_pid"
680 call_rc=$?
681 active_pid=""
682 (( interrupted_rc )) && call_rc="$interrupted_rc"
683 _plan_build_cleanup
684 trap - EXIT HUP INT TERM
685 return "$call_rc"
686}
687
688# -----------------------------------------------------------------------------
689# Function: aicommit (Automated Conventional Commit Engine Wrapper)
690# -----------------------------------------------------------------------------
691aicommit() {
692 local diff engine prompt
693
694 # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes
695 diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000)
696
697 if [ -z "$diff" ]; then
698 printf "❌ Error: Nothing staged to commit.\n" >&2
699 return 1
700 fi
701
702 if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then
703 printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2
704 printf "Please create it and paste your system prompt inside.\n" >&2
705 return 1
706 fi
707
708 engine=${1:-claude}
709 prompt=$(cat "$HOME/.config/ai-commit-prompt.txt")
710
711 case "$engine" in
712 claude)
713 echo "🤖 Claude is analyzing staged changes..."
714 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
715 | command claude --allowedTools 'Bash(git commit *)' --permission-mode dontAsk -p \
716 "Read the provided instructions and diff, then generate and execute the commit."
717 ;;
718
719 codex)
720 echo "🤖 Codex is analyzing staged changes..."
721 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
722 | command codex --ask-for-approval never exec --sandbox workspace-write --ephemeral \
723 "Read the provided instructions and diff, then generate and execute the commit."
724 ;;
725
726 copilot)
727 echo "🤖 Copilot is analyzing staged changes..."
728 copilot -p "$prompt
729
730Here is the git diff:
731$diff" -s --no-ask-user --allow-tool='shell(git:*)'
732 ;;
733
734 opencode)
735 echo "🤖 OpenCode is analyzing staged changes..."
736 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | opencode run --agent ai-commit
737 ;;
738
739 *)
740 printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2
741 printf "Usage: aicommit [claude | codex | copilot | opencode]\n" >&2
742 return 1
743 ;;
744 esac
745}
746
747# -----------------------------------------------------------------------------
748# Function: create (Efficiently create file + parent dirs)
749# -----------------------------------------------------------------------------
750create() {
751 if ! command -v install >/dev/null 2>&1; then
752 printf "Error: 'install' coreutil is not available.\n" >&2
753 return 1
754 fi
755
756 if [ $# -eq 0 ]; then
757 printf "Usage: create <file> [file ...]\n" >&2
758 return 2
759 fi
760
761 for p in "$@"; do
762 # Expand tilde manually if shell doesn't
763 [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}"
764
765 if [ -z "$p" ] || [ "$p" = "/" ]; then
766 printf "create: refusing to operate on '%s'\n" "$p" >&2
767 continue
768 fi
769
770 if [ -e "$p" ]; then
771 printf "create: '%s' already exists. Skipping.\n" "$p" >&2
772 continue
773 fi
774
775 if install -D /dev/null "$p"; then
776 echo "Created '$p'"
777 fi
778 done
779}
780
781# -----------------------------------------------------------------------------
782# Function: kill_port (Kill or check the process using a port)
783# -----------------------------------------------------------------------------
784kill_port() {
785 local action="kill"
786 local signal="TERM"
787 local port
788 local pids
789 local pid
790
791 case "${1:-}" in
792 -c|--check)
793 action="check"
794 shift
795 ;;
796 -k|--kill)
797 action="kill"
798 shift
799 ;;
800 -f|--force)
801 action="kill"
802 signal="KILL"
803 shift
804 ;;
805 -h|--help|"")
806 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
807 return 1
808 ;;
809 esac
810
811 port="${1:-}"
812 if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
813 printf "Error: port must be a number between 1 and 65535.\n" >&2
814 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
815 return 1
816 fi
817
818 if [[ "$OSTYPE" == "darwin"* ]]; then
819 pids="$(
820 {
821 lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null
822 lsof -nP -tiUDP:"$port" 2>/dev/null
823 } | sort -u
824 )"
825
826 if [ -z "$pids" ]; then
827 printf "Port %s is FREE\n" "$port"
828 return 0
829 fi
830
831 printf "Port %s is in use:\n" "$port"
832 lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null
833 else
834 local port_hex
835 local inodes
836
837 port_hex="$(printf "%04X" "$port")"
838 inodes="$(
839 awk -v port="$port_hex" '
840 NR > 1 {
841 split($2, local_addr, ":")
842 if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) {
843 print $10
844 }
845 }
846 ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u
847 )"
848
849 if [ -z "$inodes" ]; then
850 printf "Port %s is FREE\n" "$port"
851 return 0
852 fi
853
854 pids=""
855 pids="$(
856 find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null |
857 awk -v inodes="$inodes" '
858 BEGIN {
859 split(inodes, inode_list, " ")
860 for (i in inode_list) {
861 wanted["socket:[" inode_list[i] "]"] = 1
862 }
863 }
864 $2 in wanted {
865 split($1, path, "/")
866 print path[3]
867 }
868 ' | sort -u
869 )"
870
871 if [ -z "$pids" ]; then
872 printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2
873 return 1
874 fi
875
876 printf "Port %s is in use:\n" "$port"
877 for pid in $pids; do
878 if [ -r "/proc/$pid/cmdline" ]; then
879 printf " PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")"
880 else
881 printf " PID %s\n" "$pid"
882 fi
883 done
884 fi
885
886 if [ "$action" = "kill" ]; then
887 for pid in $pids; do
888 if kill "-$signal" "$pid" 2>/dev/null; then
889 printf "Killed PID %s with SIG%s.\n" "$pid" "$signal"
890 else
891 printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2
892 return 1
893 fi
894 done
895 fi
896}
897
898# -----------------------------------------------------------------------------
899# Function: append_path (Adds to session PATH if exists)
900# -----------------------------------------------------------------------------
901append_path() {
902 local dir="$1"
903 if [ -d "$dir" ]; then
904 case ":$PATH:" in
905 *":$dir:"*) ;;
906 *) PATH="$PATH:$dir" ;;
907 esac
908 elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then
909 echo "Warning: $dir does not exist." >&2
910 fi
911}
912
913# -----------------------------------------------------------------------------
914# Function: add_path_to_config (Fixed awk/sed injection)
915# -----------------------------------------------------------------------------
916add_path_to_config() {
917 local new_path="$1"
918 local config_file="$HOME/.bashrc"
919 [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc"
920
921 if [ -z "$new_path" ]; then
922 printf "Usage: add_path_to_config <directory>\n" >&2
923 return 1
924 fi
925
926 new_path="${new_path/#\~/$HOME}"
927
928 if [ ! -d "$new_path" ]; then
929 printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path"
930 read -r REPLY
931 [[ ! $REPLY =~ ^[Yy]$ ]] && return 1
932 fi
933
934 # Check if already exists in file
935 if grep -Fq "append_path \"$new_path\"" "$config_file"; then
936 printf "Notice: Already in %s.\n" "$config_file"
937 return 0
938 fi
939
940 # Robust Injection: Use sed to insert before export PATH or just append
941 if grep -q "^export PATH" "$config_file"; then
942 sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file"
943 else
944 echo "append_path \"$new_path\"" >> "$config_file"
945 echo "export PATH" >> "$config_file"
946 fi
947
948 printf "✅ Success: Added to %s\n" "$config_file"
949 append_path "$new_path" && export PATH
950}
951
pathrc Raw
1# =============================================================================
2# ENVIRONMENT & PATH CONFIGURATION
3# =============================================================================
4
5# 1. Source functions first
6if [[ -f "$HOME/.func" && -r "$HOME/.func" ]]; then
7 source "$HOME/.func"
8fi
9
10# 2. Define Root Variables
11export DOTNET_ROOT="$HOME/.dotnet"
12
13# 3. PATH INITIALIZATION
14# -----------------------------------------------------------------------------
15append_path "$HOME/.local/bin"
16append_path "$DOTNET_ROOT"
17append_path "$DOTNET_ROOT/tools"
18append_path "$HOME/.opencode/bin"
19append_path "$HOME/Flutter/bin"
20
21# macOS specific paths
22if [[ "$OSTYPE" == "darwin"* ]]; then
23 append_path "/Applications/Espanso.app/Contents/MacOS"
24fi
25
26# Finalize PATH
27export PATH
sourcerc Raw
1# =============================================================================
2# FILE: ~/.sourcerc
3# Description: Initializes third-party package managers and external tools.
4# =============================================================================
5
6# =============================================================================
7# GENERAL ENV
8# =============================================================================
9if [[ -f "$HOME/.local/bin/env" && -r "$HOME/.local/bin/env" ]]; then
10 source "$HOME/.local/bin/env"
11fi
12
13# =============================================================================
14# CLAUDE CODE / AI GATEWAY
15# =============================================================================
16
17# =============================================================================
18# SDKMAN (Java/Kotlin/Scala Version Manager)
19# =============================================================================
20export SDKMAN_DIR="$HOME/.sdkman"
21if [[ -f "$SDKMAN_DIR/bin/sdkman-init.sh" && -r "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then
22 source "$SDKMAN_DIR/bin/sdkman-init.sh"
23fi
24
25# =============================================================================
26# NVM (Node Version Manager)
27# =============================================================================
28export NVM_DIR="$HOME/.nvm"
29if [[ -f "$NVM_DIR/nvm.sh" && -r "$NVM_DIR/nvm.sh" ]]; then
30 source "$NVM_DIR/nvm.sh"
31fi
32if [[ -f "$NVM_DIR/bash_completion" && -r "$NVM_DIR/bash_completion" ]]; then
33 source "$NVM_DIR/bash_completion"
34fi
35
36# =============================================================================
37# GOOGLE CLOUD SDK
38# =============================================================================
39if [[ -f "/opt/google-cloud-sdk/path.zsh.inc" ]]; then
40 source "/opt/google-cloud-sdk/path.zsh.inc"
41fi
42
43if [[ -f "/opt/google-cloud-sdk/completion.zsh.inc" ]]; then
44 source "/opt/google-cloud-sdk/completion.zsh.inc"
45fi
46
starship.toml Raw
1"$schema" = 'https://starship.rs/config-schema.json'
2
3format = """
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
32palette = 'catppuccin_mocha'
33
34[os]
35disabled = false
36style = "bg:red fg:crust"
37
38[os.symbols]
39Windows = ""
40Ubuntu = "󰕈"
41SUSE = ""
42Raspbian = "󰐿"
43Mint = "󰣭"
44Macos = "󰀵"
45Manjaro = ""
46Linux = "󰌽"
47Gentoo = "󰣨"
48Fedora = "󰣛"
49Alpine = ""
50Amazon = ""
51Android = ""
52AOSC = ""
53Arch = "󰣇"
54Artix = "󰣇"
55CentOS = ""
56Debian = "󰣚"
57Redhat = "󱄛"
58RedHatEnterprise = "󱄛"
59
60[username]
61show_always = true
62style_user = "bg:red fg:crust"
63style_root = "bg:red fg:crust"
64format = '[ $user]($style)'
65
66[directory]
67style = "bg:peach fg:crust"
68format = "[ $path ]($style)"
69truncation_length = 3
70truncation_symbol = "…/"
71
72[directory.substitutions]
73"Documents" = "󰈙 "
74"Downloads" = " "
75"Music" = "󰝚 "
76"Pictures" = " "
77"Developer" = "󰲋 "
78
79[git_branch]
80symbol = ""
81style = "bg:yellow"
82format = '[[ $symbol $branch ](fg:crust bg:yellow)]($style)'
83
84[git_status]
85style = "bg:yellow"
86format = '[[($all_status$ahead_behind )](fg:crust bg:yellow)]($style)'
87
88[nodejs]
89symbol = ""
90style = "bg:green"
91format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
92
93[bun]
94symbol = ""
95style = "bg:green"
96format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
97
98[c]
99symbol = " "
100style = "bg:green"
101format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
102
103[rust]
104symbol = ""
105style = "bg:green"
106format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
107
108[golang]
109symbol = ""
110style = "bg:green"
111format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
112
113[php]
114symbol = ""
115style = "bg:green"
116format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
117
118[java]
119symbol = " "
120style = "bg:green"
121format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
122
123[kotlin]
124symbol = ""
125style = "bg:green"
126format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
127
128[haskell]
129symbol = ""
130style = "bg:green"
131format = '[[ $symbol( $version) ](fg:crust bg:green)]($style)'
132
133[python]
134symbol = ""
135style = "bg:green"
136format = '[[ $symbol( $version)(\(#$virtualenv\)) ](fg:crust bg:green)]($style)'
137
138[docker_context]
139symbol = ""
140style = "bg:sapphire"
141format = '[[ $symbol( $context) ](fg:crust bg:sapphire)]($style)'
142
143[conda]
144symbol = "  "
145style = "fg:crust bg:sapphire"
146format = '[$symbol$environment ]($style)'
147ignore_base = false
148
149[time]
150disabled = false
151time_format = "%R"
152style = "bg:lavender"
153format = '[[  $time ](fg:crust bg:lavender)]($style)'
154
155[line_break]
156disabled = false
157
158[character]
159disabled = false
160success_symbol = '[❯](bold fg:green)'
161error_symbol = '[❯](bold fg:red)'
162vimcmd_symbol = '[❮](bold fg:green)'
163vimcmd_replace_one_symbol = '[❮](bold fg:lavender)'
164vimcmd_replace_symbol = '[❮](bold fg:lavender)'
165vimcmd_visual_symbol = '[❮](bold fg:yellow)'
166
167[cmd_duration]
168show_milliseconds = true
169format = " in $duration "
170style = "bg:lavender"
171disabled = false
172show_notifications = true
173min_time_to_notify = 45000
174
175[palettes.catppuccin_mocha]
176rosewater = "#f5e0dc"
177flamingo = "#f2cdcd"
178pink = "#f5c2e7"
179mauve = "#cba6f7"
180red = "#f38ba8"
181maroon = "#eba0ac"
182peach = "#fab387"
183yellow = "#f9e2af"
184green = "#a6e3a1"
185teal = "#94e2d5"
186sky = "#89dceb"
187sapphire = "#74c7ec"
188blue = "#89b4fa"
189lavender = "#b4befe"
190text = "#cdd6f4"
191subtext1 = "#bac2de"
192subtext0 = "#a6adc8"
193overlay2 = "#9399b2"
194overlay1 = "#7f849c"
195overlay0 = "#6c7086"
196surface2 = "#585b70"
197surface1 = "#45475a"
198surface0 = "#313244"
199base = "#1e1e2e"
200mantle = "#181825"
201crust = "#11111b"
202
203[palettes.catppuccin_frappe]
204rosewater = "#f2d5cf"
205flamingo = "#eebebe"
206pink = "#f4b8e4"
207mauve = "#ca9ee6"
208red = "#e78284"
209maroon = "#ea999c"
210peach = "#ef9f76"
211yellow = "#e5c890"
212green = "#a6d189"
213teal = "#81c8be"
214sky = "#99d1db"
215sapphire = "#85c1dc"
216blue = "#8caaee"
217lavender = "#babbf1"
218text = "#c6d0f5"
219subtext1 = "#b5bfe2"
220subtext0 = "#a5adce"
221overlay2 = "#949cbb"
222overlay1 = "#838ba7"
223overlay0 = "#737994"
224surface2 = "#626880"
225surface1 = "#51576d"
226surface0 = "#414559"
227base = "#303446"
228mantle = "#292c3c"
229crust = "#232634"
230
231[palettes.catppuccin_latte]
232rosewater = "#dc8a78"
233flamingo = "#dd7878"
234pink = "#ea76cb"
235mauve = "#8839ef"
236red = "#d20f39"
237maroon = "#e64553"
238peach = "#fe640b"
239yellow = "#df8e1d"
240green = "#40a02b"
241teal = "#179299"
242sky = "#04a5e5"
243sapphire = "#209fb5"
244blue = "#1e66f5"
245lavender = "#7287fd"
246text = "#4c4f69"
247subtext1 = "#5c5f77"
248subtext0 = "#6c6f85"
249overlay2 = "#7c7f93"
250overlay1 = "#8c8fa1"
251overlay0 = "#9ca0b0"
252surface2 = "#acb0be"
253surface1 = "#bcc0cc"
254surface0 = "#ccd0da"
255base = "#eff1f5"
256mantle = "#e6e9ef"
257crust = "#dce0e8"
258
259[palettes.catppuccin_macchiato]
260rosewater = "#f4dbd6"
261flamingo = "#f0c6c6"
262pink = "#f5bde6"
263mauve = "#c6a0f6"
264red = "#ed8796"
265maroon = "#ee99a0"
266peach = "#f5a97f"
267yellow = "#eed49f"
268green = "#a6da95"
269teal = "#8bd5ca"
270sky = "#91d7e3"
271sapphire = "#7dc4e4"
272blue = "#8aadf4"
273lavender = "#b7bdf8"
274text = "#cad3f5"
275subtext1 = "#b8c0e0"
276subtext0 = "#a5adcb"
277overlay2 = "#939ab7"
278overlay1 = "#8087a2"
279overlay0 = "#6e738d"
280surface2 = "#5b6078"
281surface1 = "#494d64"
282surface0 = "#363a4f"
283base = "#24273a"
284mantle = "#1e2030"
285crust = "#181926"
286
test_plan_build.zsh Raw
1#!/usr/bin/env zsh
2
3set -u
4
5repo_dir="${0:A:h}"
6source "$repo_dir/func"
7
8failures=0
9fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan build launcher.XXXXXX")" || exit 1
10source_dir="$(mktemp -d "${TMPDIR:-/tmp}/plan-build-source.XXXXXX")" || exit 1
11export PLAN_BUILD_CACHE_DIR="$fixture_dir/cache with spaces"
12export PLAN_BUILD_BASE_URL="file://$source_dir"
13export HOME="$fixture_dir/home with spaces"
14PLAN_BUILD_SKILLS_DIR="$HOME/.claude/skills"
15mkdir -p "$source_dir" "$PLAN_BUILD_SKILLS_DIR/plan-build" "$PLAN_BUILD_SKILLS_DIR/plan-build-architect"
16print -r -- "old standard" > "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md"
17print -r -- "old architect" > "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md"
18trap 'rm -rf -- "$fixture_dir" "$source_dir"' EXIT
19
20fail() {
21 print -u2 -- "FAIL: $1"
22 failures=$((failures + 1))
23}
24
25assert_equal() {
26 [[ "$1" = "$2" ]] || fail "$3 (expected '$1', got '$2')"
27}
28
29assert_contains() {
30 [[ "$1" == *"$2"* ]] || fail "$3 (missing '$2')"
31}
32
33assert_file_contains() {
34 command grep -Fq -- "$2" "$1" || fail "$3"
35}
36
37assert_bundle_consistent() {
38 local release="$PLAN_BUILD_CACHE_DIR/current"
39 local script_generation skill_generation architect_generation
40 release="${release:A}"
41 [[ -L "$PLAN_BUILD_CACHE_DIR/current" ]] || fail "$1 current is a symlink"
42 [[ -d "$release" ]] || fail "$1 current resolves to a release"
43 script_generation="$(command sed -n 's/^# generation: //p' "$release/plan_build.zsh")"
44 skill_generation="$(command sed -n 's/ standard skill$//p' "$release/SKILL.md")"
45 architect_generation="$(command sed -n 's/ architect skill$//p' "$release/ARCHITECT.md")"
46 assert_equal "$script_generation" "$skill_generation" "$1 script/standard generation"
47 assert_equal "$script_generation" "$architect_generation" "$1 script/architect generation"
48}
49
50assert_skill_links() {
51 [[ -L "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" ]] || fail "$1 standard path is symlink"
52 [[ -L "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md" ]] || fail "$1 architect path is symlink"
53 assert_equal "$PLAN_BUILD_CACHE_DIR/current/SKILL.md" \
54 "$(command readlink "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md")" "$1 standard link target"
55 assert_equal "$PLAN_BUILD_CACHE_DIR/current/ARCHITECT.md" \
56 "$(command readlink "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md")" "$1 architect link target"
57}
58
59write_bundle() {
60 local generation="$1"
61 local exit_code="${2:-0}"
62 local marker_file="${3:-}"
63 print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
64 print -r -- "# generation: $generation" >> "$source_dir/plan_build.zsh"
65 print -r -- "print -r -- \"$generation:\${(j:|:)@}\"" >> "$source_dir/plan_build.zsh"
66 print -r -- "print -r -- \"path:\${0}\"" >> "$source_dir/plan_build.zsh"
67 print -r -- 'print -r -- "skills:${PLAN_BUILD_SKILL_PATH}|${PLAN_BUILD_ARCHITECT_SKILL_PATH}"' >> "$source_dir/plan_build.zsh"
68 [[ -n "$marker_file" ]] && print -r -- ": > ${(q)marker_file}" >> "$source_dir/plan_build.zsh"
69 print -r -- "exit $exit_code" >> "$source_dir/plan_build.zsh"
70 print -r -- "$generation standard skill" > "$source_dir/SKILL.md"
71 print -r -- "$generation architect skill" > "$source_dir/ARCHITECT.md"
72}
73
74run_plan_build() {
75 output="$(plan_build "$@" 2>&1)"
76 rc=$?
77}
78
79write_bundle initial
80run_plan_build alpha "two words"
81assert_equal 0 "$rc" "initial migration status"
82assert_contains "$output" "initial:alpha|two words" "argument forwarding"
83assert_skill_links "initial migration"
84assert_bundle_consistent "initial migration"
85assert_file_contains "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" "initial standard" "initial standard content"
86assert_file_contains "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md" "initial architect" "initial architect content"
87execution_path="${${(f)output}[(r)path:*]#path:}"
88[[ "$execution_path" == "$PLAN_BUILD_CACHE_DIR"/release.*/plan_build.zsh ]] ||
89 fail "execution uses an immutable release path"
90assert_contains "$output" "skills:${execution_path:h}/SKILL.md|${execution_path:h}/ARCHITECT.md" \
91 "execution binds immutable skill paths"
92
93old_release="$PLAN_BUILD_CACHE_DIR/current"
94old_release="${old_release:A}"
95write_bundle replacement 23
96run_plan_build forwarded
97assert_equal 23 "$rc" "canonical status forwarding"
98assert_contains "$output" "replacement:forwarded" "replacement executes"
99new_release="$PLAN_BUILD_CACHE_DIR/current"
100new_release="${new_release:A}"
101[[ "$new_release" != "$old_release" ]] || fail "refresh activates a unique release"
102assert_skill_links "replacement"
103assert_bundle_consistent "replacement"
104
105print -r -- 'if broken' > "$source_dir/plan_build.zsh"
106print -r -- "bad standard skill" > "$source_dir/SKILL.md"
107print -r -- "bad architect skill" > "$source_dir/ARCHITECT.md"
108run_plan_build fallback
109assert_equal 23 "$rc" "failed refresh fallback status"
110assert_contains "$output" "Warning: Unable to refresh" "failed refresh warning"
111assert_contains "$output" "replacement:fallback" "failed refresh executes current"
112preserved_release="$PLAN_BUILD_CACHE_DIR/current"
113preserved_release="${preserved_release:A}"
114assert_equal "$new_release" "$preserved_release" "failed refresh preserves current"
115assert_skill_links "failed refresh"
116assert_bundle_consistent "failed refresh"
117
118export PLAN_BUILD_CURL_COMMAND="definitely-missing-plan-build-curl"
119run_plan_build no-curl
120assert_equal 23 "$rc" "missing curl fallback status"
121assert_contains "$output" "curl is unavailable" "missing curl warning"
122assert_contains "$output" "replacement:no-curl" "missing curl executes current"
123rm -f "$PLAN_BUILD_SKILLS_DIR/plan-build/SKILL.md" "$PLAN_BUILD_SKILLS_DIR/plan-build-architect/SKILL.md"
124run_plan_build repaired-offline
125assert_equal 23 "$rc" "offline skill repair status"
126assert_skill_links "offline skill repair"
127unset PLAN_BUILD_CURL_COMMAND
128
129# An already selected immutable script must not change beneath a concurrent caller.
130ready_file="$fixture_dir/ready"
131go_file="$fixture_dir/go"
132first_output="$fixture_dir/first.out"
133print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
134print -r -- '# generation: concurrent_first' >> "$source_dir/plan_build.zsh"
135print -r -- 'print -r -- "concurrent_first:${(j:|:)@}"' >> "$source_dir/plan_build.zsh"
136print -r -- ": > ${(q)ready_file}" >> "$source_dir/plan_build.zsh"
137print -r -- "while [[ ! -e ${(q)go_file} ]]; do sleep 0.01; done" >> "$source_dir/plan_build.zsh"
138print -r -- 'print -r -- "finished:first"' >> "$source_dir/plan_build.zsh"
139print -r -- "concurrent_first standard skill" > "$source_dir/SKILL.md"
140print -r -- "concurrent_first architect skill" > "$source_dir/ARCHITECT.md"
141(plan_build one > "$first_output" 2>&1) &
142first_pid=$!
143for attempt in {1..500}; do
144 [[ -e "$ready_file" ]] && break
145 sleep 0.01
146done
147[[ -e "$ready_file" ]] || fail "first concurrent call reached canonical execution"
148write_bundle concurrent_second
149run_plan_build two
150assert_equal 0 "$rc" "second concurrent status"
151assert_contains "$output" "concurrent_second:two" "second concurrent generation"
152: > "$go_file"
153wait "$first_pid" || fail "first concurrent status"
154first_text="$(<"$first_output")"
155assert_contains "$first_text" "concurrent_first:one" "first call retains selected release"
156assert_contains "$first_text" "finished:first" "first immutable script finishes"
157assert_bundle_consistent "concurrent final current"
158assert_skill_links "concurrent final links"
159
160# Cancellation while refreshing must clean staging and never execute canonical code.
161blocking_curl="$fixture_dir/blocking-curl"
162cancel_started="$fixture_dir/cancel.started"
163cancel_executed="$fixture_dir/cancel.executed"
164write_bundle cancellation-marker 0 "$cancel_executed"
165run_plan_build activate-cancellation-marker
166assert_equal 0 "$rc" "cancellation marker bundle activation"
167rm -f "$cancel_executed"
168{
169 print -r -- '#!/bin/sh'
170 print -r -- "touch ${(q)cancel_started}"
171 print -r -- 'while :; do sleep 1; done'
172} > "$blocking_curl"
173chmod +x "$blocking_curl"
174export PLAN_BUILD_CURL_COMMAND="$blocking_curl"
175(plan_build cancelled > "$fixture_dir/cancel.out" 2>&1; cancel_rc=$?; print -r -- "$cancel_rc" > "$fixture_dir/cancel.rc"; exit "$cancel_rc") &
176cancel_pid=$!
177for attempt in {1..500}; do
178 [[ -e "$cancel_started" ]] && break
179 sleep 0.01
180done
181kill -TERM "$cancel_pid"
182wait "$cancel_pid"
183cancel_wait_rc=$?
184cancel_recorded_rc=""
185[[ -s "$fixture_dir/cancel.rc" ]] && cancel_recorded_rc="$(<"$fixture_dir/cancel.rc")"
186[[ "$cancel_wait_rc" = 143 || "$cancel_recorded_rc" = 143 ]] ||
187 fail "TERM returns conventional status 143"
188[[ ! -e "$cancel_executed" ]] || fail "TERM does not execute canonical plan-build"
189unset PLAN_BUILD_CURL_COMMAND
190
191# A pre-existing lock file must not be mistaken for an active kernel lock.
192print -r -- "stale contents" > "$PLAN_BUILD_CACHE_DIR/.activate.lock"
193write_bundle stale-lock-recovery
194run_plan_build recovered
195assert_equal 0 "$rc" "stale activation lock recovery status"
196assert_contains "$output" "stale-lock-recovery:recovered" "stale activation lock recovery execution"
197
198# Cancellation while canonical code runs must terminate the child promptly.
199canonical_started="$fixture_dir/canonical.started"
200canonical_finished="$fixture_dir/canonical.finished"
201print -r -- '#!/usr/bin/env zsh' > "$source_dir/plan_build.zsh"
202print -r -- '# generation: canonical-cancel' >> "$source_dir/plan_build.zsh"
203print -r -- "trap 'exit 143' TERM" >> "$source_dir/plan_build.zsh"
204print -r -- ": > ${(q)canonical_started}" >> "$source_dir/plan_build.zsh"
205print -r -- 'while :; do sleep 1; done' >> "$source_dir/plan_build.zsh"
206print -r -- ": > ${(q)canonical_finished}" >> "$source_dir/plan_build.zsh"
207print -r -- "canonical-cancel standard skill" > "$source_dir/SKILL.md"
208print -r -- "canonical-cancel architect skill" > "$source_dir/ARCHITECT.md"
209(plan_build canonical-cancel > "$fixture_dir/canonical-cancel.out" 2>&1; print -r -- "$?" > "$fixture_dir/canonical-cancel.rc") &
210canonical_pid=$!
211for attempt in {1..500}; do
212 [[ -e "$canonical_started" ]] && break
213 sleep 0.01
214done
215[[ -e "$canonical_started" ]] || fail "canonical cancellation reached execution"
216kill -TERM "$canonical_pid"
217wait "$canonical_pid"
218assert_equal 143 "$(<"$fixture_dir/canonical-cancel.rc")" "canonical cancellation status"
219[[ ! -e "$canonical_finished" ]] || fail "canonical child continued after TERM"
220
221temp_count="$(command find "$PLAN_BUILD_CACHE_DIR" -maxdepth 1 \
222 \( -name '.stage.*' -o -name '.current.*' \) -print | command wc -l | command tr -d ' ')"
223assert_equal 0 "$temp_count" "cache staging and temporary links cleaned"
224skill_temp_count="$(command find "$PLAN_BUILD_SKILLS_DIR" \
225 \( -name '*.link.*' -o -name '*.backup.*' \) -print | command wc -l | command tr -d ' ')"
226assert_equal 0 "$skill_temp_count" "skill temporary links and backups cleaned"
227
228if (( failures )); then
229 exit 1
230fi
231
232print -- "PASS: plan_build launcher tests"
233
vimrc Raw
1" Enable line numbers
2set number
3
4" Enable relative line numbers
5set relativenumber
6
7" Enable syntax highlighting
8syntax on
9
10" Set colorscheme
11colorscheme slate
12
13" Enable file type detection and plugins
14filetype plugin indent on
15
16" Set the tab width to 4 spaces
17set tabstop=4
18set shiftwidth=4
19set expandtab
20
21" Enable auto-indentation
22set autoindent
23set smartindent
24
25" Highlight current line
26set cursorline
27
28" Show matching parentheses
29set showmatch
30
31" Enable line wrapping
32set wrap
33
34" Enable mouse support
35set mouse=a
36
37" Use Wayland's clipboard tools when this Vim lacks native clipboard support
38if !has('clipboard') && exists('v:clipproviders')
39 \ && executable('wl-copy') && executable('wl-paste')
40 function! s:WaylandCopy(register, type, lines) abort
41 let l:text = join(a:lines, "\n")
42 if a:type ==# 'V'
43 let l:text ..= "\n"
44 endif
45 call system('wl-copy', l:text)
46 endfunction
47
48 function! s:WaylandPaste(register) abort
49 return ['', systemlist('wl-paste --no-newline')]
50 endfunction
51
52 let v:clipproviders['wltools'] = {
53 \ 'copy': {
54 \ '+': function('s:WaylandCopy'),
55 \ '*': function('s:WaylandCopy')
56 \ },
57 \ 'paste': {
58 \ '+': function('s:WaylandPaste'),
59 \ '*': function('s:WaylandPaste')
60 \ }
61 \ }
62 set clipmethod^=wltools
63endif
64set clipboard=unnamedplus
65
66" Disable swap file
67set noswapfile
68
69" Enable incremental search
70set incsearch
71
72" Ignore case in search
73set ignorecase
74
75" Override ignorecase if search contains capital letters
76set smartcase
77
78" Display line and column number of the cursor position
79set ruler
80
81" Set the status line at the bottom
82set laststatus=2
83
84" Show command in bottom bar
85set showcmd
86
87" Set command height
88set cmdheight=2
89
90" Set history lines
91set history=1000
92
93" Disable backup file
94set nobackup
95
96" Enable persistent undo
97set undofile
98
99" Set maximum number of undo levels
100set undolevels=1000
101
102" Set undo directory
103if has("persistent_undo")
104 silent !mkdir ~/.vim/undodir > /dev/null 2>&1
105 set undodir=~/.vim/undodir
106endif
107
108" Set search highlighting
109set hlsearch
110
111" Enable visual bell
112set visualbell
113
114" Set default file encoding
115set encoding=utf-8
116
117" Set the leader key to space
118let mapleader = " "
119
120" Map <Leader>w to save the file
121nnoremap <Leader>w :w<CR>
122
123" Map <Leader>q to quit
124nnoremap <Leader>q :q<CR>
125
126" Map <Leader>x to save and quit
127nnoremap <Leader>x :wq<CR>
128
129" Enable folding
130set foldmethod=syntax
131set foldlevelstart=99
132
133" Enable line wrapping at 80 characters
134set textwidth=80
135set colorcolumn=80
136
137" Add some basic key mappings
138" Map jj to escape insert mode
139inoremap jj <Esc>
140
141" Map <Leader>n to toggle line numbers
142nnoremap <Leader>n :set number!<CR>
143
144" Map <Leader>r to toggle relative line numbers
145nnoremap <Leader>r :set relativenumber!<CR>
146
147" Configure plugins (if you use a plugin manager like vim-plug)
148" Example with vim-plug:
149" call plug#begin('~/.vim/plugged')
150" Plug 'tpope/vim-sensible'
151" Plug 'preservim/nerdtree'
152" Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
153" Plug 'airblade/vim-gitgutter'
154" call plug#end()
155
156" NERDTree key mappings
157" nnoremap <C-n> :NERDTreeToggle<CR>
158
159" Enable automatic hard wrapping at textwidth (80 chars)
160set formatoptions+=t " Auto-wrap text using textwidth
161set formatoptions+=c " Auto-wrap comments using textwidth
162set formatoptions+=r " Continue comments when pressing Enter
163set formatoptions+=o " Continue comments when using 'o' or 'O'
164set formatoptions+=q " Allow formatting of comments with 'gq'
165set formatoptions+=n " Recognize numbered lists
166set formatoptions+=l " Don't break lines that were already long
167
168" Enable syntax highlighting
169syntax on
170
171" Increase memory limit for complex syntax parsing (Prevents E363)
172set maxmempattern=20000
173
zsh_macos.sh Raw
1#!/bin/bash
2set -euo pipefail
3
4# =============================
5# COLORS & LOGGING
6# =============================
7RED='\033[0;31m'
8GREEN='\033[0;32m'
9YELLOW='\033[1;33m'
10BLUE='\033[0;34m'
11NC='\033[0m'
12
13log() { echo -e "${BLUE}[INFO]${NC} $*"; }
14ok() { echo -e "${GREEN}[OK]${NC} $*"; }
15warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
16err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
17
18# =============================
19# CONFIG
20# =============================
21GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD"
22
23CONFIG_FILES=(
24 ".alias"
25 ".func"
26 ".pathrc"
27 ".sourcerc"
28 ".vimrc"
29 ".zshrc"
30 ".config/starship.toml"
31 ".config/opencode/agents/ai-commit.md"
32)
33
34# =============================
35# REQUIREMENTS
36# =============================
37check_requirements() {
38 if [[ $EUID -eq 0 ]]; then
39 err "Do not run as root on macOS"
40 exit 1
41 fi
42}
43
44# =============================
45# INSTALLATION FUNCTIONS
46# =============================
47install_oh_my_zsh() {
48 log "Installing Oh My Zsh and plugins..."
49 export RUNZSH=no
50 export CHSH=no
51 export KEEP_ZSHRC=yes
52
53 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
54 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
55 else
56 ok "Oh My Zsh already installed"
57 fi
58
59 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
60 local plugin_dir="$custom/plugins"
61 mkdir -p "$plugin_dir"
62
63 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || \
64 git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
65
66 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || \
67 git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
68
69 ok "Oh My Zsh plugins installed to $plugin_dir"
70}
71
72install_starship() {
73 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
74 ok "Starship already installed"
75 elif command -v brew >/dev/null 2>&1; then
76 log "Installing Starship with Homebrew..."
77 brew install starship
78 ok "Starship installed"
79 else
80 log "Installing Starship..."
81 mkdir -p "$HOME/.local/bin"
82 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
83 ok "Starship installed"
84 fi
85}
86
87download_configs() {
88 log "Downloading custom config files..."
89 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
90 mkdir -p "$backup"
91
92 for f in "${CONFIG_FILES[@]}"; do
93 # Strip the leading dot for the download URL
94 local remote_file="${f#.}"
95 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
96 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
97 local url="$GIST_RAW_BASE/$remote_file"
98 local target="$HOME/$f"
99 local tmp="${target}.tmp.$$"
100
101 mkdir -p "$(dirname "$target")"
102
103 if [[ -f "$target" ]]; then
104 cp "$target" "$backup/"
105 fi
106
107 log "Fetching $remote_file -> $f ..."
108 if curl -fsSL "$url" -o "$tmp"; then
109 mv "$tmp" "$target"
110 else
111 rm -f "$tmp"
112 warn "Failed to download $remote_file"
113 fi
114 done
115
116 ok "Configs downloaded (Backup at $backup)"
117}
118
119configure_zshrc() {
120 local zshrc="$HOME/.zshrc"
121 log "Configuring .zshrc for Oh My Zsh and Starship..."
122
123 touch "$zshrc"
124 cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)"
125
126 cat > "$zshrc" <<'EOF'
127# =============================================================================
128# 1. HELPER FUNCTIONS & PATH
129# =============================================================================
130source_if_readable() {
131 local file="$1"
132 if [[ -f "$file" && -r "$file" ]]; then
133 source "$file"
134 fi
135}
136
137export PATH="$HOME/.local/bin:$PATH"
138
139# =============================================================================
140# 2. OH MY ZSH FRAMEWORK
141# =============================================================================
142export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
143ZSH_THEME=""
144
145plugins=(
146 git
147 zsh-autosuggestions
148 zsh-syntax-highlighting
149)
150
151source_if_readable "$ZSH/oh-my-zsh.sh"
152
153# =============================================================================
154# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
155# =============================================================================
156source_if_readable "$HOME/.sourcerc"
157source_if_readable "$HOME/.func"
158source_if_readable "$HOME/.pathrc"
159source_if_readable "$HOME/.alias"
160
161# =============================================================================
162# 4. STARSHIP PROMPT
163# =============================================================================
164if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
165 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
166fi
167
168if command -v starship >/dev/null 2>&1; then
169 eval "$(starship init zsh)"
170elif [[ -x "$HOME/.local/bin/starship" ]]; then
171 eval "$("$HOME/.local/bin/starship" init zsh)"
172fi
173EOF
174
175 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
176}
177
178configure_git_identity() {
179 log "Configuring global Git identity..."
180 git config --global user.name "Vernon Wee Hong KOH"
181 git config --global user.email "261549452+weehong@users.noreply.github.com"
182 ok "Global Git identity configured"
183}
184
185switch_shell() {
186 log "Starting Zsh session..."
187 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
188 echo "----------------------------------------"
189 zsh -l
190 echo "----------------------------------------"
191 ok "Returned from Zsh session"
192}
193
194# =============================
195# INTERACTIVE MENU
196# =============================
197show_menu() {
198 echo "==========================================="
199 echo "macOS Minimal Zsh Setup - Choose what to do"
200 echo "==========================================="
201 echo " 0) Run ALL steps (1-6)"
202 echo " 1) Install Oh My Zsh + plugins"
203 echo " 2) Install Starship prompt"
204 echo " 3) Download custom configs (~/.alias, .func, .vimrc, etc.)"
205 echo " 4) Configure ~/.zshrc (Oh My Zsh + Starship)"
206 echo " 5) Configure global Git identity"
207 echo " 6) Switch to Zsh (Temporary Sub-shell)"
208 echo " 7) Quit"
209 echo "==========================================="
210}
211
212run_choices() {
213 local input
214 read -p "Select: " input
215 input="${input//,/ }"
216
217 local -a to_run=()
218 local -a to_exclude=()
219
220 for item in $input; do
221 if [[ "$item" == !* ]]; then
222 to_exclude+=("${item:1}")
223 elif [[ "$item" == "0" ]]; then
224 to_run+=(1 2 3 4 5 6)
225 else
226 to_run+=("$item")
227 fi
228 done
229
230 if [[ ${#to_run[@]} -gt 0 ]]; then
231 for choice in "${to_run[@]}"; do
232 local skip=false
233
234 if [[ ${#to_exclude[@]} -gt 0 ]]; then
235 for ex in "${to_exclude[@]}"; do
236 if [[ "$choice" == "$ex" ]]; then
237 skip=true
238 break
239 fi
240 done
241 fi
242
243 $skip && continue
244
245 case "$choice" in
246 1) install_oh_my_zsh ;;
247 2) install_starship ;;
248 3) download_configs ;;
249 4) configure_zshrc ;;
250 5) configure_git_identity ;;
251 6) switch_shell ;;
252 7) exit 0 ;;
253 *) warn "Skipping invalid option: $choice" ;;
254 esac
255 echo
256 done
257 fi
258}
259
260# =============================
261# MAIN
262# =============================
263main() {
264 check_requirements
265 while true; do
266 show_menu
267 run_choices
268 read -p "Do you want to run more options? (y/n): " again
269 [[ "$again" =~ ^[Yy]$ ]] || break
270 done
271 ok "macOS minimal Zsh configuration complete!"
272}
273
274main "$@"
275
zsh_ubuntu.sh Raw
1#!/bin/bash
2set -euo pipefail
3
4# =============================
5# COLORS & LOGGING
6# =============================
7RED='\033[0;31m'
8GREEN='\033[0;32m'
9YELLOW='\033[1;33m'
10BLUE='\033[0;34m'
11NC='\033[0m'
12
13log() { echo -e "${BLUE}[INFO]${NC} $*"; }
14ok() { echo -e "${GREEN}[OK]${NC} $*"; }
15warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
16err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
17
18# =============================
19# FLAGS & CONFIG
20# =============================
21SKIP_PACKAGES=false
22SKIP_SHELL_CHANGE=false
23INSTALL_HOMEBREW=false
24
25GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD"
26CONFIG_FILES=(
27 ".alias"
28 ".func"
29 ".pathrc"
30 ".sourcerc"
31 ".vimrc"
32 ".zshrc"
33 ".config/starship.toml"
34 ".config/opencode/agents/ai-commit.md"
35)
36
37# =============================
38# OS DETECTION
39# =============================
40detect_os() {
41 if [[ "$OSTYPE" == "darwin"* ]]; then
42 echo "macos"
43 elif [ -f /etc/os-release ]; then
44 . /etc/os-release
45 echo "$ID"
46 else
47 echo "unknown"
48 fi
49}
50
51# =============================
52# REQUIREMENTS
53# =============================
54check_requirements() {
55 if [[ $EUID -eq 0 ]]; then
56 err "Do not run as root. The script will request sudo when necessary."
57 exit 1
58 fi
59 if [[ "$(detect_os)" != "macos" ]] && ! command -v sudo >/dev/null 2>&1; then
60 err "sudo required on Linux"
61 exit 1
62 fi
63}
64
65# =============================
66# INSTALLATION FUNCTIONS
67# =============================
68update_system() {
69 $SKIP_PACKAGES && return
70 log "Updating system..."
71 local os=$(detect_os)
72
73 case "$os" in
74 macos)
75 brew update || warn "Homebrew update failed; continuing installer"
76 ;;
77 ubuntu|debian)
78 sudo apt-get update -y
79 if ! sudo apt-get upgrade -y; then
80 warn "System upgrade did not complete. This can happen when apt wants to downgrade a package."
81 warn "Continuing because the Zsh setup does not require OS package upgrades to finish."
82 fi
83 ;;
84 fedora)
85 sudo dnf upgrade -y || warn "System upgrade failed; continuing installer"
86 ;;
87 arch)
88 sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer"
89 ;;
90 *) warn "Auto-update not supported for OS: $os" ;;
91 esac
92 ok "System update step finished"
93}
94
95install_packages() {
96 $SKIP_PACKAGES && return
97 log "Installing core packages..."
98 local os=$(detect_os)
99
100 case "$os" in
101 macos) brew install zsh git vim curl wget unzip xz ;;
102 ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;;
103 fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;;
104 arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;;
105 *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;;
106 esac
107 ok "Packages installed"
108}
109
110set_timezone() {
111 log "Setting timezone to Asia/Singapore..."
112 local os=$(detect_os)
113 if [[ "$os" == "macos" ]]; then
114 sudo systemsetup -settimezone Asia/Singapore >/dev/null
115 else
116 sudo timedatectl set-timezone Asia/Singapore
117 fi
118 ok "Timezone set to Asia/Singapore"
119}
120
121install_homebrew() {
122 ! $INSTALL_HOMEBREW && return
123 command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return
124 log "Installing Homebrew..."
125 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
126 ok "Homebrew installed"
127}
128
129configure_shell() {
130 $SKIP_SHELL_CHANGE && return
131 log "Changing default shell to zsh..."
132 local zsh_path
133 zsh_path="$(command -v zsh)"
134
135 if ! grep -qx "$zsh_path" /etc/shells; then
136 echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
137 fi
138 chsh -s "$zsh_path"
139 ok "Shell changed (requires logout/login to take effect)"
140}
141
142install_oh_my_zsh() {
143 log "Installing Oh My Zsh and plugins..."
144 export RUNZSH=no
145 export CHSH=no
146 export KEEP_ZSHRC=yes
147
148 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
149 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
150 else
151 ok "Oh My Zsh already installed"
152 fi
153
154 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
155 local plugin_dir="$custom/plugins"
156 mkdir -p "$plugin_dir"
157
158 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
159 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
160
161 ok "Oh My Zsh plugins installed to $plugin_dir"
162}
163
164install_starship() {
165 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
166 ok "Starship already installed"
167 else
168 log "Installing Starship prompt..."
169 if command -v brew >/dev/null 2>&1; then
170 brew install starship
171 else
172 mkdir -p "$HOME/.local/bin"
173 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
174 fi
175 ok "Starship installed"
176 fi
177}
178
179download_configs() {
180 log "Downloading custom config files from OpenGist..."
181 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
182 mkdir -p "$backup"
183
184 for f in "${CONFIG_FILES[@]}"; do
185 local remote_file="${f#.}"
186 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
187 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
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
210configure_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# =============================================================================
221source_if_readable() {
222 local file="$1"
223 if [[ -f "$file" && -r "$file" ]]; then
224 source "$file"
225 fi
226}
227
228export PATH="$HOME/.local/bin:$PATH"
229
230# =============================================================================
231# 2. OH MY ZSH FRAMEWORK
232# =============================================================================
233export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
234ZSH_THEME=""
235
236plugins=(
237 git
238 zsh-autosuggestions
239 zsh-syntax-highlighting
240)
241
242source_if_readable "$ZSH/oh-my-zsh.sh"
243
244# =============================================================================
245# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
246# =============================================================================
247source_if_readable "$HOME/.sourcerc"
248source_if_readable "$HOME/.func"
249source_if_readable "$HOME/.pathrc"
250source_if_readable "$HOME/.alias"
251
252# =============================================================================
253# 4. STARSHIP PROMPT
254# =============================================================================
255if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
256 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
257fi
258
259if command -v starship >/dev/null 2>&1; then
260 eval "$(starship init zsh)"
261elif [[ -x "$HOME/.local/bin/starship" ]]; then
262 eval "$("$HOME/.local/bin/starship" init zsh)"
263fi
264EOF
265
266 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
267}
268
269configure_git_identity() {
270 log "Configuring global Git identity..."
271 git config --global user.name "Vernon Wee Hong KOH"
272 git config --global user.email "261549452+weehong@users.noreply.github.com"
273 ok "Global Git identity configured"
274}
275
276switch_shell() {
277 log "Starting Zsh session..."
278 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
279 echo "----------------------------------------"
280 zsh -l
281 echo "----------------------------------------"
282 ok "Returned from Zsh session"
283}
284
285# =============================
286# INTERACTIVE MENU
287# =============================
288show_menu() {
289 echo "==========================================="
290 echo "Minimal Zsh Installer - Choose what to do"
291 echo "==========================================="
292 echo " 0) Run ALL steps (1-11)"
293 echo " 1) Update system packages"
294 echo " 2) Install core packages (zsh, git, vim, etc.)"
295 echo " 3) Set Timezone (Asia/Singapore)"
296 echo " 4) Install Homebrew"
297 echo " 5) Configure shell (chsh - sets default shell)"
298 echo " 6) Install Oh My Zsh + plugins"
299 echo " 7) Install Starship prompt"
300 echo " 8) Download custom configs (from OpenGist)"
301 echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)"
302 echo "10) Configure global Git identity"
303 echo "11) Switch to Zsh (Temporary Sub-shell)"
304 echo "12) Quit"
305 echo "==========================================="
306}
307
308run_choices() {
309 local input
310 read -p "Select: " input
311 input="${input//,/ }"
312
313 local -a to_run=()
314 local -a to_exclude=()
315
316 for item in $input; do
317 if [[ "$item" == !* ]]; then
318 to_exclude+=("${item:1}")
319 elif [[ "$item" == "0" ]]; then
320 to_run+=(1 2 3 4 5 6 7 8 9 10 11)
321 else
322 to_run+=("$item")
323 fi
324 done
325
326 for choice in "${to_run[@]}"; do
327 local skip=false
328
329 for ex in "${to_exclude[@]}"; do
330 if [[ "$choice" == "$ex" ]]; then
331 skip=true
332 break
333 fi
334 done
335
336 $skip && continue
337
338 case "$choice" in
339 1) update_system ;;
340 2) install_packages ;;
341 3) set_timezone ;;
342 4) install_homebrew ;;
343 5) configure_shell ;;
344 6) install_oh_my_zsh ;;
345 7) install_starship ;;
346 8) download_configs ;;
347 9) configure_zshrc ;;
348 10) configure_git_identity ;;
349 11) switch_shell ;;
350 12) log "Exiting..."; exit 0 ;;
351 *) warn "Skipping invalid option: $choice" ;;
352 esac
353 echo
354 done
355}
356
357# =============================
358# MAIN
359# =============================
360main() {
361 check_requirements
362 while true; do
363 show_menu
364 run_choices
365 read -p "Do you want to run more options? (y/n): " again
366 [[ "$again" =~ ^[Yy]$ ]] || break
367 done
368 ok "Zsh installation/configuration complete!"
369}
370
371main "$@"
372
zsh_wsl.sh Raw
1#!/bin/bash
2set -euo pipefail
3
4# =============================
5# COLORS & LOGGING
6# =============================
7RED='\033[0;31m'
8GREEN='\033[0;32m'
9YELLOW='\033[1;33m'
10BLUE='\033[0;34m'
11NC='\033[0m'
12
13log() { echo -e "${BLUE}[INFO]${NC} $*"; }
14ok() { echo -e "${GREEN}[OK]${NC} $*"; }
15warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
16err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
17
18# =============================
19# FLAGS & CONFIG
20# =============================
21SKIP_PACKAGES=false
22SKIP_SHELL_CHANGE=false
23INSTALL_HOMEBREW=false
24
25GIST_RAW_BASE="https://opengist.resetrix.work/weehong/f0d940c3c1214bf5b7996195199fdc09/raw/HEAD"
26CONFIG_FILES=(
27 ".alias"
28 ".func"
29 ".pathrc"
30 ".sourcerc"
31 ".vimrc"
32 ".zshrc"
33 ".config/starship.toml"
34 ".config/opencode/agents/ai-commit.md"
35)
36
37# =============================
38# PLATFORM DETECTION
39# =============================
40detect_os() {
41 if [ -f /etc/os-release ]; then
42 . /etc/os-release
43 echo "$ID"
44 else
45 echo "unknown"
46 fi
47}
48
49is_wsl() {
50 grep -qi "microsoft" /proc/version 2>/dev/null
51}
52
53# =============================
54# REQUIREMENTS
55# =============================
56check_requirements() {
57 if [[ $EUID -eq 0 ]]; then
58 err "Do not run as root. The script will request sudo when necessary."
59 exit 1
60 fi
61 if ! command -v sudo >/dev/null 2>&1; then
62 err "sudo required"
63 exit 1
64 fi
65 if ! is_wsl; then
66 err "This installer is intended for WSL"
67 exit 1
68 fi
69}
70
71# =============================
72# INSTALLATION FUNCTIONS
73# =============================
74update_system() {
75 $SKIP_PACKAGES && return
76 log "Updating system..."
77 local os=$(detect_os)
78
79 case "$os" in
80 ubuntu|debian)
81 sudo apt-get update -y
82 if ! sudo apt-get upgrade -y; then
83 warn "System upgrade did not complete. This can happen when apt wants to downgrade a package."
84 warn "Continuing because the Zsh setup does not require OS package upgrades to finish."
85 fi
86 ;;
87 fedora)
88 sudo dnf upgrade -y || warn "System upgrade failed; continuing installer"
89 ;;
90 arch)
91 sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer"
92 ;;
93 *) warn "Auto-update not supported for OS: $os" ;;
94 esac
95 ok "System update step finished"
96}
97
98install_packages() {
99 $SKIP_PACKAGES && return
100 log "Installing core packages..."
101 local os=$(detect_os)
102
103 case "$os" in
104 ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;;
105 fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;;
106 arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;;
107 *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;;
108 esac
109 ok "Packages installed"
110}
111
112set_timezone() {
113 log "Checking timezone configuration..."
114 if command -v timedatectl >/dev/null 2>&1 && timedatectl status >/dev/null 2>&1; then
115 sudo timedatectl set-timezone Asia/Singapore
116 ok "Timezone set to Asia/Singapore"
117 return
118 fi
119
120 warn "Skipping timezone change: timedatectl is not available in this WSL environment"
121}
122
123install_homebrew() {
124 ! $INSTALL_HOMEBREW && return
125 command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return
126 log "Installing Homebrew..."
127 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
128 ok "Homebrew installed"
129}
130
131configure_shell() {
132 $SKIP_SHELL_CHANGE && return
133 log "Changing default shell to zsh..."
134 local zsh_path
135 zsh_path="$(command -v zsh)"
136
137 if ! grep -qx "$zsh_path" /etc/shells; then
138 echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
139 fi
140 chsh -s "$zsh_path"
141 ok "Shell changed (open a new WSL session for it to take effect)"
142}
143
144install_oh_my_zsh() {
145 log "Installing Oh My Zsh and plugins..."
146 export RUNZSH=no
147 export CHSH=no
148 export KEEP_ZSHRC=yes
149
150 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
151 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
152 else
153 ok "Oh My Zsh already installed"
154 fi
155
156 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
157 local plugin_dir="$custom/plugins"
158 mkdir -p "$plugin_dir"
159
160 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
161 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
162
163 ok "Oh My Zsh plugins installed to $plugin_dir"
164}
165
166install_starship() {
167 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
168 ok "Starship already installed"
169 else
170 log "Installing Starship prompt..."
171 if command -v brew >/dev/null 2>&1; then
172 brew install starship
173 else
174 mkdir -p "$HOME/.local/bin"
175 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
176 fi
177 ok "Starship installed"
178 fi
179}
180
181download_configs() {
182 log "Downloading custom config files from OpenGist..."
183 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
184 mkdir -p "$backup"
185
186 for f in "${CONFIG_FILES[@]}"; do
187 local remote_file="${f#.}"
188 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
189 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
190 local url="$GIST_RAW_BASE/$remote_file"
191 local target="$HOME/$f"
192 local tmp="${target}.tmp.$$"
193
194 mkdir -p "$(dirname "$target")"
195
196 if [[ -f "$target" ]]; then
197 cp "$target" "$backup/"
198 fi
199
200 log "Fetching $remote_file -> $f ..."
201 if curl -fsSL "$url" -o "$tmp"; then
202 mv "$tmp" "$target"
203 else
204 rm -f "$tmp"
205 warn "Failed to download $remote_file"
206 fi
207 done
208
209 ok "Configs downloaded (Backup at $backup)"
210}
211
212configure_zshrc() {
213 local zshrc="$HOME/.zshrc"
214 log "Configuring .zshrc for Oh My Zsh and Starship..."
215
216 touch "$zshrc"
217 cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)"
218
219 cat > "$zshrc" <<'EOF'
220# =============================================================================
221# 1. HELPER FUNCTIONS & PATH
222# =============================================================================
223source_if_readable() {
224 local file="$1"
225 if [[ -f "$file" && -r "$file" ]]; then
226 source "$file"
227 fi
228}
229
230export PATH="$HOME/.local/bin:$PATH"
231
232# =============================================================================
233# 2. OH MY ZSH FRAMEWORK
234# =============================================================================
235export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
236ZSH_THEME=""
237
238plugins=(
239 git
240 zsh-autosuggestions
241 zsh-syntax-highlighting
242)
243
244source_if_readable "$ZSH/oh-my-zsh.sh"
245
246# =============================================================================
247# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
248# =============================================================================
249source_if_readable "$HOME/.sourcerc"
250source_if_readable "$HOME/.func"
251source_if_readable "$HOME/.pathrc"
252source_if_readable "$HOME/.alias"
253
254# =============================================================================
255# 4. STARSHIP PROMPT
256# =============================================================================
257if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
258 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
259fi
260
261if command -v starship >/dev/null 2>&1; then
262 eval "$(starship init zsh)"
263elif [[ -x "$HOME/.local/bin/starship" ]]; then
264 eval "$("$HOME/.local/bin/starship" init zsh)"
265fi
266EOF
267
268 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
269}
270
271configure_git_identity() {
272 log "Configuring global Git identity..."
273 git config --global user.name "Vernon Wee Hong KOH"
274 git config --global user.email "261549452+weehong@users.noreply.github.com"
275 ok "Global Git identity configured"
276}
277
278switch_shell() {
279 log "Starting Zsh session..."
280 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
281 echo "----------------------------------------"
282 zsh -l
283 echo "----------------------------------------"
284 ok "Returned from Zsh session"
285}
286
287# =============================
288# INTERACTIVE MENU
289# =============================
290show_menu() {
291 echo "==========================================="
292 echo "WSL Minimal Zsh Installer - Choose what to do"
293 echo "==========================================="
294 echo " 0) Run ALL steps (1-11)"
295 echo " 1) Update system packages"
296 echo " 2) Install core packages (zsh, git, vim, etc.)"
297 echo " 3) Set Timezone (best effort)"
298 echo " 4) Install Homebrew"
299 echo " 5) Configure shell (chsh - sets default shell)"
300 echo " 6) Install Oh My Zsh + plugins"
301 echo " 7) Install Starship prompt"
302 echo " 8) Download custom configs (from OpenGist)"
303 echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)"
304 echo "10) Configure global Git identity"
305 echo "11) Switch to Zsh (Temporary Sub-shell)"
306 echo "12) Quit"
307 echo "==========================================="
308}
309
310run_choices() {
311 local input
312 read -p "Select: " input
313 input="${input//,/ }"
314
315 local -a to_run=()
316 local -a to_exclude=()
317
318 for item in $input; do
319 if [[ "$item" == !* ]]; then
320 to_exclude+=("${item:1}")
321 elif [[ "$item" == "0" ]]; then
322 to_run+=(1 2 3 4 5 6 7 8 9 10 11)
323 else
324 to_run+=("$item")
325 fi
326 done
327
328 for choice in "${to_run[@]}"; do
329 local skip=false
330
331 for ex in "${to_exclude[@]}"; do
332 if [[ "$choice" == "$ex" ]]; then
333 skip=true
334 break
335 fi
336 done
337
338 $skip && continue
339
340 case "$choice" in
341 1) update_system ;;
342 2) install_packages ;;
343 3) set_timezone ;;
344 4) install_homebrew ;;
345 5) configure_shell ;;
346 6) install_oh_my_zsh ;;
347 7) install_starship ;;
348 8) download_configs ;;
349 9) configure_zshrc ;;
350 10) configure_git_identity ;;
351 11) switch_shell ;;
352 12) log "Exiting..."; exit 0 ;;
353 *) warn "Skipping invalid option: $choice" ;;
354 esac
355 echo
356 done
357}
358
359# =============================
360# MAIN
361# =============================
362main() {
363 check_requirements
364 while true; do
365 show_menu
366 run_choices
367 read -p "Do you want to run more options? (y/n): " again
368 [[ "$again" =~ ^[Yy]$ ]] || break
369 done
370 ok "WSL Zsh installation/configuration complete!"
371}
372
373main "$@"
374
zshrc Raw
1# =============================================================================
2# 1. HELPER FUNCTIONS & PATH
3# =============================================================================
4source_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)
12export PATH="$HOME/.local/bin:$PATH"
13
14# =============================================================================
15# 2. OH MY ZSH FRAMEWORK
16# =============================================================================
17export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
18ZSH_THEME=""
19
20plugins=(
21 git
22 zsh-autosuggestions
23 zsh-syntax-highlighting
24)
25
26source_if_readable "$ZSH/oh-my-zsh.sh"
27
28# =============================================================================
29# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
30# =============================================================================
31# Load external initializers (SDKMAN, NVM, etc.)
32source_if_readable "$HOME/.sourcerc"
33
34# Custom Functions (must load before Path Management)
35source_if_readable "$HOME/.func"
36
37# Path Management (Relies on append_path from .func)
38source_if_readable "$HOME/.pathrc"
39
40# Aliases (Loaded late so they override framework/system defaults)
41source_if_readable "$HOME/.alias"
42
43# =============================================================================
44# 4. STARSHIP PROMPT
45# =============================================================================
46if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
47 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
48fi
49
50# Starship owns the prompt. It must initialize after Oh My Zsh.
51if command -v starship >/dev/null 2>&1; then
52 eval "$(starship init zsh)"
53elif [[ -x "$HOME/.local/bin/starship" ]]; then
54 eval "$("$HOME/.local/bin/starship" init zsh)"
55fi
56