Ultima attività 1 day ago

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

Revisione 94dfb235b5e25f2cdb3a11d57339465dbd4a922b

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 shared commit instructions at ~/.config/ai-commit-prompt.txt and the required OpenCode agent at ~/.config/opencode/agents/ai-commit.md. The agent is hidden from the interactive agent selector but remains available to aicommit opencode. It may inspect Git status, the cached diff, and recent history, and may execute git commit; all unrelated tools and shell commands are denied.

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-prompt.txt Raw
1# Git Commit Message Generator Prompt
2
3## Role
4You are an Automated Git Commit Engine.
5
6## Objective
7Analyze staged changes (`git diff --cached`), identify the **primary logical change**, and generate a commit message that **strictly adheres** to Conventional Commits v1.0.0.
8
9---
10
11## Input
12The user will provide the output of:
13```bash
14git diff --cached
15```
16
17---
18
19## Strict Rules (Zero Tolerance)
20
21### 1. Header Format
22**Pattern:** `type(scope): subject`
23
24| Element | Rule |
25| :--- | :--- |
26| **Type** | ONLY use: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore` |
27| **Scope** | Optional, in parentheses, entirely lowercase (e.g., `docker`, `api`, `auth`) |
28| **Subject** | Lowercase, imperative mood ("add" not "added"), NO period at the end |
29| **Length** | Header MUST be ≤ 50 characters |
30
31### 2. Body Format
32| Element | Rule |
33| :--- | :--- |
34| **Spacing** | Exactly ONE blank line between the header and the body |
35| **Bullet Points**| Use hyphens (`-`) only |
36| **Line Length** | Each line MUST be ≤ 72 characters |
37| **Case** | Lowercase throughout |
38
39### 3. Content Rules
40* **NO** "Merge branch" or auto-generated text.
41* **NO** Markdown formatting (bold/italic/code blocks) inside the commit message string itself.
42* **NO** periods at the end of the subject line.
43* **NO** "Co-authored-by" trailers or any co-author attributions.
44
45---
46
47## Execution Steps
48
49### Step 1: Atomic Scan
50Identify the **largest cluster of related changes** (the "Primary Scope").
51
52**Priority Order for Type Selection:**
531. `ci` - Pipeline, CI/CD configuration changes
542. `feat` - New features
553. `fix` - Bug fixes
564. `refactor` - Code restructuring
575. `build` - Build system, dependencies
586. `test` - Test additions/modifications
597. `chore` - Maintenance tasks
608. `docs` - Documentation only (use ONLY if no code changes exist)
61
62### Step 2: Classify Changes
63Categorize all changed files:
64
65| Category | Influences Subject? | Include in Body? |
66| :--- | :---: | :---: |
67| **Primary Scope** | ✅ Yes | ✅ Yes |
68| **Supporting Changes** | ❌ No | ✅ Yes |
69| **Documentation** | ❌ No | ✅ Yes (as a single line) |
70
71> **Key Rule:** Documentation changes (`docs/`) should be mentioned in the body but MUST NOT influence the subject type unless documentation is the *only* change in the commit.
72
73### Step 3: Draft Commit Message
74Structure the message exactly like this:
75```text
76type(scope): subject (≤50 chars, imperative, lowercase, no period)
77
78- primary change detail 1
79- primary change detail 2
80- supporting change detail
81- add/update project documentation
82```
83
84---
85
86## Output Format & Execution Authorization
87
88You must output **ONLY** a single bash code block containing the complete git command. Do not provide any conversational filler, explanations, or greeting.
89
90Once you have generated the `git commit` bash command, **you are explicitly authorized to execute it directly in the terminal** (if your environment supports agentic execution).
91* Do not ask for further user confirmation.
92* Execute sequentially.
93* If the command fails, output the exact error message and halt execution.
94
95**Format Template:**
96```bash
97git commit -m "type(scope): subject
98
99- body detail 1
100- body detail 2
101- body detail 3"
102```
103
104---
105
106## Examples
107
108### Example 1: CI/CD with Documentation
109* **Changes:** `.gitlab-ci.yaml` (new), `Dockerfile` (new), `docs/deployment.md` (new)
110* **Output:**
111```bash
112git commit -m "ci: add gitlab pipeline and docker config
113
114- add gitlab-ci.yaml with build, test, publish stages
115- add Dockerfile for container deployment
116- add deployment documentation"
117```
118
119### Example 2: Feature with Config Changes
120* **Changes:** `src/auth/login.java` (new), `pom.xml` (modified), `docs/api.md` (new)
121* **Output:**
122```bash
123git commit -m "feat(auth): add user login endpoint
124
125- implement login service with jwt tokens
126- add spring-security dependency to pom.xml
127- add api documentation"
128```
129
130### Example 3: Documentation Only
131* **Changes:** `docs/setup.md` (new), `docs/testing.md` (modified), `README.md` (modified)
132* **Output:**
133```bash
134git commit -m "docs: add setup guide and update testing docs
135
136- add initial setup guide
137- update testing documentation with examples
138- update readme with quick start section"
139```
140
141---
142
143## Quick Reference
144
145| Type | When to Use |
146| :--- | :--- |
147| `feat` | New feature for the user |
148| `fix` | Bug fix for the user |
149| `docs` | Documentation only changes |
150| `style` | Formatting, missing semicolons, etc. (no code change) |
151| `refactor` | Code change that neither fixes a bug nor adds a feature |
152| `perf` | Code change that improves performance |
153| `test` | Adding or refactoring tests |
154| `build` | Build system or external dependencies |
155| `ci` | CI configuration files and scripts |
156| `chore` | Maintenance tasks, no production code changes |
157
ai-commit.md Raw

description: Creates one conventional commit from the staged diff supplied in the prompt. mode: primary hidden: true 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/ai-commit-prompt.txt"
15 ".config/opencode/agents/ai-commit.md"
16)
17
18echo "Starting configuration download..."
19download_failed=0
20
21for f in "${CONFIG_FILES[@]}"; do
22 # Remove the leading dot for the URL path
23 remote_name="${f#.}"
24 [[ "$f" == ".config/starship.toml" ]] && remote_name="starship.toml"
25 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_name="ai-commit-prompt.txt"
26 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_name="ai-commit.md"
27 url="$GIST_RAW_BASE/$remote_name"
28 target="$HOME/$f"
29 tmp="${target}.tmp.$$"
30
31 echo "Downloading $f..."
32 mkdir -p "$(dirname "$target")"
33
34 # Use -f to fail silently on server errors, -s for silent, -L to follow redirects
35 if curl -fsSL "$url" -o "$tmp" && mv "$tmp" "$target"; then
36 echo "Successfully updated $target"
37 else
38 rm -f "$tmp"
39 echo "Error: Failed to download $f from $url" >&2
40 download_failed=1
41 fi
42done
43
44if [ "$download_failed" -ne 0 ]; then
45 echo "Configuration download completed with errors." >&2
46 exit 1
47fi
48
49echo "Done! All configuration files have been replaced."
50
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 --all)
110 clear_history claude
111 clear_history codex
112 clear_history opencode
113 clear_history
114
115 if [ -t 1 ]; then
116 command clear
117 fi
118 ;;
119
120 claude)
121 if [ -d "$HOME/.claude/projects" ]; then
122 # Use 'yes' to skip prompts and -f to ignore non-existent files
123 yes | rm -rf "$HOME/.claude/projects"/*
124 echo "Claude project history/cache cleared."
125 fi
126 ;;
127
128 codex)
129 local codex_home="${CODEX_HOME:-$HOME/.codex}"
130 local codex_session_file
131 local codex_session_id
132
133 mkdir -p "$codex_home"
134 : > "$codex_home/history.jsonl"
135
136 if command -v codex >/dev/null 2>&1; then
137 find "$codex_home/sessions" "$codex_home/archived_sessions" -type f -name '*.jsonl' -print 2>/dev/null | while IFS= read -r codex_session_file; do
138 codex_session_id="$(sed -n 's/.*"session_id":"\([^"]*\)".*/\1/p; q' "$codex_session_file")"
139 if [ -n "$codex_session_id" ]; then
140 codex delete --force "$codex_session_id" >/dev/null 2>&1 || true
141 fi
142 done
143 fi
144
145 rm -rf "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots"
146 mkdir -p "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots"
147
148 echo "Codex history cleared."
149 ;;
150
151 opencode)
152 local opencode_state_home="${XDG_STATE_HOME:-$HOME/.local/state}/opencode"
153 local opencode_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/opencode"
154
155 mkdir -p "$opencode_state_home" "$opencode_data_home"
156 : > "$opencode_state_home/prompt-history.jsonl"
157 rm -f \
158 "$opencode_data_home/opencode.db" \
159 "$opencode_data_home/opencode.db-shm" \
160 "$opencode_data_home/opencode.db-wal"
161 rm -rf "$opencode_data_home/repos" "$opencode_data_home/log"
162 mkdir -p "$opencode_data_home/repos" "$opencode_data_home/log"
163
164 echo "OpenCode history cleared."
165 ;;
166
167 *)
168 # 1. Truncate the file
169 : > "$HISTFILE"
170
171 # 2. Clear RAM by briefly setting history size to 0
172 local old_histsize=$HISTSIZE
173 HISTSIZE=0
174 HISTSIZE=$old_histsize
175
176 echo "Shell history cleared."
177 ;;
178 esac
179}
180
181# -----------------------------------------------------------------------------
182# Function: require_cli (Check external CLI availability)
183# -----------------------------------------------------------------------------
184require_cli() {
185 local binary="$1"
186 local label="$2"
187 local resolved
188
189 if command -v whence >/dev/null 2>&1; then
190 if whence -p "$binary" >/dev/null 2>&1; then
191 return 0
192 fi
193 else
194 resolved="$(command -v "$binary" 2>/dev/null)" || resolved=""
195 if [ -n "$resolved" ] && [ -x "$resolved" ]; then
196 return 0
197 fi
198 fi
199
200 printf "Error: %s ('%s') is not installed or not in your PATH.\n" "$label" "$binary" >&2
201 return 1
202}
203
204# -----------------------------------------------------------------------------
205# Function: claude (Includes --yolo and clear shortcuts)
206# -----------------------------------------------------------------------------
207claude() {
208 # Shortcut for clearing cache
209 if [[ "$1" == "clear" ]]; then
210 clear_history claude
211 return 0
212 fi
213
214 # Check if binary exists
215 require_cli claude "Claude Code CLI" || return 1
216
217 # Handle --yolo mode
218 if [[ "$1" == "--yolo" ]]; then
219 shift
220 command claude --dangerously-skip-permissions "$@"
221 return $?
222 fi
223
224 command claude "$@"
225}
226
227# -----------------------------------------------------------------------------
228# Internal: _prompt_confirm_indexing (Confirm project indexing via the terminal)
229# -----------------------------------------------------------------------------
230_prompt_confirm_indexing() {
231 local project_root="$1"
232 local reply
233
234 if ! (: </dev/tty) 2>/dev/null; then
235 printf "Notice: No interactive terminal available; enhancing without project indexing.\n" >&2
236 return 1
237 fi
238
239 printf "Allow Auggie to index and use project context from '%s'? (y/N) " "$project_root" >/dev/tty
240 if ! IFS= read -r reply </dev/tty; then
241 printf "\nNotice: Unable to read confirmation; enhancing without project indexing.\n" >&2
242 return 1
243 fi
244
245 case "$reply" in
246 y|Y|yes|YES|Yes)
247 return 0
248 ;;
249 *)
250 return 1
251 ;;
252 esac
253}
254
255# -----------------------------------------------------------------------------
256# Internal: _enhance_prompt (Run Auggie and write only the enhanced prompt)
257# -----------------------------------------------------------------------------
258_enhance_prompt() {
259 local prompt="$1"
260 local output_file="$2"
261 local run_dir
262 local prompt_file
263 local workspace
264 local cache_dir
265 local auth_file
266 local log_file
267 local parsed_file
268 local project_root
269 local use_project_context
270 local script_pid
271 local waited
272 local timeout_seconds
273 local monitor_status
274
275 if [ -z "$prompt" ]; then
276 printf "Error: Prompt was empty.\n" >&2
277 return 1
278 fi
279
280 if [ ! -s "$HOME/.augment/session.json" ]; then
281 printf "Error: Auggie session file not found. Run 'auggie login' first.\n" >&2
282 return 1
283 fi
284
285 run_dir="$(mktemp -d "${TMPDIR:-/tmp}/auggie-enhance.XXXXXX")" || return 1
286 prompt_file="$run_dir/prompt.txt"
287 workspace="$run_dir/workspace"
288 cache_dir="$run_dir/cache"
289 auth_file="$HOME/.augment/session.json"
290 log_file="$run_dir/auggie.log"
291 parsed_file="$run_dir/enhanced.txt"
292 mkdir -p "$workspace" "$cache_dir"
293 printf "%s\n" "$prompt" > "$prompt_file"
294
295 project_root="$(git rev-parse --show-toplevel 2>/dev/null)" || project_root="$PWD"
296 use_project_context=0
297 timeout_seconds=90
298
299 if _prompt_confirm_indexing "$project_root"; then
300 use_project_context=1
301 timeout_seconds=300
302 workspace="$project_root"
303 cache_dir="$HOME/.augment"
304 printf "Indexing approved; enhancing with project context from '%s'.\n" "$project_root" >&2
305 else
306 printf "Enhancing without project indexing.\n" >&2
307 fi
308
309 (
310 if [ "$use_project_context" -eq 1 ]; then
311 AUGGIE_PROMPT_FILE="$prompt_file" \
312 AUGGIE_WORKSPACE="$workspace" \
313 AUGGIE_CACHE_DIR="$cache_dir" \
314 AUGGIE_AUTH_FILE="$auth_file" \
315 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 &
316 else
317 AUGGIE_PROMPT_FILE="$prompt_file" \
318 AUGGIE_WORKSPACE="$workspace" \
319 AUGGIE_CACHE_DIR="$cache_dir" \
320 AUGGIE_AUTH_FILE="$auth_file" \
321 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 &
322 fi
323
324 script_pid=$!
325 waited=0
326
327 while kill -0 "$script_pid" >/dev/null 2>&1; do
328 if grep -aq "🤖" "$log_file" 2>/dev/null || grep -aq "Tool call:" "$log_file" 2>/dev/null; then
329 kill "$script_pid" >/dev/null 2>&1 || true
330 wait "$script_pid" >/dev/null 2>&1 || true
331 exit 0
332 fi
333
334 if [ "$waited" -ge "$timeout_seconds" ]; then
335 kill "$script_pid" >/dev/null 2>&1 || true
336 wait "$script_pid" >/dev/null 2>&1 || true
337 exit 124
338 fi
339
340 sleep 1
341 waited=$((waited + 1))
342 done
343
344 wait "$script_pid" >/dev/null 2>&1 || true
345 exit 0
346 )
347 monitor_status=$?
348
349 if [ "$monitor_status" -eq 124 ]; then
350 printf "Error: Timed out waiting for Auggie to enhance the prompt.\n" >&2
351 rm -rf "$run_dir"
352 return 124
353 fi
354
355 perl -ne '
356 # Auggie redraws streamed terminal lines with carriage returns. Keep only
357 # the final rendered segment so stale text cannot corrupt the prompt.
358 s/\r$//;
359 s/.*\r//;
360 1 while s/[^\x08]\x08//g;
361 s/\x08//g;
362 s/\e\][^\a]*(?:\a|\e\\)//g;
363 s/\e\[[0-?]*[ -\/]*[@-~]//g;
364 next if /Script started on/ || /Script done on/;
365 if (/^(?:✨\s*)?Enhanced prompt:\s*(.*)$/) {
366 $capturing = 1;
367 $output .= "$1\n" if length $1;
368 next;
369 }
370 next unless $capturing;
371 exit if /^🤖/ || /Tool call:/ || /Session terminated/;
372 $output .= $_;
373 END {
374 $output =~ s/^\s*\n//;
375 $output =~ s/\s+\z//;
376 print "$output\n" if length $output;
377 }
378 ' "$log_file" > "$parsed_file"
379
380 if [ ! -s "$parsed_file" ]; then
381 printf "Error: Auggie did not return an enhanced prompt.\n" >&2
382 rm -rf "$run_dir"
383 return 1
384 fi
385
386 if ! command cp "$parsed_file" "$output_file"; then
387 printf "Error: Unable to save the enhanced prompt.\n" >&2
388 rm -rf "$run_dir"
389 return 1
390 fi
391 rm -rf "$run_dir"
392}
393
394# -----------------------------------------------------------------------------
395# Function: prompt (Enhance a prompt with Auggie and optional project context)
396# -----------------------------------------------------------------------------
397prompt() {
398 local prompt
399 local line
400 local output_file
401
402 require_cli auggie "Auggie CLI" || return 1
403 require_cli script "script utility" || return 1
404
405 if [ $# -gt 0 ]; then
406 prompt="$*"
407 elif [ ! -t 0 ]; then
408 prompt="$(cat)"
409 else
410 echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished."
411 prompt=""
412
413 while IFS= read -r line; do
414 [ "$line" = "EOF" ] && break
415 if [ -z "$prompt" ]; then
416 prompt="$line"
417 else
418 prompt="${prompt}"$'\n'"${line}"
419 fi
420 done
421 fi
422
423 if [ -z "$prompt" ]; then
424 printf "Error: Prompt was empty.\n" >&2
425 return 1
426 fi
427
428 output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1
429 if ! _enhance_prompt "$prompt" "$output_file"; then
430 rm -f "$output_file"
431 return 1
432 fi
433
434 if [ -t 1 ]; then
435 clear
436 fi
437
438 printf "Enhanced prompt:\n"
439 command cat "$output_file"
440 rm -f "$output_file"
441
442 command auggie account status
443}
444
445# -----------------------------------------------------------------------------
446# Function: plan_build (Cached canonical plan-build launcher)
447# -----------------------------------------------------------------------------
448plan_build() {
449 emulate -L zsh
450 setopt localtraps
451 local base_url="${PLAN_BUILD_BASE_URL:-https://opengist.resetrix.work/weehong/plan-build/raw/HEAD}"
452 local cache_dir="${PLAN_BUILD_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/plan-build}"
453 local curl_command="${PLAN_BUILD_CURL_COMMAND:-curl}"
454 local skill_file="$HOME/.claude/skills/plan-build/SKILL.md"
455 local architect_file="$HOME/.claude/skills/plan-build-architect/SKILL.md"
456 local stage_dir="" release_dir="" current_tmp=""
457 local skill_tmp="" architect_tmp="" skill_backup="" architect_backup=""
458 local activation_lock="$cache_dir/.activate.lock"
459 local activation_lock_fd=""
460 local selected_release="" active_pid="" refresh_rc=1 call_rc=0 interrupted_rc=0
461
462 if ! zmodload zsh/system 2>/dev/null; then
463 printf "Error: Unable to load the Zsh system module required by plan-build.\n" >&2
464 return 1
465 fi
466
467 require_cli zsh "Zsh" || return 1
468
469 if ! command mkdir -p "$cache_dir" "${skill_file:h}" "${architect_file:h}"; then
470 printf "Error: Unable to create plan-build cache or skill directories.\n" >&2
471 return 1
472 fi
473
474 _plan_build_cleanup() {
475 if [[ -n "${active_pid:-}" ]] && command kill -0 "$active_pid" 2>/dev/null; then
476 command kill "$active_pid" 2>/dev/null || true
477 wait "$active_pid" 2>/dev/null || true
478 fi
479 active_pid=""
480 [[ -n "$stage_dir" && -d "$stage_dir" ]] && command rm -rf -- "$stage_dir"
481 command rm -f -- "$current_tmp" "$skill_tmp" "$architect_tmp"
482 command rm -f -- "$skill_backup" "$architect_backup"
483 if [[ -n "${activation_lock_fd:-}" ]]; then
484 zsystem flock -u "$activation_lock_fd" 2>/dev/null || true
485 activation_lock_fd=""
486 fi
487 }
488 _plan_build_cancel() {
489 interrupted_rc="$1"
490 [[ -n "${active_pid:-}" ]] && command kill "$active_pid" 2>/dev/null || true
491 }
492 trap '_plan_build_cleanup' EXIT
493 trap '_plan_build_cancel 129' HUP
494 trap '_plan_build_cancel 130' INT
495 trap '_plan_build_cancel 143' TERM
496
497 _plan_build_validate_release() {
498 local candidate="$1"
499 [[ -d "$candidate" &&
500 -s "$candidate/plan_build.zsh" &&
501 -s "$candidate/SKILL.md" &&
502 -s "$candidate/ARCHITECT.md" ]] &&
503 command zsh -n "$candidate/plan_build.zsh"
504 }
505
506 _plan_build_replace_path() {
507 local replacement="$1" destination="$2"
508 if command mv -fT "$replacement" "$destination" 2>/dev/null; then
509 return 0
510 fi
511 command mv -fh "$replacement" "$destination"
512 }
513
514 _plan_build_select_current() {
515 local resolved
516 [[ -L "$cache_dir/current" ]] || return 1
517 resolved="$cache_dir/current"
518 resolved="${resolved:A}"
519 _plan_build_validate_release "$resolved" || return 1
520 selected_release="$resolved"
521 }
522
523 _plan_build_acquire_activation_lock() {
524 (( interrupted_rc )) && return 1
525 command touch "$activation_lock" || return 1
526 zsystem flock -t 10 -f activation_lock_fd "$activation_lock"
527 }
528
529 _plan_build_release_activation_lock() {
530 [[ -n "$activation_lock_fd" ]] || return 0
531 zsystem flock -u "$activation_lock_fd" || return 1
532 activation_lock_fd=""
533 }
534
535 _plan_build_download() {
536 local remote="$1" destination="$2" download_rc
537 command "$curl_command" -fsSL "$remote" -o "$destination" &
538 active_pid=$!
539 wait "$active_pid"
540 download_rc=$?
541 active_pid=""
542 (( interrupted_rc )) && return "$interrupted_rc"
543 return "$download_rc"
544 }
545
546 _plan_build_backup_path() {
547 local source="$1" backup="$2"
548 [[ -e "$source" || -L "$source" ]] || return 0
549 command rm -f -- "$backup" && command cp -RP "$source" "$backup"
550 }
551
552 _plan_build_restore_path() {
553 local destination="$1" backup="$2" had_old="$3"
554 if (( had_old )); then
555 _plan_build_replace_path "$backup" "$destination" || return 1
556 else
557 command rm -f -- "$destination"
558 fi
559 }
560
561 _plan_build_install_skill_links() {
562 local activation_ok=1
563 local skill_had_old=0 architect_had_old=0
564
565 skill_tmp="$(command mktemp "${skill_file}.link.XXXXXX")" &&
566 command rm -f "$skill_tmp" &&
567 command ln -s "$cache_dir/current/SKILL.md" "$skill_tmp" || skill_tmp=""
568 architect_tmp="$(command mktemp "${architect_file}.link.XXXXXX")" &&
569 command rm -f "$architect_tmp" &&
570 command ln -s "$cache_dir/current/ARCHITECT.md" "$architect_tmp" || architect_tmp=""
571 [[ -n "$skill_tmp" && -L "$skill_tmp" &&
572 -n "$architect_tmp" && -L "$architect_tmp" ]] || return 1
573
574 skill_backup="${skill_file}.backup.$$.$RANDOM"
575 architect_backup="${architect_file}.backup.$$.$RANDOM"
576 [[ -e "$skill_file" || -L "$skill_file" ]] && skill_had_old=1
577 [[ -e "$architect_file" || -L "$architect_file" ]] && architect_had_old=1
578 _plan_build_backup_path "$skill_file" "$skill_backup" || activation_ok=0
579 (( activation_ok )) && _plan_build_backup_path "$architect_file" "$architect_backup" || activation_ok=0
580 (( activation_ok && ! interrupted_rc )) &&
581 _plan_build_replace_path "$skill_tmp" "$skill_file" || activation_ok=0
582 (( activation_ok )) && skill_tmp=""
583 (( activation_ok && ! interrupted_rc )) &&
584 _plan_build_replace_path "$architect_tmp" "$architect_file" || activation_ok=0
585 (( activation_ok )) && architect_tmp=""
586
587 if (( activation_ok )); then
588 command rm -f -- "$skill_backup" "$architect_backup"
589 skill_backup="" architect_backup=""
590 return 0
591 fi
592
593 if ! _plan_build_restore_path "$skill_file" "$skill_backup" "$skill_had_old"; then
594 printf "Error: Unable to restore plan-build skill; backup preserved at %s.\n" "$skill_backup" >&2
595 skill_backup=""
596 fi
597 if ! _plan_build_restore_path "$architect_file" "$architect_backup" "$architect_had_old"; then
598 printf "Error: Unable to restore architect skill; backup preserved at %s.\n" "$architect_backup" >&2
599 architect_backup=""
600 fi
601 return 1
602 }
603
604 if (( $+commands[$curl_command] )) || [[ "$curl_command" == */* && -x "$curl_command" ]]; then
605 stage_dir="$(command mktemp -d "$cache_dir/.stage.XXXXXX")" || stage_dir=""
606 if [[ -n "$stage_dir" ]]; then
607 _plan_build_download "$base_url/plan_build.zsh" "$stage_dir/plan_build.zsh" &&
608 _plan_build_download "$base_url/SKILL.md" "$stage_dir/SKILL.md" &&
609 _plan_build_download "$base_url/ARCHITECT.md" "$stage_dir/ARCHITECT.md"
610 refresh_rc=$?
611 if (( interrupted_rc )); then
612 _plan_build_cleanup
613 trap - EXIT HUP INT TERM
614 return "$interrupted_rc"
615 fi
616 else
617 refresh_rc=1
618 fi
619 if (( refresh_rc == 0 )) &&
620 _plan_build_validate_release "$stage_dir"; then
621 release_dir="$(command mktemp -d "$cache_dir/release.XXXXXX")" || release_dir=""
622 if [[ -n "$release_dir" ]] &&
623 command rmdir "$release_dir" &&
624 command mv "$stage_dir" "$release_dir" &&
625 _plan_build_acquire_activation_lock; then
626 stage_dir=""
627 release_dir="${release_dir:A}"
628 current_tmp="$(command mktemp "$cache_dir/.current.XXXXXX")" &&
629 command rm -f "$current_tmp" &&
630 command ln -s "$release_dir" "$current_tmp" || current_tmp=""
631
632 if [[ -n "$current_tmp" && -L "$current_tmp" ]] &&
633 _plan_build_install_skill_links &&
634 _plan_build_replace_path "$current_tmp" "$cache_dir/current"; then
635 current_tmp=""
636 selected_release="$release_dir"
637 fi
638 _plan_build_release_activation_lock || true
639 fi
640 fi
641 fi
642
643 if (( interrupted_rc )); then
644 _plan_build_cleanup
645 trap - EXIT HUP INT TERM
646 return "$interrupted_rc"
647 fi
648
649 if [[ -z "$selected_release" ]]; then
650 if ! _plan_build_select_current; then
651 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
652 printf "Error: curl is required because no valid cached plan-build bundle is available.\n" >&2
653 else
654 printf "Error: Unable to refresh plan-build and no valid cached bundle is available.\n" >&2
655 fi
656 return 1
657 fi
658 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
659 printf "Warning: curl is unavailable; using the validated cached plan-build bundle.\n" >&2
660 else
661 printf "Warning: Unable to refresh plan-build; using the validated cached bundle.\n" >&2
662 fi
663 fi
664
665 if [[ "$(command readlink "$skill_file" 2>/dev/null)" != "$cache_dir/current/SKILL.md" ||
666 "$(command readlink "$architect_file" 2>/dev/null)" != "$cache_dir/current/ARCHITECT.md" ]]; then
667 if ! _plan_build_acquire_activation_lock || ! _plan_build_install_skill_links; then
668 _plan_build_release_activation_lock 2>/dev/null || true
669 if (( interrupted_rc )); then
670 _plan_build_cleanup
671 trap - EXIT HUP INT TERM
672 return "$interrupted_rc"
673 fi
674 printf "Error: Unable to install the plan-build skill links.\n" >&2
675 return 1
676 fi
677 _plan_build_release_activation_lock || true
678 fi
679
680 if (( interrupted_rc )); then
681 _plan_build_cleanup
682 trap - EXIT HUP INT TERM
683 return "$interrupted_rc"
684 fi
685
686 PLAN_BUILD_SKILL_PATH="$selected_release/SKILL.md" \
687 PLAN_BUILD_ARCHITECT_SKILL_PATH="$selected_release/ARCHITECT.md" \
688 command zsh "$selected_release/plan_build.zsh" "$@" &
689 active_pid=$!
690 wait "$active_pid"
691 call_rc=$?
692 active_pid=""
693 (( interrupted_rc )) && call_rc="$interrupted_rc"
694 _plan_build_cleanup
695 trap - EXIT HUP INT TERM
696 return "$call_rc"
697}
698
699# -----------------------------------------------------------------------------
700# Function: aicommit (Automated Conventional Commit Engine Wrapper)
701# -----------------------------------------------------------------------------
702aicommit() {
703 local diff engine prompt
704
705 # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes
706 diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000)
707
708 if [ -z "$diff" ]; then
709 printf "❌ Error: Nothing staged to commit.\n" >&2
710 return 1
711 fi
712
713 if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then
714 printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2
715 printf "Please create it and paste your system prompt inside.\n" >&2
716 return 1
717 fi
718
719 engine=${1:-claude}
720 prompt=$(cat "$HOME/.config/ai-commit-prompt.txt")
721
722 case "$engine" in
723 claude)
724 echo "🤖 Claude is analyzing staged changes..."
725 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
726 | command claude --allowedTools 'Bash(git commit *)' --permission-mode dontAsk -p \
727 "Read the provided instructions and diff, then generate and execute the commit."
728 ;;
729
730 codex)
731 echo "🤖 Codex is analyzing staged changes..."
732 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
733 | command codex --ask-for-approval never exec --sandbox workspace-write --ephemeral \
734 "Read the provided instructions and diff, then generate and execute the commit."
735 ;;
736
737 copilot)
738 echo "🤖 Copilot is analyzing staged changes..."
739 copilot -p "$prompt
740
741Here is the git diff:
742$diff" -s --no-ask-user --allow-tool='shell(git:*)'
743 ;;
744
745 opencode)
746 echo "🤖 OpenCode is analyzing staged changes..."
747 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | opencode run --agent ai-commit
748 ;;
749
750 *)
751 printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2
752 printf "Usage: aicommit [claude | codex | copilot | opencode]\n" >&2
753 return 1
754 ;;
755 esac
756}
757
758# -----------------------------------------------------------------------------
759# Function: create (Efficiently create file + parent dirs)
760# -----------------------------------------------------------------------------
761create() {
762 if ! command -v install >/dev/null 2>&1; then
763 printf "Error: 'install' coreutil is not available.\n" >&2
764 return 1
765 fi
766
767 if [ $# -eq 0 ]; then
768 printf "Usage: create <file> [file ...]\n" >&2
769 return 2
770 fi
771
772 for p in "$@"; do
773 # Expand tilde manually if shell doesn't
774 [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}"
775
776 if [ -z "$p" ] || [ "$p" = "/" ]; then
777 printf "create: refusing to operate on '%s'\n" "$p" >&2
778 continue
779 fi
780
781 if [ -e "$p" ]; then
782 printf "create: '%s' already exists. Skipping.\n" "$p" >&2
783 continue
784 fi
785
786 if install -D /dev/null "$p"; then
787 echo "Created '$p'"
788 fi
789 done
790}
791
792# -----------------------------------------------------------------------------
793# Function: kill_port (Kill or check the process using a port)
794# -----------------------------------------------------------------------------
795kill_port() {
796 local action="kill"
797 local signal="TERM"
798 local port
799 local pids
800 local pid
801
802 case "${1:-}" in
803 -c|--check)
804 action="check"
805 shift
806 ;;
807 -k|--kill)
808 action="kill"
809 shift
810 ;;
811 -f|--force)
812 action="kill"
813 signal="KILL"
814 shift
815 ;;
816 -h|--help|"")
817 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
818 return 1
819 ;;
820 esac
821
822 port="${1:-}"
823 if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
824 printf "Error: port must be a number between 1 and 65535.\n" >&2
825 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
826 return 1
827 fi
828
829 if [[ "$OSTYPE" == "darwin"* ]]; then
830 pids="$(
831 {
832 lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null
833 lsof -nP -tiUDP:"$port" 2>/dev/null
834 } | sort -u
835 )"
836
837 if [ -z "$pids" ]; then
838 printf "Port %s is FREE\n" "$port"
839 return 0
840 fi
841
842 printf "Port %s is in use:\n" "$port"
843 lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null
844 else
845 local port_hex
846 local inodes
847
848 port_hex="$(printf "%04X" "$port")"
849 inodes="$(
850 awk -v port="$port_hex" '
851 NR > 1 {
852 split($2, local_addr, ":")
853 if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) {
854 print $10
855 }
856 }
857 ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u
858 )"
859
860 if [ -z "$inodes" ]; then
861 printf "Port %s is FREE\n" "$port"
862 return 0
863 fi
864
865 pids=""
866 pids="$(
867 find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null |
868 awk -v inodes="$inodes" '
869 BEGIN {
870 split(inodes, inode_list, " ")
871 for (i in inode_list) {
872 wanted["socket:[" inode_list[i] "]"] = 1
873 }
874 }
875 $2 in wanted {
876 split($1, path, "/")
877 print path[3]
878 }
879 ' | sort -u
880 )"
881
882 if [ -z "$pids" ]; then
883 printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2
884 return 1
885 fi
886
887 printf "Port %s is in use:\n" "$port"
888 for pid in $pids; do
889 if [ -r "/proc/$pid/cmdline" ]; then
890 printf " PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")"
891 else
892 printf " PID %s\n" "$pid"
893 fi
894 done
895 fi
896
897 if [ "$action" = "kill" ]; then
898 for pid in $pids; do
899 if kill "-$signal" "$pid" 2>/dev/null; then
900 printf "Killed PID %s with SIG%s.\n" "$pid" "$signal"
901 else
902 printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2
903 return 1
904 fi
905 done
906 fi
907}
908
909# -----------------------------------------------------------------------------
910# Function: append_path (Adds to session PATH if exists)
911# -----------------------------------------------------------------------------
912append_path() {
913 local dir="$1"
914 if [ -d "$dir" ]; then
915 case ":$PATH:" in
916 *":$dir:"*) ;;
917 *) PATH="$PATH:$dir" ;;
918 esac
919 elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then
920 echo "Warning: $dir does not exist." >&2
921 fi
922}
923
924# -----------------------------------------------------------------------------
925# Function: add_path_to_config (Fixed awk/sed injection)
926# -----------------------------------------------------------------------------
927add_path_to_config() {
928 local new_path="$1"
929 local config_file="$HOME/.bashrc"
930 [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc"
931
932 if [ -z "$new_path" ]; then
933 printf "Usage: add_path_to_config <directory>\n" >&2
934 return 1
935 fi
936
937 new_path="${new_path/#\~/$HOME}"
938
939 if [ ! -d "$new_path" ]; then
940 printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path"
941 read -r REPLY
942 [[ ! $REPLY =~ ^[Yy]$ ]] && return 1
943 fi
944
945 # Check if already exists in file
946 if grep -Fq "append_path \"$new_path\"" "$config_file"; then
947 printf "Notice: Already in %s.\n" "$config_file"
948 return 0
949 fi
950
951 # Robust Injection: Use sed to insert before export PATH or just append
952 if grep -q "^export PATH" "$config_file"; then
953 sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file"
954 else
955 echo "append_path \"$new_path\"" >> "$config_file"
956 echo "export PATH" >> "$config_file"
957 fi
958
959 printf "✅ Success: Added to %s\n" "$config_file"
960 append_path "$new_path" && export PATH
961}
962
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/ai-commit-prompt.txt"
32 ".config/opencode/agents/ai-commit.md"
33)
34
35# =============================
36# REQUIREMENTS
37# =============================
38check_requirements() {
39 if [[ $EUID -eq 0 ]]; then
40 err "Do not run as root on macOS"
41 exit 1
42 fi
43}
44
45# =============================
46# INSTALLATION FUNCTIONS
47# =============================
48install_oh_my_zsh() {
49 log "Installing Oh My Zsh and plugins..."
50 export RUNZSH=no
51 export CHSH=no
52 export KEEP_ZSHRC=yes
53
54 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
55 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
56 else
57 ok "Oh My Zsh already installed"
58 fi
59
60 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
61 local plugin_dir="$custom/plugins"
62 mkdir -p "$plugin_dir"
63
64 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || \
65 git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
66
67 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || \
68 git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
69
70 ok "Oh My Zsh plugins installed to $plugin_dir"
71}
72
73install_starship() {
74 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
75 ok "Starship already installed"
76 elif command -v brew >/dev/null 2>&1; then
77 log "Installing Starship with Homebrew..."
78 brew install starship
79 ok "Starship installed"
80 else
81 log "Installing Starship..."
82 mkdir -p "$HOME/.local/bin"
83 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
84 ok "Starship installed"
85 fi
86}
87
88download_configs() {
89 log "Downloading custom config files..."
90 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
91 mkdir -p "$backup"
92
93 for f in "${CONFIG_FILES[@]}"; do
94 # Strip the leading dot for the download URL
95 local remote_file="${f#.}"
96 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
97 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
98 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
99 local url="$GIST_RAW_BASE/$remote_file"
100 local target="$HOME/$f"
101 local tmp="${target}.tmp.$$"
102
103 mkdir -p "$(dirname "$target")"
104
105 if [[ -f "$target" ]]; then
106 cp "$target" "$backup/"
107 fi
108
109 log "Fetching $remote_file -> $f ..."
110 if curl -fsSL "$url" -o "$tmp"; then
111 mv "$tmp" "$target"
112 else
113 rm -f "$tmp"
114 warn "Failed to download $remote_file"
115 fi
116 done
117
118 ok "Configs downloaded (Backup at $backup)"
119}
120
121configure_zshrc() {
122 local zshrc="$HOME/.zshrc"
123 log "Configuring .zshrc for Oh My Zsh and Starship..."
124
125 touch "$zshrc"
126 cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)"
127
128 cat > "$zshrc" <<'EOF'
129# =============================================================================
130# 1. HELPER FUNCTIONS & PATH
131# =============================================================================
132source_if_readable() {
133 local file="$1"
134 if [[ -f "$file" && -r "$file" ]]; then
135 source "$file"
136 fi
137}
138
139export PATH="$HOME/.local/bin:$PATH"
140
141# =============================================================================
142# 2. OH MY ZSH FRAMEWORK
143# =============================================================================
144export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
145ZSH_THEME=""
146
147plugins=(
148 git
149 zsh-autosuggestions
150 zsh-syntax-highlighting
151)
152
153source_if_readable "$ZSH/oh-my-zsh.sh"
154
155# =============================================================================
156# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
157# =============================================================================
158source_if_readable "$HOME/.sourcerc"
159source_if_readable "$HOME/.func"
160source_if_readable "$HOME/.pathrc"
161source_if_readable "$HOME/.alias"
162
163# =============================================================================
164# 4. STARSHIP PROMPT
165# =============================================================================
166if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
167 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
168fi
169
170if command -v starship >/dev/null 2>&1; then
171 eval "$(starship init zsh)"
172elif [[ -x "$HOME/.local/bin/starship" ]]; then
173 eval "$("$HOME/.local/bin/starship" init zsh)"
174fi
175EOF
176
177 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
178}
179
180configure_git_identity() {
181 log "Configuring global Git identity..."
182 git config --global user.name "Vernon Wee Hong KOH"
183 git config --global user.email "261549452+weehong@users.noreply.github.com"
184 ok "Global Git identity configured"
185}
186
187switch_shell() {
188 log "Starting Zsh session..."
189 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
190 echo "----------------------------------------"
191 zsh -l
192 echo "----------------------------------------"
193 ok "Returned from Zsh session"
194}
195
196# =============================
197# INTERACTIVE MENU
198# =============================
199show_menu() {
200 echo "==========================================="
201 echo "macOS Minimal Zsh Setup - Choose what to do"
202 echo "==========================================="
203 echo " 0) Run ALL steps (1-6)"
204 echo " 1) Install Oh My Zsh + plugins"
205 echo " 2) Install Starship prompt"
206 echo " 3) Download custom configs (~/.alias, .func, .vimrc, etc.)"
207 echo " 4) Configure ~/.zshrc (Oh My Zsh + Starship)"
208 echo " 5) Configure global Git identity"
209 echo " 6) Switch to Zsh (Temporary Sub-shell)"
210 echo " 7) Quit"
211 echo "==========================================="
212}
213
214run_choices() {
215 local input
216 read -p "Select: " input
217 input="${input//,/ }"
218
219 local -a to_run=()
220 local -a to_exclude=()
221
222 for item in $input; do
223 if [[ "$item" == !* ]]; then
224 to_exclude+=("${item:1}")
225 elif [[ "$item" == "0" ]]; then
226 to_run+=(1 2 3 4 5 6)
227 else
228 to_run+=("$item")
229 fi
230 done
231
232 if [[ ${#to_run[@]} -gt 0 ]]; then
233 for choice in "${to_run[@]}"; do
234 local skip=false
235
236 if [[ ${#to_exclude[@]} -gt 0 ]]; then
237 for ex in "${to_exclude[@]}"; do
238 if [[ "$choice" == "$ex" ]]; then
239 skip=true
240 break
241 fi
242 done
243 fi
244
245 $skip && continue
246
247 case "$choice" in
248 1) install_oh_my_zsh ;;
249 2) install_starship ;;
250 3) download_configs ;;
251 4) configure_zshrc ;;
252 5) configure_git_identity ;;
253 6) switch_shell ;;
254 7) exit 0 ;;
255 *) warn "Skipping invalid option: $choice" ;;
256 esac
257 echo
258 done
259 fi
260}
261
262# =============================
263# MAIN
264# =============================
265main() {
266 check_requirements
267 while true; do
268 show_menu
269 run_choices
270 read -p "Do you want to run more options? (y/n): " again
271 [[ "$again" =~ ^[Yy]$ ]] || break
272 done
273 ok "macOS minimal Zsh configuration complete!"
274}
275
276main "$@"
277
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/ai-commit-prompt.txt"
35 ".config/opencode/agents/ai-commit.md"
36)
37
38# =============================
39# OS DETECTION
40# =============================
41detect_os() {
42 if [[ "$OSTYPE" == "darwin"* ]]; then
43 echo "macos"
44 elif [ -f /etc/os-release ]; then
45 . /etc/os-release
46 echo "$ID"
47 else
48 echo "unknown"
49 fi
50}
51
52# =============================
53# REQUIREMENTS
54# =============================
55check_requirements() {
56 if [[ $EUID -eq 0 ]]; then
57 err "Do not run as root. The script will request sudo when necessary."
58 exit 1
59 fi
60 if [[ "$(detect_os)" != "macos" ]] && ! command -v sudo >/dev/null 2>&1; then
61 err "sudo required on Linux"
62 exit 1
63 fi
64}
65
66# =============================
67# INSTALLATION FUNCTIONS
68# =============================
69update_system() {
70 $SKIP_PACKAGES && return
71 log "Updating system..."
72 local os=$(detect_os)
73
74 case "$os" in
75 macos)
76 brew update || warn "Homebrew update failed; continuing installer"
77 ;;
78 ubuntu|debian)
79 sudo apt-get update -y
80 if ! sudo apt-get upgrade -y; then
81 warn "System upgrade did not complete. This can happen when apt wants to downgrade a package."
82 warn "Continuing because the Zsh setup does not require OS package upgrades to finish."
83 fi
84 ;;
85 fedora)
86 sudo dnf upgrade -y || warn "System upgrade failed; continuing installer"
87 ;;
88 arch)
89 sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer"
90 ;;
91 *) warn "Auto-update not supported for OS: $os" ;;
92 esac
93 ok "System update step finished"
94}
95
96install_packages() {
97 $SKIP_PACKAGES && return
98 log "Installing core packages..."
99 local os=$(detect_os)
100
101 case "$os" in
102 macos) brew install zsh git vim curl wget unzip xz ;;
103 ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;;
104 fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;;
105 arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;;
106 *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;;
107 esac
108 ok "Packages installed"
109}
110
111set_timezone() {
112 log "Setting timezone to Asia/Singapore..."
113 local os=$(detect_os)
114 if [[ "$os" == "macos" ]]; then
115 sudo systemsetup -settimezone Asia/Singapore >/dev/null
116 else
117 sudo timedatectl set-timezone Asia/Singapore
118 fi
119 ok "Timezone set to Asia/Singapore"
120}
121
122install_homebrew() {
123 ! $INSTALL_HOMEBREW && return
124 command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return
125 log "Installing Homebrew..."
126 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
127 ok "Homebrew installed"
128}
129
130configure_shell() {
131 $SKIP_SHELL_CHANGE && return
132 log "Changing default shell to zsh..."
133 local zsh_path
134 zsh_path="$(command -v zsh)"
135
136 if ! grep -qx "$zsh_path" /etc/shells; then
137 echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
138 fi
139 chsh -s "$zsh_path"
140 ok "Shell changed (requires logout/login to take effect)"
141}
142
143install_oh_my_zsh() {
144 log "Installing Oh My Zsh and plugins..."
145 export RUNZSH=no
146 export CHSH=no
147 export KEEP_ZSHRC=yes
148
149 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
150 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
151 else
152 ok "Oh My Zsh already installed"
153 fi
154
155 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
156 local plugin_dir="$custom/plugins"
157 mkdir -p "$plugin_dir"
158
159 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
160 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
161
162 ok "Oh My Zsh plugins installed to $plugin_dir"
163}
164
165install_starship() {
166 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
167 ok "Starship already installed"
168 else
169 log "Installing Starship prompt..."
170 if command -v brew >/dev/null 2>&1; then
171 brew install starship
172 else
173 mkdir -p "$HOME/.local/bin"
174 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
175 fi
176 ok "Starship installed"
177 fi
178}
179
180download_configs() {
181 log "Downloading custom config files from OpenGist..."
182 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
183 mkdir -p "$backup"
184
185 for f in "${CONFIG_FILES[@]}"; do
186 local remote_file="${f#.}"
187 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
188 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
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 "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 (Asia/Singapore)"
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 "Zsh installation/configuration complete!"
371}
372
373main "$@"
374
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/ai-commit-prompt.txt"
35 ".config/opencode/agents/ai-commit.md"
36)
37
38# =============================
39# PLATFORM DETECTION
40# =============================
41detect_os() {
42 if [ -f /etc/os-release ]; then
43 . /etc/os-release
44 echo "$ID"
45 else
46 echo "unknown"
47 fi
48}
49
50is_wsl() {
51 grep -qi "microsoft" /proc/version 2>/dev/null
52}
53
54# =============================
55# REQUIREMENTS
56# =============================
57check_requirements() {
58 if [[ $EUID -eq 0 ]]; then
59 err "Do not run as root. The script will request sudo when necessary."
60 exit 1
61 fi
62 if ! command -v sudo >/dev/null 2>&1; then
63 err "sudo required"
64 exit 1
65 fi
66 if ! is_wsl; then
67 err "This installer is intended for WSL"
68 exit 1
69 fi
70}
71
72# =============================
73# INSTALLATION FUNCTIONS
74# =============================
75update_system() {
76 $SKIP_PACKAGES && return
77 log "Updating system..."
78 local os=$(detect_os)
79
80 case "$os" in
81 ubuntu|debian)
82 sudo apt-get update -y
83 if ! sudo apt-get upgrade -y; then
84 warn "System upgrade did not complete. This can happen when apt wants to downgrade a package."
85 warn "Continuing because the Zsh setup does not require OS package upgrades to finish."
86 fi
87 ;;
88 fedora)
89 sudo dnf upgrade -y || warn "System upgrade failed; continuing installer"
90 ;;
91 arch)
92 sudo pacman -Syu --noconfirm || warn "System upgrade failed; continuing installer"
93 ;;
94 *) warn "Auto-update not supported for OS: $os" ;;
95 esac
96 ok "System update step finished"
97}
98
99install_packages() {
100 $SKIP_PACKAGES && return
101 log "Installing core packages..."
102 local os=$(detect_os)
103
104 case "$os" in
105 ubuntu|debian) sudo apt-get install -y zsh git vim curl wget unzip zip build-essential xz-utils ;;
106 fedora) sudo dnf install -y zsh git vim curl wget unzip zip @development-tools xz ;;
107 arch) sudo pacman -S --noconfirm zsh git vim curl wget unzip zip base-devel xz ;;
108 *) warn "Auto-install not supported for OS: $os. Please install zsh, git, vim, curl manually." ;;
109 esac
110 ok "Packages installed"
111}
112
113set_timezone() {
114 log "Checking timezone configuration..."
115 if command -v timedatectl >/dev/null 2>&1 && timedatectl status >/dev/null 2>&1; then
116 sudo timedatectl set-timezone Asia/Singapore
117 ok "Timezone set to Asia/Singapore"
118 return
119 fi
120
121 warn "Skipping timezone change: timedatectl is not available in this WSL environment"
122}
123
124install_homebrew() {
125 ! $INSTALL_HOMEBREW && return
126 command -v brew >/dev/null 2>&1 && ok "Homebrew already installed" && return
127 log "Installing Homebrew..."
128 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
129 ok "Homebrew installed"
130}
131
132configure_shell() {
133 $SKIP_SHELL_CHANGE && return
134 log "Changing default shell to zsh..."
135 local zsh_path
136 zsh_path="$(command -v zsh)"
137
138 if ! grep -qx "$zsh_path" /etc/shells; then
139 echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
140 fi
141 chsh -s "$zsh_path"
142 ok "Shell changed (open a new WSL session for it to take effect)"
143}
144
145install_oh_my_zsh() {
146 log "Installing Oh My Zsh and plugins..."
147 export RUNZSH=no
148 export CHSH=no
149 export KEEP_ZSHRC=yes
150
151 if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
152 sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
153 else
154 ok "Oh My Zsh already installed"
155 fi
156
157 local custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
158 local plugin_dir="$custom/plugins"
159 mkdir -p "$plugin_dir"
160
161 [[ -d "$plugin_dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir/zsh-autosuggestions"
162 [[ -d "$plugin_dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$plugin_dir/zsh-syntax-highlighting"
163
164 ok "Oh My Zsh plugins installed to $plugin_dir"
165}
166
167install_starship() {
168 if command -v starship >/dev/null 2>&1 || [[ -f "$HOME/.local/bin/starship" ]]; then
169 ok "Starship already installed"
170 else
171 log "Installing Starship prompt..."
172 if command -v brew >/dev/null 2>&1; then
173 brew install starship
174 else
175 mkdir -p "$HOME/.local/bin"
176 curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
177 fi
178 ok "Starship installed"
179 fi
180}
181
182download_configs() {
183 log "Downloading custom config files from OpenGist..."
184 local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
185 mkdir -p "$backup"
186
187 for f in "${CONFIG_FILES[@]}"; do
188 local remote_file="${f#.}"
189 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
190 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
191 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
192 local url="$GIST_RAW_BASE/$remote_file"
193 local target="$HOME/$f"
194 local tmp="${target}.tmp.$$"
195
196 mkdir -p "$(dirname "$target")"
197
198 if [[ -f "$target" ]]; then
199 cp "$target" "$backup/"
200 fi
201
202 log "Fetching $remote_file -> $f ..."
203 if curl -fsSL "$url" -o "$tmp"; then
204 mv "$tmp" "$target"
205 else
206 rm -f "$tmp"
207 warn "Failed to download $remote_file"
208 fi
209 done
210
211 ok "Configs downloaded (Backup at $backup)"
212}
213
214configure_zshrc() {
215 local zshrc="$HOME/.zshrc"
216 log "Configuring .zshrc for Oh My Zsh and Starship..."
217
218 touch "$zshrc"
219 cp "$zshrc" "$HOME/.zshrc.backup_$(date +%Y%m%d_%H%M%S)"
220
221 cat > "$zshrc" <<'EOF'
222# =============================================================================
223# 1. HELPER FUNCTIONS & PATH
224# =============================================================================
225source_if_readable() {
226 local file="$1"
227 if [[ -f "$file" && -r "$file" ]]; then
228 source "$file"
229 fi
230}
231
232export PATH="$HOME/.local/bin:$PATH"
233
234# =============================================================================
235# 2. OH MY ZSH FRAMEWORK
236# =============================================================================
237export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
238ZSH_THEME=""
239
240plugins=(
241 git
242 zsh-autosuggestions
243 zsh-syntax-highlighting
244)
245
246source_if_readable "$ZSH/oh-my-zsh.sh"
247
248# =============================================================================
249# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
250# =============================================================================
251source_if_readable "$HOME/.sourcerc"
252source_if_readable "$HOME/.func"
253source_if_readable "$HOME/.pathrc"
254source_if_readable "$HOME/.alias"
255
256# =============================================================================
257# 4. STARSHIP PROMPT
258# =============================================================================
259if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
260 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
261fi
262
263if command -v starship >/dev/null 2>&1; then
264 eval "$(starship init zsh)"
265elif [[ -x "$HOME/.local/bin/starship" ]]; then
266 eval "$("$HOME/.local/bin/starship" init zsh)"
267fi
268EOF
269
270 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
271}
272
273configure_git_identity() {
274 log "Configuring global Git identity..."
275 git config --global user.name "Vernon Wee Hong KOH"
276 git config --global user.email "261549452+weehong@users.noreply.github.com"
277 ok "Global Git identity configured"
278}
279
280switch_shell() {
281 log "Starting Zsh session..."
282 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
283 echo "----------------------------------------"
284 zsh -l
285 echo "----------------------------------------"
286 ok "Returned from Zsh session"
287}
288
289# =============================
290# INTERACTIVE MENU
291# =============================
292show_menu() {
293 echo "==========================================="
294 echo "WSL Minimal Zsh Installer - Choose what to do"
295 echo "==========================================="
296 echo " 0) Run ALL steps (1-11)"
297 echo " 1) Update system packages"
298 echo " 2) Install core packages (zsh, git, vim, etc.)"
299 echo " 3) Set Timezone (best effort)"
300 echo " 4) Install Homebrew"
301 echo " 5) Configure shell (chsh - sets default shell)"
302 echo " 6) Install Oh My Zsh + plugins"
303 echo " 7) Install Starship prompt"
304 echo " 8) Download custom configs (from OpenGist)"
305 echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)"
306 echo "10) Configure global Git identity"
307 echo "11) Switch to Zsh (Temporary Sub-shell)"
308 echo "12) Quit"
309 echo "==========================================="
310}
311
312run_choices() {
313 local input
314 read -p "Select: " input
315 input="${input//,/ }"
316
317 local -a to_run=()
318 local -a to_exclude=()
319
320 for item in $input; do
321 if [[ "$item" == !* ]]; then
322 to_exclude+=("${item:1}")
323 elif [[ "$item" == "0" ]]; then
324 to_run+=(1 2 3 4 5 6 7 8 9 10 11)
325 else
326 to_run+=("$item")
327 fi
328 done
329
330 for choice in "${to_run[@]}"; do
331 local skip=false
332
333 for ex in "${to_exclude[@]}"; do
334 if [[ "$choice" == "$ex" ]]; then
335 skip=true
336 break
337 fi
338 done
339
340 $skip && continue
341
342 case "$choice" in
343 1) update_system ;;
344 2) install_packages ;;
345 3) set_timezone ;;
346 4) install_homebrew ;;
347 5) configure_shell ;;
348 6) install_oh_my_zsh ;;
349 7) install_starship ;;
350 8) download_configs ;;
351 9) configure_zshrc ;;
352 10) configure_git_identity ;;
353 11) switch_shell ;;
354 12) log "Exiting..."; exit 0 ;;
355 *) warn "Skipping invalid option: $choice" ;;
356 esac
357 echo
358 done
359}
360
361# =============================
362# MAIN
363# =============================
364main() {
365 check_requirements
366 while true; do
367 show_menu
368 run_choices
369 read -p "Do you want to run more options? (y/n): " again
370 [[ "$again" =~ ^[Yy]$ ]] || break
371 done
372 ok "WSL Zsh installation/configuration complete!"
373}
374
375main "$@"
376
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