最後活躍 1 day ago

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

修訂 47964c0114d285ba2f77ac6921cd889723ae9959

README.md 原始檔案

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 原始檔案
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 原始檔案

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 原始檔案
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 gsts='git stash push --staged'
29# Keep staged changes in the index; the stash also records their snapshot.
30alias gstu='git stash push --keep-index --include-untracked'
31alias gsta='git stash push --include-untracked'
32alias grmrf='git checkout -- . && git clean -fd'
33
34# Create a feature worktree from origin/integration: gwa pill
35gwa() {
36 if [ "$#" -ne 1 ] || [ -z "$1" ]; then
37 printf 'Usage: gwa <feature-name>\n' >&2
38 return 1
39 fi
40 git worktree add "../worktree/$1" -b "feature/$1" origin/integration
41}
42
43# List loaded aliases, optionally filtered by a case-insensitive keyword.
44ah() {
45 if [ "$#" -eq 0 ]; then
46 alias
47 else
48 alias | command grep -iF -- "$*"
49 fi
50}
51
config.sh 原始檔案
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 原始檔案
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 if [ "$#" -gt 1 ]; then
109 printf "Usage: clear_history [--all | shell | claude | codex | opencode]\n" >&2
110 return 2
111 fi
112 case "${1:-}" in
113 --all)
114 local result=0 target
115 for target in claude codex opencode shell; do
116 clear_history "$target" || result=1
117 done
118
119 if [ -t 1 ]; then
120 command clear
121 fi
122 return "$result"
123 ;;
124
125 claude)
126 local claude_home="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
127 if [ -d "$claude_home/projects" ]; then
128 command rm -rf -- "$claude_home/projects"/*(DN) || return 1
129 fi
130 if [ -f "$claude_home/history.jsonl" ]; then
131 : > "$claude_home/history.jsonl" || return 1
132 fi
133 echo "Claude project history/cache cleared."
134 ;;
135
136 codex)
137 local codex_home="${CODEX_HOME:-$HOME/.codex}"
138 command mkdir -p -- "$codex_home" || return 1
139 : > "$codex_home/history.jsonl" || return 1
140
141 command rm -rf -- "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" || return 1
142 command mkdir -p -- "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" || return 1
143
144 echo "Codex history cleared."
145 ;;
146
147 opencode)
148 local opencode_state_home="${XDG_STATE_HOME:-$HOME/.local/state}/opencode"
149 local opencode_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/opencode"
150
151 command mkdir -p -- "$opencode_state_home" "$opencode_data_home" || return 1
152 : > "$opencode_state_home/prompt-history.jsonl" || return 1
153 command rm -f -- \
154 "$opencode_data_home/opencode.db" \
155 "$opencode_data_home/opencode.db-shm" \
156 "$opencode_data_home/opencode.db-wal" || return 1
157 command rm -rf -- "$opencode_data_home/repos" "$opencode_data_home/log" "$opencode_data_home/snapshot" || return 1
158 command mkdir -p -- "$opencode_data_home/repos" "$opencode_data_home/log" "$opencode_data_home/snapshot" || return 1
159
160 echo "OpenCode history cleared."
161 ;;
162
163 ""|shell)
164 # 1. Truncate the file
165 if [ -n "${HISTFILE:-}" ]; then
166 : > "$HISTFILE" || return 1
167 fi
168
169 # 2. Start a fresh Zsh history context. HISTSIZE=0 retains one entry.
170 builtin fc -p "${HISTFILE:-}" "$HISTSIZE" "$SAVEHIST" || return 1
171
172 echo "Shell history cleared."
173 ;;
174 *)
175 printf "Usage: clear_history [--all | shell | claude | codex | opencode]\n" >&2
176 return 2
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 $?
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 local input_keymap
402 local input_status
403
404 require_cli auggie "Auggie CLI" || return 1
405 require_cli script "script utility" || return 1
406
407 if [ $# -gt 0 ]; then
408 prompt="$*"
409 elif [ ! -t 0 ]; then
410 prompt="$(cat)"
411 elif [ -n "${ZSH_VERSION:-}" ]; then
412 # Keep all lines in one ZLE buffer so previous lines remain editable.
413 input_keymap="auggie-prompt-$$-$RANDOM"
414 bindkey -N "$input_keymap" emacs || return 1
415 bindkey -M "$input_keymap" '^J' self-insert
416 bindkey -M "$input_keymap" '^M' self-insert-unmeta
417 bindkey -M "$input_keymap" '^[[13;2u' accept-line
418 bindkey -M "$input_keymap" '^[[27;2;13~' accept-line
419 bindkey -M "$input_keymap" '^D' accept-line
420 printf 'Enter your prompt: Enter adds a line; Shift+Enter (configured terminal) or Ctrl+D submits; Ctrl+C cancels.\n'
421 prompt=""
422 vared -M "$input_keymap" -p '> ' prompt
423 input_status=$?
424 bindkey -D "$input_keymap"
425 [ "$input_status" -eq 0 ] || return "$input_status"
426 else
427 echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished."
428 prompt=""
429
430 while IFS= read -r line; do
431 [ "$line" = "EOF" ] && break
432 if [ -z "$prompt" ]; then
433 prompt="$line"
434 else
435 prompt="${prompt}"$'\n'"${line}"
436 fi
437 done
438 fi
439
440 if [ -z "$prompt" ]; then
441 printf "Error: Prompt was empty.\n" >&2
442 return 1
443 fi
444
445 output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1
446 if ! _enhance_prompt "$prompt" "$output_file"; then
447 rm -f "$output_file"
448 return 1
449 fi
450
451 if [ -t 1 ]; then
452 clear
453 fi
454
455 printf "Enhanced prompt:\n"
456 command cat "$output_file"
457 if clip < "$output_file"; then
458 printf '\nEnhanced prompt copied to clipboard.\n' >&2
459 else
460 printf '\nUnable to copy to clipboard; the enhanced prompt is shown above.\n' >&2
461 fi
462 rm -f "$output_file"
463
464 command auggie account status
465}
466
467# -----------------------------------------------------------------------------
468# Function: plan_build (Cached canonical plan-build launcher)
469# -----------------------------------------------------------------------------
470plan_build() {
471 emulate -L zsh
472 setopt localtraps
473 local base_url="${PLAN_BUILD_BASE_URL:-https://opengist.resetrix.work/weehong/plan-build/raw/HEAD}"
474 local cache_dir="${PLAN_BUILD_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/plan-build}"
475 local curl_command="${PLAN_BUILD_CURL_COMMAND:-curl}"
476 local skill_file="$HOME/.claude/skills/plan-build/SKILL.md"
477 local architect_file="$HOME/.claude/skills/plan-build-architect/SKILL.md"
478 local stage_dir="" release_dir="" current_tmp=""
479 local skill_tmp="" architect_tmp="" skill_backup="" architect_backup=""
480 local activation_lock="$cache_dir/.activate.lock"
481 local activation_lock_fd=""
482 local selected_release="" active_pid="" refresh_rc=1 call_rc=0 interrupted_rc=0
483
484 if ! zmodload zsh/system 2>/dev/null; then
485 printf "Error: Unable to load the Zsh system module required by plan-build.\n" >&2
486 return 1
487 fi
488
489 require_cli zsh "Zsh" || return 1
490
491 if ! command mkdir -p "$cache_dir" "${skill_file:h}" "${architect_file:h}"; then
492 printf "Error: Unable to create plan-build cache or skill directories.\n" >&2
493 return 1
494 fi
495
496 _plan_build_cleanup() {
497 if [[ -n "${active_pid:-}" ]] && command kill -0 "$active_pid" 2>/dev/null; then
498 command kill "$active_pid" 2>/dev/null || true
499 wait "$active_pid" 2>/dev/null || true
500 fi
501 active_pid=""
502 [[ -n "$stage_dir" && -d "$stage_dir" ]] && command rm -rf -- "$stage_dir"
503 command rm -f -- "$current_tmp" "$skill_tmp" "$architect_tmp"
504 command rm -f -- "$skill_backup" "$architect_backup"
505 if [[ -n "${activation_lock_fd:-}" ]]; then
506 zsystem flock -u "$activation_lock_fd" 2>/dev/null || true
507 activation_lock_fd=""
508 fi
509 }
510 _plan_build_cancel() {
511 interrupted_rc="$1"
512 [[ -n "${active_pid:-}" ]] && command kill "$active_pid" 2>/dev/null || true
513 }
514 trap '_plan_build_cleanup' EXIT
515 trap '_plan_build_cancel 129' HUP
516 trap '_plan_build_cancel 130' INT
517 trap '_plan_build_cancel 143' TERM
518
519 _plan_build_validate_release() {
520 local candidate="$1"
521 [[ -d "$candidate" &&
522 -s "$candidate/plan_build.zsh" &&
523 -s "$candidate/SKILL.md" &&
524 -s "$candidate/ARCHITECT.md" ]] &&
525 command zsh -n "$candidate/plan_build.zsh"
526 }
527
528 _plan_build_replace_path() {
529 local replacement="$1" destination="$2"
530 if command mv -fT "$replacement" "$destination" 2>/dev/null; then
531 return 0
532 fi
533 command mv -fh "$replacement" "$destination"
534 }
535
536 _plan_build_select_current() {
537 local resolved
538 [[ -L "$cache_dir/current" ]] || return 1
539 resolved="$cache_dir/current"
540 resolved="${resolved:A}"
541 _plan_build_validate_release "$resolved" || return 1
542 selected_release="$resolved"
543 }
544
545 _plan_build_acquire_activation_lock() {
546 (( interrupted_rc )) && return 1
547 command touch "$activation_lock" || return 1
548 zsystem flock -t 10 -f activation_lock_fd "$activation_lock"
549 }
550
551 _plan_build_release_activation_lock() {
552 [[ -n "$activation_lock_fd" ]] || return 0
553 zsystem flock -u "$activation_lock_fd" || return 1
554 activation_lock_fd=""
555 }
556
557 _plan_build_download() {
558 local remote="$1" destination="$2" download_rc
559 command "$curl_command" -fsSL "$remote" -o "$destination" &
560 active_pid=$!
561 wait "$active_pid"
562 download_rc=$?
563 active_pid=""
564 (( interrupted_rc )) && return "$interrupted_rc"
565 return "$download_rc"
566 }
567
568 _plan_build_backup_path() {
569 local source="$1" backup="$2"
570 [[ -e "$source" || -L "$source" ]] || return 0
571 command rm -f -- "$backup" && command cp -RP "$source" "$backup"
572 }
573
574 _plan_build_restore_path() {
575 local destination="$1" backup="$2" had_old="$3"
576 if (( had_old )); then
577 _plan_build_replace_path "$backup" "$destination" || return 1
578 else
579 command rm -f -- "$destination"
580 fi
581 }
582
583 _plan_build_install_skill_links() {
584 local activation_ok=1
585 local skill_had_old=0 architect_had_old=0
586
587 skill_tmp="$(command mktemp "${skill_file}.link.XXXXXX")" &&
588 command rm -f "$skill_tmp" &&
589 command ln -s "$cache_dir/current/SKILL.md" "$skill_tmp" || skill_tmp=""
590 architect_tmp="$(command mktemp "${architect_file}.link.XXXXXX")" &&
591 command rm -f "$architect_tmp" &&
592 command ln -s "$cache_dir/current/ARCHITECT.md" "$architect_tmp" || architect_tmp=""
593 [[ -n "$skill_tmp" && -L "$skill_tmp" &&
594 -n "$architect_tmp" && -L "$architect_tmp" ]] || return 1
595
596 skill_backup="${skill_file}.backup.$$.$RANDOM"
597 architect_backup="${architect_file}.backup.$$.$RANDOM"
598 [[ -e "$skill_file" || -L "$skill_file" ]] && skill_had_old=1
599 [[ -e "$architect_file" || -L "$architect_file" ]] && architect_had_old=1
600 _plan_build_backup_path "$skill_file" "$skill_backup" || activation_ok=0
601 (( activation_ok )) && _plan_build_backup_path "$architect_file" "$architect_backup" || activation_ok=0
602 (( activation_ok && ! interrupted_rc )) &&
603 _plan_build_replace_path "$skill_tmp" "$skill_file" || activation_ok=0
604 (( activation_ok )) && skill_tmp=""
605 (( activation_ok && ! interrupted_rc )) &&
606 _plan_build_replace_path "$architect_tmp" "$architect_file" || activation_ok=0
607 (( activation_ok )) && architect_tmp=""
608
609 if (( activation_ok )); then
610 command rm -f -- "$skill_backup" "$architect_backup"
611 skill_backup="" architect_backup=""
612 return 0
613 fi
614
615 if ! _plan_build_restore_path "$skill_file" "$skill_backup" "$skill_had_old"; then
616 printf "Error: Unable to restore plan-build skill; backup preserved at %s.\n" "$skill_backup" >&2
617 skill_backup=""
618 fi
619 if ! _plan_build_restore_path "$architect_file" "$architect_backup" "$architect_had_old"; then
620 printf "Error: Unable to restore architect skill; backup preserved at %s.\n" "$architect_backup" >&2
621 architect_backup=""
622 fi
623 return 1
624 }
625
626 if (( $+commands[$curl_command] )) || [[ "$curl_command" == */* && -x "$curl_command" ]]; then
627 stage_dir="$(command mktemp -d "$cache_dir/.stage.XXXXXX")" || stage_dir=""
628 if [[ -n "$stage_dir" ]]; then
629 _plan_build_download "$base_url/plan_build.zsh" "$stage_dir/plan_build.zsh" &&
630 _plan_build_download "$base_url/SKILL.md" "$stage_dir/SKILL.md" &&
631 _plan_build_download "$base_url/ARCHITECT.md" "$stage_dir/ARCHITECT.md"
632 refresh_rc=$?
633 if (( interrupted_rc )); then
634 _plan_build_cleanup
635 trap - EXIT HUP INT TERM
636 return "$interrupted_rc"
637 fi
638 else
639 refresh_rc=1
640 fi
641 if (( refresh_rc == 0 )) &&
642 _plan_build_validate_release "$stage_dir"; then
643 release_dir="$(command mktemp -d "$cache_dir/release.XXXXXX")" || release_dir=""
644 if [[ -n "$release_dir" ]] &&
645 command rmdir "$release_dir" &&
646 command mv "$stage_dir" "$release_dir" &&
647 _plan_build_acquire_activation_lock; then
648 stage_dir=""
649 release_dir="${release_dir:A}"
650 current_tmp="$(command mktemp "$cache_dir/.current.XXXXXX")" &&
651 command rm -f "$current_tmp" &&
652 command ln -s "$release_dir" "$current_tmp" || current_tmp=""
653
654 if [[ -n "$current_tmp" && -L "$current_tmp" ]] &&
655 _plan_build_install_skill_links &&
656 _plan_build_replace_path "$current_tmp" "$cache_dir/current"; then
657 current_tmp=""
658 selected_release="$release_dir"
659 fi
660 _plan_build_release_activation_lock || true
661 fi
662 fi
663 fi
664
665 if (( interrupted_rc )); then
666 _plan_build_cleanup
667 trap - EXIT HUP INT TERM
668 return "$interrupted_rc"
669 fi
670
671 if [[ -z "$selected_release" ]]; then
672 if ! _plan_build_select_current; then
673 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
674 printf "Error: curl is required because no valid cached plan-build bundle is available.\n" >&2
675 else
676 printf "Error: Unable to refresh plan-build and no valid cached bundle is available.\n" >&2
677 fi
678 return 1
679 fi
680 if (( ! $+commands[$curl_command] )) && [[ ! ( "$curl_command" == */* && -x "$curl_command" ) ]]; then
681 printf "Warning: curl is unavailable; using the validated cached plan-build bundle.\n" >&2
682 else
683 printf "Warning: Unable to refresh plan-build; using the validated cached bundle.\n" >&2
684 fi
685 fi
686
687 if [[ "$(command readlink "$skill_file" 2>/dev/null)" != "$cache_dir/current/SKILL.md" ||
688 "$(command readlink "$architect_file" 2>/dev/null)" != "$cache_dir/current/ARCHITECT.md" ]]; then
689 if ! _plan_build_acquire_activation_lock || ! _plan_build_install_skill_links; then
690 _plan_build_release_activation_lock 2>/dev/null || true
691 if (( interrupted_rc )); then
692 _plan_build_cleanup
693 trap - EXIT HUP INT TERM
694 return "$interrupted_rc"
695 fi
696 printf "Error: Unable to install the plan-build skill links.\n" >&2
697 return 1
698 fi
699 _plan_build_release_activation_lock || true
700 fi
701
702 if (( interrupted_rc )); then
703 _plan_build_cleanup
704 trap - EXIT HUP INT TERM
705 return "$interrupted_rc"
706 fi
707
708 PLAN_BUILD_SKILL_PATH="$selected_release/SKILL.md" \
709 PLAN_BUILD_ARCHITECT_SKILL_PATH="$selected_release/ARCHITECT.md" \
710 command zsh "$selected_release/plan_build.zsh" "$@" &
711 active_pid=$!
712 wait "$active_pid"
713 call_rc=$?
714 active_pid=""
715 (( interrupted_rc )) && call_rc="$interrupted_rc"
716 _plan_build_cleanup
717 trap - EXIT HUP INT TERM
718 return "$call_rc"
719}
720
721# -----------------------------------------------------------------------------
722# Function: aicommit (Automated Conventional Commit Engine Wrapper)
723# -----------------------------------------------------------------------------
724aicommit() {
725 local diff engine prompt
726 engine=${1:-claude}
727 case "$engine" in
728 claude|copilot|opencode) ;;
729 *)
730 printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2
731 printf "Usage: aicommit [claude | copilot | opencode]\n" >&2
732 return 1
733 ;;
734 esac
735
736 # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes
737 diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000)
738
739 if [ -z "$diff" ]; then
740 printf "❌ Error: Nothing staged to commit.\n" >&2
741 return 1
742 fi
743
744 if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then
745 printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2
746 printf "Please create it and paste your system prompt inside.\n" >&2
747 return 1
748 fi
749
750 prompt=$(cat "$HOME/.config/ai-commit-prompt.txt")
751
752 case "$engine" in
753 claude)
754 echo "🤖 Claude is analyzing staged changes..."
755 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
756 | command claude --allowedTools 'Bash(git commit *)' --permission-mode dontAsk -p \
757 "Read the provided instructions and diff, then generate and execute the commit."
758 ;;
759
760 copilot)
761 echo "🤖 Copilot is analyzing staged changes..."
762 copilot -p "$prompt
763
764Here is the git diff:
765$diff" -s --no-ask-user --allow-tool='shell(git:*)'
766 ;;
767
768 opencode)
769 echo "🤖 OpenCode is analyzing staged changes..."
770 cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | opencode run --agent ai-commit
771 ;;
772
773 *)
774 printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2
775 printf "Usage: aicommit [claude | copilot | opencode]\n" >&2
776 return 1
777 ;;
778 esac
779}
780
781# -----------------------------------------------------------------------------
782# Function: create (Efficiently create file + parent dirs)
783# -----------------------------------------------------------------------------
784create() {
785 if ! command -v install >/dev/null 2>&1; then
786 printf "Error: 'install' coreutil is not available.\n" >&2
787 return 1
788 fi
789
790 if [ $# -eq 0 ]; then
791 printf "Usage: create <file> [file ...]\n" >&2
792 return 2
793 fi
794
795 for p in "$@"; do
796 # Expand tilde manually if shell doesn't
797 [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}"
798
799 if [ -z "$p" ] || [ "$p" = "/" ]; then
800 printf "create: refusing to operate on '%s'\n" "$p" >&2
801 continue
802 fi
803
804 if [ -e "$p" ]; then
805 printf "create: '%s' already exists. Skipping.\n" "$p" >&2
806 continue
807 fi
808
809 if install -D /dev/null "$p"; then
810 echo "Created '$p'"
811 fi
812 done
813}
814
815# -----------------------------------------------------------------------------
816# Function: kill_port (Kill or check the process using a port)
817# -----------------------------------------------------------------------------
818kill_port() {
819 local action="kill"
820 local signal="TERM"
821 local port
822 local pids
823 local pid
824
825 case "${1:-}" in
826 -c|--check)
827 action="check"
828 shift
829 ;;
830 -k|--kill)
831 action="kill"
832 shift
833 ;;
834 -f|--force)
835 action="kill"
836 signal="KILL"
837 shift
838 ;;
839 -h|--help|"")
840 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
841 return 1
842 ;;
843 esac
844
845 port="${1:-}"
846 if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
847 printf "Error: port must be a number between 1 and 65535.\n" >&2
848 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
849 return 1
850 fi
851
852 if [[ "$OSTYPE" == "darwin"* ]]; then
853 pids="$(
854 {
855 lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null
856 lsof -nP -tiUDP:"$port" 2>/dev/null
857 } | sort -u
858 )"
859
860 if [ -z "$pids" ]; then
861 printf "Port %s is FREE\n" "$port"
862 return 0
863 fi
864
865 printf "Port %s is in use:\n" "$port"
866 lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null
867 else
868 local port_hex
869 local inodes
870
871 port_hex="$(printf "%04X" "$port")"
872 inodes="$(
873 awk -v port="$port_hex" '
874 NR > 1 {
875 split($2, local_addr, ":")
876 if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) {
877 print $10
878 }
879 }
880 ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u
881 )"
882
883 if [ -z "$inodes" ]; then
884 printf "Port %s is FREE\n" "$port"
885 return 0
886 fi
887
888 pids=""
889 pids="$(
890 find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null |
891 awk -v inodes="$inodes" '
892 BEGIN {
893 split(inodes, inode_list, " ")
894 for (i in inode_list) {
895 wanted["socket:[" inode_list[i] "]"] = 1
896 }
897 }
898 $2 in wanted {
899 split($1, path, "/")
900 print path[3]
901 }
902 ' | sort -u
903 )"
904
905 if [ -z "$pids" ]; then
906 printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2
907 return 1
908 fi
909
910 printf "Port %s is in use:\n" "$port"
911 for pid in $pids; do
912 if [ -r "/proc/$pid/cmdline" ]; then
913 printf " PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")"
914 else
915 printf " PID %s\n" "$pid"
916 fi
917 done
918 fi
919
920 if [ "$action" = "kill" ]; then
921 for pid in $pids; do
922 if kill "-$signal" "$pid" 2>/dev/null; then
923 printf "Killed PID %s with SIG%s.\n" "$pid" "$signal"
924 else
925 printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2
926 return 1
927 fi
928 done
929 fi
930}
931
932# -----------------------------------------------------------------------------
933# Function: append_path (Adds to session PATH if exists)
934# -----------------------------------------------------------------------------
935append_path() {
936 local dir="$1"
937 if [ -d "$dir" ]; then
938 case ":$PATH:" in
939 *":$dir:"*) ;;
940 *) PATH="$PATH:$dir" ;;
941 esac
942 elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then
943 echo "Warning: $dir does not exist." >&2
944 fi
945}
946
947# -----------------------------------------------------------------------------
948# Function: add_path_to_config (Fixed awk/sed injection)
949# -----------------------------------------------------------------------------
950add_path_to_config() {
951 local new_path="$1"
952 local config_file="$HOME/.bashrc"
953 [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc"
954
955 if [ -z "$new_path" ]; then
956 printf "Usage: add_path_to_config <directory>\n" >&2
957 return 1
958 fi
959
960 new_path="${new_path/#\~/$HOME}"
961
962 if [ ! -d "$new_path" ]; then
963 printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path"
964 read -r REPLY
965 [[ ! $REPLY =~ ^[Yy]$ ]] && return 1
966 fi
967
968 # Check if already exists in file
969 if grep -Fq "append_path \"$new_path\"" "$config_file"; then
970 printf "Notice: Already in %s.\n" "$config_file"
971 return 0
972 fi
973
974 # Robust Injection: Use sed to insert before export PATH or just append
975 if grep -q "^export PATH" "$config_file"; then
976 sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file"
977 else
978 echo "append_path \"$new_path\"" >> "$config_file"
979 echo "export PATH" >> "$config_file"
980 fi
981
982 printf "✅ Success: Added to %s\n" "$config_file"
983 append_path "$new_path" && export PATH
984}
985
pathrc 原始檔案
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"
12export PNPM_HOME="$HOME/.local/share/pnpm"
13
14# 3. PATH INITIALIZATION
15# -----------------------------------------------------------------------------
16append_path "$HOME/.local/bin"
17append_path "$DOTNET_ROOT"
18append_path "$DOTNET_ROOT/tools"
19append_path "$HOME/.opencode/bin"
20append_path "$HOME/Flutter/bin"
21append_path "$PNPM_HOME/bin"
22
23# macOS specific paths
24if [[ "$OSTYPE" == "darwin"* ]]; then
25 append_path "/Applications/Espanso.app/Contents/MacOS"
26fi
27
28# Finalize PATH
29export PATH
sourcerc 原始檔案
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# SDKMAN (Java/Kotlin/Scala Version Manager)
15# =============================================================================
16export SDKMAN_DIR="$HOME/.sdkman"
17if [[ -f "$SDKMAN_DIR/bin/sdkman-init.sh" && -r "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then
18 source "$SDKMAN_DIR/bin/sdkman-init.sh"
19fi
20
21# =============================================================================
22# NVM (Node Version Manager)
23# =============================================================================
24export NVM_DIR="$HOME/.nvm"
25if [[ -f "$NVM_DIR/nvm.sh" && -r "$NVM_DIR/nvm.sh" ]]; then
26 source "$NVM_DIR/nvm.sh"
27fi
28if [[ -f "$NVM_DIR/bash_completion" && -r "$NVM_DIR/bash_completion" ]]; then
29 source "$NVM_DIR/bash_completion"
30fi
31
32# =============================================================================
33# GOOGLE CLOUD SDK
34# =============================================================================
35if [[ -f "/opt/google-cloud-sdk/path.zsh.inc" ]]; then
36 source "/opt/google-cloud-sdk/path.zsh.inc"
37fi
38
39if [[ -f "/opt/google-cloud-sdk/completion.zsh.inc" ]]; then
40 source "/opt/google-cloud-sdk/completion.zsh.inc"
41fi
42
starship.toml 原始檔案
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 原始檔案
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 原始檔案
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 原始檔案
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 for f in "${CONFIG_FILES[@]}"; do
91 # Strip the leading dot for the download URL
92 local remote_file="${f#.}"
93 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
94 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
95 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
96 local url="$GIST_RAW_BASE/$remote_file"
97 local target="$HOME/$f"
98 local tmp="${target}.tmp.$$"
99
100 mkdir -p "$(dirname "$target")"
101
102 log "Fetching $remote_file -> $f ..."
103 if curl -fsSL "$url" -o "$tmp"; then
104 mv "$tmp" "$target"
105 else
106 rm -f "$tmp"
107 warn "Failed to download $remote_file"
108 fi
109 done
110
111 ok "Configs downloaded"
112}
113
114configure_zshrc() {
115 local zshrc="$HOME/.zshrc"
116 log "Configuring .zshrc for Oh My Zsh and Starship..."
117
118 cat > "$zshrc" <<'EOF'
119# =============================================================================
120# 1. HELPER FUNCTIONS & PATH
121# =============================================================================
122source_if_readable() {
123 local file="$1"
124 if [[ -f "$file" && -r "$file" ]]; then
125 source "$file"
126 fi
127}
128
129export PATH="$HOME/.local/bin:$PATH"
130
131# =============================================================================
132# 2. OH MY ZSH FRAMEWORK
133# =============================================================================
134export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
135ZSH_THEME=""
136
137plugins=(
138 git
139 zsh-autosuggestions
140 zsh-syntax-highlighting
141)
142
143source_if_readable "$ZSH/oh-my-zsh.sh"
144
145# =============================================================================
146# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
147# =============================================================================
148source_if_readable "$HOME/.sourcerc"
149source_if_readable "$HOME/.func"
150source_if_readable "$HOME/.pathrc"
151source_if_readable "$HOME/.alias"
152
153# =============================================================================
154# 4. STARSHIP PROMPT
155# =============================================================================
156if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
157 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
158fi
159
160if command -v starship >/dev/null 2>&1; then
161 eval "$(starship init zsh)"
162elif [[ -x "$HOME/.local/bin/starship" ]]; then
163 eval "$("$HOME/.local/bin/starship" init zsh)"
164fi
165EOF
166
167 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
168}
169
170configure_git_identity() {
171 log "Configuring global Git identity..."
172 git config --global user.name "Vernon Wee Hong KOH"
173 git config --global user.email "261549452+weehong@users.noreply.github.com"
174 ok "Global Git identity configured"
175}
176
177switch_shell() {
178 log "Starting Zsh session..."
179 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
180 echo "----------------------------------------"
181 zsh -l
182 echo "----------------------------------------"
183 ok "Returned from Zsh session"
184}
185
186# =============================
187# INTERACTIVE MENU
188# =============================
189show_menu() {
190 echo "==========================================="
191 echo "macOS Minimal Zsh Setup - Choose what to do"
192 echo "==========================================="
193 echo " 0) Run ALL steps (1-6)"
194 echo " 1) Install Oh My Zsh + plugins"
195 echo " 2) Install Starship prompt"
196 echo " 3) Download custom configs (~/.alias, .func, .vimrc, etc.)"
197 echo " 4) Configure ~/.zshrc (Oh My Zsh + Starship)"
198 echo " 5) Configure global Git identity"
199 echo " 6) Switch to Zsh (Temporary Sub-shell)"
200 echo " 7) Quit"
201 echo "==========================================="
202}
203
204run_choices() {
205 local input
206 read -p "Select: " input
207 input="${input//,/ }"
208
209 local -a to_run=()
210 local -a to_exclude=()
211
212 for item in $input; do
213 if [[ "$item" == !* ]]; then
214 to_exclude+=("${item:1}")
215 elif [[ "$item" == "0" ]]; then
216 to_run+=(1 2 3 4 5 6)
217 else
218 to_run+=("$item")
219 fi
220 done
221
222 if [[ ${#to_run[@]} -gt 0 ]]; then
223 for choice in "${to_run[@]}"; do
224 local skip=false
225
226 if [[ ${#to_exclude[@]} -gt 0 ]]; then
227 for ex in "${to_exclude[@]}"; do
228 if [[ "$choice" == "$ex" ]]; then
229 skip=true
230 break
231 fi
232 done
233 fi
234
235 $skip && continue
236
237 case "$choice" in
238 1) install_oh_my_zsh ;;
239 2) install_starship ;;
240 3) download_configs ;;
241 4) configure_zshrc ;;
242 5) configure_git_identity ;;
243 6) switch_shell ;;
244 7) exit 0 ;;
245 *) warn "Skipping invalid option: $choice" ;;
246 esac
247 echo
248 done
249 fi
250}
251
252# =============================
253# MAIN
254# =============================
255main() {
256 check_requirements
257 while true; do
258 show_menu
259 run_choices
260 read -p "Do you want to run more options? (y/n): " again
261 [[ "$again" =~ ^[Yy]$ ]] || break
262 done
263 ok "macOS minimal Zsh configuration complete!"
264}
265
266main "$@"
267
zsh_ubuntu.sh 原始檔案
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 for f in "${CONFIG_FILES[@]}"; do
183 local remote_file="${f#.}"
184 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
185 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
186 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
187 local url="$GIST_RAW_BASE/$remote_file"
188 local target="$HOME/$f"
189 local tmp="${target}.tmp.$$"
190
191 mkdir -p "$(dirname "$target")"
192
193 log "Fetching $remote_file -> $f ..."
194 if curl -fsSL "$url" -o "$tmp"; then
195 mv "$tmp" "$target"
196 else
197 rm -f "$tmp"
198 warn "Failed to download $remote_file"
199 fi
200 done
201
202 ok "Configs downloaded"
203}
204
205configure_zshrc() {
206 local zshrc="$HOME/.zshrc"
207 log "Configuring .zshrc for Oh My Zsh and Starship..."
208
209 cat > "$zshrc" <<'EOF'
210# =============================================================================
211# 1. HELPER FUNCTIONS & PATH
212# =============================================================================
213source_if_readable() {
214 local file="$1"
215 if [[ -f "$file" && -r "$file" ]]; then
216 source "$file"
217 fi
218}
219
220export PATH="$HOME/.local/bin:$PATH"
221
222# =============================================================================
223# 2. OH MY ZSH FRAMEWORK
224# =============================================================================
225export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
226ZSH_THEME=""
227
228plugins=(
229 git
230 zsh-autosuggestions
231 zsh-syntax-highlighting
232)
233
234source_if_readable "$ZSH/oh-my-zsh.sh"
235
236# =============================================================================
237# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
238# =============================================================================
239source_if_readable "$HOME/.sourcerc"
240source_if_readable "$HOME/.func"
241source_if_readable "$HOME/.pathrc"
242source_if_readable "$HOME/.alias"
243
244# =============================================================================
245# 4. STARSHIP PROMPT
246# =============================================================================
247if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
248 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
249fi
250
251if command -v starship >/dev/null 2>&1; then
252 eval "$(starship init zsh)"
253elif [[ -x "$HOME/.local/bin/starship" ]]; then
254 eval "$("$HOME/.local/bin/starship" init zsh)"
255fi
256EOF
257
258 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
259}
260
261configure_git_identity() {
262 log "Configuring global Git identity..."
263 git config --global user.name "Vernon Wee Hong KOH"
264 git config --global user.email "261549452+weehong@users.noreply.github.com"
265 ok "Global Git identity configured"
266}
267
268switch_shell() {
269 log "Starting Zsh session..."
270 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
271 echo "----------------------------------------"
272 zsh -l
273 echo "----------------------------------------"
274 ok "Returned from Zsh session"
275}
276
277# =============================
278# INTERACTIVE MENU
279# =============================
280show_menu() {
281 echo "==========================================="
282 echo "Minimal Zsh Installer - Choose what to do"
283 echo "==========================================="
284 echo " 0) Run ALL steps (1-11)"
285 echo " 1) Update system packages"
286 echo " 2) Install core packages (zsh, git, vim, etc.)"
287 echo " 3) Set Timezone (Asia/Singapore)"
288 echo " 4) Install Homebrew"
289 echo " 5) Configure shell (chsh - sets default shell)"
290 echo " 6) Install Oh My Zsh + plugins"
291 echo " 7) Install Starship prompt"
292 echo " 8) Download custom configs (from OpenGist)"
293 echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)"
294 echo "10) Configure global Git identity"
295 echo "11) Switch to Zsh (Temporary Sub-shell)"
296 echo "12) Quit"
297 echo "==========================================="
298}
299
300run_choices() {
301 local input
302 read -p "Select: " input
303 input="${input//,/ }"
304
305 local -a to_run=()
306 local -a to_exclude=()
307
308 for item in $input; do
309 if [[ "$item" == !* ]]; then
310 to_exclude+=("${item:1}")
311 elif [[ "$item" == "0" ]]; then
312 to_run+=(1 2 3 4 5 6 7 8 9 10 11)
313 else
314 to_run+=("$item")
315 fi
316 done
317
318 for choice in "${to_run[@]}"; do
319 local skip=false
320
321 for ex in "${to_exclude[@]}"; do
322 if [[ "$choice" == "$ex" ]]; then
323 skip=true
324 break
325 fi
326 done
327
328 $skip && continue
329
330 case "$choice" in
331 1) update_system ;;
332 2) install_packages ;;
333 3) set_timezone ;;
334 4) install_homebrew ;;
335 5) configure_shell ;;
336 6) install_oh_my_zsh ;;
337 7) install_starship ;;
338 8) download_configs ;;
339 9) configure_zshrc ;;
340 10) configure_git_identity ;;
341 11) switch_shell ;;
342 12) log "Exiting..."; exit 0 ;;
343 *) warn "Skipping invalid option: $choice" ;;
344 esac
345 echo
346 done
347}
348
349# =============================
350# MAIN
351# =============================
352main() {
353 check_requirements
354 while true; do
355 show_menu
356 run_choices
357 read -p "Do you want to run more options? (y/n): " again
358 [[ "$again" =~ ^[Yy]$ ]] || break
359 done
360 ok "Zsh installation/configuration complete!"
361}
362
363main "$@"
364
zsh_wsl.sh 原始檔案
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 for f in "${CONFIG_FILES[@]}"; do
185 local remote_file="${f#.}"
186 [[ "$f" == ".config/starship.toml" ]] && remote_file="starship.toml"
187 [[ "$f" == ".config/ai-commit-prompt.txt" ]] && remote_file="ai-commit-prompt.txt"
188 [[ "$f" == ".config/opencode/agents/ai-commit.md" ]] && remote_file="ai-commit.md"
189 local url="$GIST_RAW_BASE/$remote_file"
190 local target="$HOME/$f"
191 local tmp="${target}.tmp.$$"
192
193 mkdir -p "$(dirname "$target")"
194
195 log "Fetching $remote_file -> $f ..."
196 if curl -fsSL "$url" -o "$tmp"; then
197 mv "$tmp" "$target"
198 else
199 rm -f "$tmp"
200 warn "Failed to download $remote_file"
201 fi
202 done
203
204 ok "Configs downloaded"
205}
206
207configure_zshrc() {
208 local zshrc="$HOME/.zshrc"
209 log "Configuring .zshrc for Oh My Zsh and Starship..."
210
211 cat > "$zshrc" <<'EOF'
212# =============================================================================
213# 1. HELPER FUNCTIONS & PATH
214# =============================================================================
215source_if_readable() {
216 local file="$1"
217 if [[ -f "$file" && -r "$file" ]]; then
218 source "$file"
219 fi
220}
221
222export PATH="$HOME/.local/bin:$PATH"
223
224# =============================================================================
225# 2. OH MY ZSH FRAMEWORK
226# =============================================================================
227export ZSH="${ZSH:-$HOME/.oh-my-zsh}"
228ZSH_THEME=""
229
230plugins=(
231 git
232 zsh-autosuggestions
233 zsh-syntax-highlighting
234)
235
236source_if_readable "$ZSH/oh-my-zsh.sh"
237
238# =============================================================================
239# 3. THIRD-PARTY INITIALIZATION & CUSTOM CONFIGS
240# =============================================================================
241source_if_readable "$HOME/.sourcerc"
242source_if_readable "$HOME/.func"
243source_if_readable "$HOME/.pathrc"
244source_if_readable "$HOME/.alias"
245
246# =============================================================================
247# 4. STARSHIP PROMPT
248# =============================================================================
249if [[ -z "${STARSHIP_CONFIG:-}" && -f "$HOME/.config/starship.toml" ]]; then
250 export STARSHIP_CONFIG="$HOME/.config/starship.toml"
251fi
252
253if command -v starship >/dev/null 2>&1; then
254 eval "$(starship init zsh)"
255elif [[ -x "$HOME/.local/bin/starship" ]]; then
256 eval "$("$HOME/.local/bin/starship" init zsh)"
257fi
258EOF
259
260 ok ".zshrc configured for Oh My Zsh framework with Starship prompt"
261}
262
263configure_git_identity() {
264 log "Configuring global Git identity..."
265 git config --global user.name "Vernon Wee Hong KOH"
266 git config --global user.email "261549452+weehong@users.noreply.github.com"
267 ok "Global Git identity configured"
268}
269
270switch_shell() {
271 log "Starting Zsh session..."
272 echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
273 echo "----------------------------------------"
274 zsh -l
275 echo "----------------------------------------"
276 ok "Returned from Zsh session"
277}
278
279# =============================
280# INTERACTIVE MENU
281# =============================
282show_menu() {
283 echo "==========================================="
284 echo "WSL Minimal Zsh Installer - Choose what to do"
285 echo "==========================================="
286 echo " 0) Run ALL steps (1-11)"
287 echo " 1) Update system packages"
288 echo " 2) Install core packages (zsh, git, vim, etc.)"
289 echo " 3) Set Timezone (best effort)"
290 echo " 4) Install Homebrew"
291 echo " 5) Configure shell (chsh - sets default shell)"
292 echo " 6) Install Oh My Zsh + plugins"
293 echo " 7) Install Starship prompt"
294 echo " 8) Download custom configs (from OpenGist)"
295 echo " 9) Configure ~/.zshrc (Oh My Zsh + Starship)"
296 echo "10) Configure global Git identity"
297 echo "11) Switch to Zsh (Temporary Sub-shell)"
298 echo "12) Quit"
299 echo "==========================================="
300}
301
302run_choices() {
303 local input
304 read -p "Select: " input
305 input="${input//,/ }"
306
307 local -a to_run=()
308 local -a to_exclude=()
309
310 for item in $input; do
311 if [[ "$item" == !* ]]; then
312 to_exclude+=("${item:1}")
313 elif [[ "$item" == "0" ]]; then
314 to_run+=(1 2 3 4 5 6 7 8 9 10 11)
315 else
316 to_run+=("$item")
317 fi
318 done
319
320 for choice in "${to_run[@]}"; do
321 local skip=false
322
323 for ex in "${to_exclude[@]}"; do
324 if [[ "$choice" == "$ex" ]]; then
325 skip=true
326 break
327 fi
328 done
329
330 $skip && continue
331
332 case "$choice" in
333 1) update_system ;;
334 2) install_packages ;;
335 3) set_timezone ;;
336 4) install_homebrew ;;
337 5) configure_shell ;;
338 6) install_oh_my_zsh ;;
339 7) install_starship ;;
340 8) download_configs ;;
341 9) configure_zshrc ;;
342 10) configure_git_identity ;;
343 11) switch_shell ;;
344 12) log "Exiting..."; exit 0 ;;
345 *) warn "Skipping invalid option: $choice" ;;
346 esac
347 echo
348 done
349}
350
351# =============================
352# MAIN
353# =============================
354main() {
355 check_requirements
356 while true; do
357 show_menu
358 run_choices
359 read -p "Do you want to run more options? (y/n): " again
360 [[ "$again" =~ ^[Yy]$ ]] || break
361 done
362 ok "WSL Zsh installation/configuration complete!"
363}
364
365main "$@"
366
zshrc 原始檔案
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