# ============================================================================= # CUSTOM FUNCTIONS # ============================================================================= # ----------------------------------------------------------------------------- # Function: funcs (List available .func functions) # ----------------------------------------------------------------------------- funcs() { local func_file="${1:-$HOME/.func}" if [ ! -f "$func_file" ]; then printf "Error: Function file '%s' not found.\n" "$func_file" >&2 return 1 fi printf "Available functions in %s:\n" "$func_file" awk ' /^# Function: / { line = $0 sub(/^# Function: /, "", line) name = line sub(/ .*/, "", name) desc = "" if (line ~ / \(.+\)$/) { desc = line sub(/^[^ ]+ \(/, "", desc) sub(/\)$/, "", desc) } if (desc != "") { printf " %-22s %s\n", name, desc } else { printf " %s\n", name } } ' "$func_file" } # ----------------------------------------------------------------------------- # Function: clip (Cross-platform clipboard) # ----------------------------------------------------------------------------- clip() { local cmd local args=() if command -v pbcopy >/dev/null 2>&1; then cmd="pbcopy" # macOS elif grep -qi "microsoft" /proc/version 2>/dev/null && command -v clip.exe >/dev/null 2>&1; then cmd="clip.exe" # WSL elif [ "$XDG_SESSION_TYPE" = "wayland" ] && command -v wl-copy >/dev/null 2>&1; then cmd="wl-copy" # Linux Wayland elif command -v xclip >/dev/null 2>&1; then cmd="xclip" # Linux X11 (Fallback 1) args=("-selection" "clipboard") elif command -v xsel >/dev/null 2>&1; then cmd="xsel" # Linux X11 (Fallback 2) args=("--clipboard" "--input") else printf "Error: No supported clipboard utility found.\n" >&2 return 1 fi if [ $# -gt 0 ]; then if [ -f "$1" ]; then "$cmd" "${args[@]}" < "$1" echo "Copied contents of '$1' to clipboard." else printf "Error: File '%s' not found.\n" "$1" >&2 return 1 fi else "$cmd" "${args[@]}" fi } # ----------------------------------------------------------------------------- # Function: open_file (Cross-platform file/directory opener) # ----------------------------------------------------------------------------- open_file() { local target="${1:-.}" if [[ "$OSTYPE" == "darwin"* ]]; then command open "$target" elif grep -qi "microsoft" /proc/version 2>/dev/null; then if command -v wslpath >/dev/null 2>&1 && command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$(wslpath -w "$target")" else printf "Error: 'wslpath' or 'explorer.exe' not found.\n" >&2 return 1 fi elif [[ "$OSTYPE" == "linux-gnu"* ]]; then if command -v xdg-open >/dev/null 2>&1; then xdg-open "$target" else printf "Error: 'xdg-open' not found.\n" >&2 return 1 fi fi } alias open='open_file' # ----------------------------------------------------------------------------- # Function: clear_history (Supports shell, Claude, Codex, and OpenCode) # ----------------------------------------------------------------------------- clear_history() { case "${1:-}" in claude) if [ -d "$HOME/.claude/projects" ]; then # Use 'yes' to skip prompts and -f to ignore non-existent files yes | rm -rf "$HOME/.claude/projects"/* echo "Claude project history/cache cleared." fi ;; codex) local codex_home="${CODEX_HOME:-$HOME/.codex}" local codex_session_file local codex_session_id mkdir -p "$codex_home" : > "$codex_home/history.jsonl" if command -v codex >/dev/null 2>&1; then find "$codex_home/sessions" "$codex_home/archived_sessions" -type f -name '*.jsonl' -print 2>/dev/null | while IFS= read -r codex_session_file; do codex_session_id="$(sed -n 's/.*"session_id":"\([^"]*\)".*/\1/p; q' "$codex_session_file")" if [ -n "$codex_session_id" ]; then codex delete --force "$codex_session_id" >/dev/null 2>&1 || true fi done fi rm -rf "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" mkdir -p "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots" echo "Codex history cleared." ;; opencode) local opencode_state_home="${XDG_STATE_HOME:-$HOME/.local/state}/opencode" local opencode_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/opencode" mkdir -p "$opencode_state_home" "$opencode_data_home" : > "$opencode_state_home/prompt-history.jsonl" rm -f \ "$opencode_data_home/opencode.db" \ "$opencode_data_home/opencode.db-shm" \ "$opencode_data_home/opencode.db-wal" rm -rf "$opencode_data_home/repos" "$opencode_data_home/log" mkdir -p "$opencode_data_home/repos" "$opencode_data_home/log" echo "OpenCode history cleared." ;; *) # 1. Truncate the file : > "$HISTFILE" # 2. Clear RAM by briefly setting history size to 0 local old_histsize=$HISTSIZE HISTSIZE=0 HISTSIZE=$old_histsize echo "Shell history cleared." ;; esac } # ----------------------------------------------------------------------------- # Function: require_cli (Check external CLI availability) # ----------------------------------------------------------------------------- require_cli() { local binary="$1" local label="$2" local resolved if command -v whence >/dev/null 2>&1; then if whence -p "$binary" >/dev/null 2>&1; then return 0 fi else resolved="$(command -v "$binary" 2>/dev/null)" || resolved="" if [ -n "$resolved" ] && [ -x "$resolved" ]; then return 0 fi fi printf "Error: %s ('%s') is not installed or not in your PATH.\n" "$label" "$binary" >&2 return 1 } # ----------------------------------------------------------------------------- # Function: claude (Includes --yolo and clear shortcuts) # ----------------------------------------------------------------------------- claude() { # Shortcut for clearing cache if [[ "$1" == "clear" ]]; then clear_history claude return 0 fi # Check if binary exists require_cli claude "Claude Code CLI" || return 1 # Handle --yolo mode if [[ "$1" == "--yolo" ]]; then shift command claude --dangerously-skip-permissions "$@" return $? fi command claude "$@" } # ----------------------------------------------------------------------------- # Internal: _prompt_confirm_indexing (Confirm project indexing via the terminal) # ----------------------------------------------------------------------------- _prompt_confirm_indexing() { local project_root="$1" local reply if ! (: /dev/null; then printf "Notice: No interactive terminal available; enhancing without project indexing.\n" >&2 return 1 fi printf "Allow Auggie to index and use project context from '%s'? (y/N) " "$project_root" >/dev/tty if ! IFS= read -r reply &2 return 1 fi case "$reply" in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac } # ----------------------------------------------------------------------------- # Internal: _enhance_prompt (Run Auggie and write only the enhanced prompt) # ----------------------------------------------------------------------------- _enhance_prompt() { local prompt="$1" local output_file="$2" local run_dir local prompt_file local workspace local cache_dir local auth_file local log_file local parsed_file local project_root local use_project_context local script_pid local waited local timeout_seconds local monitor_status if [ -z "$prompt" ]; then printf "Error: Prompt was empty.\n" >&2 return 1 fi if [ ! -s "$HOME/.augment/session.json" ]; then printf "Error: Auggie session file not found. Run 'auggie login' first.\n" >&2 return 1 fi run_dir="$(mktemp -d "${TMPDIR:-/tmp}/auggie-enhance.XXXXXX")" || return 1 prompt_file="$run_dir/prompt.txt" workspace="$run_dir/workspace" cache_dir="$run_dir/cache" auth_file="$HOME/.augment/session.json" log_file="$run_dir/auggie.log" parsed_file="$run_dir/enhanced.txt" mkdir -p "$workspace" "$cache_dir" printf "%s\n" "$prompt" > "$prompt_file" project_root="$(git rev-parse --show-toplevel 2>/dev/null)" || project_root="$PWD" use_project_context=0 timeout_seconds=90 if _prompt_confirm_indexing "$project_root"; then use_project_context=1 timeout_seconds=300 workspace="$project_root" cache_dir="$HOME/.augment" printf "Indexing approved; enhancing with project context from '%s'.\n" "$project_root" >&2 else printf "Enhancing without project indexing.\n" >&2 fi ( if [ "$use_project_context" -eq 1 ]; then AUGGIE_PROMPT_FILE="$prompt_file" \ AUGGIE_WORKSPACE="$workspace" \ AUGGIE_CACHE_DIR="$cache_dir" \ AUGGIE_AUTH_FILE="$auth_file" \ 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 2>&1 & else AUGGIE_PROMPT_FILE="$prompt_file" \ AUGGIE_WORKSPACE="$workspace" \ AUGGIE_CACHE_DIR="$cache_dir" \ AUGGIE_AUTH_FILE="$auth_file" \ 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 2>&1 & fi script_pid=$! waited=0 while kill -0 "$script_pid" >/dev/null 2>&1; do if grep -aq "🤖" "$log_file" 2>/dev/null || grep -aq "Tool call:" "$log_file" 2>/dev/null; then kill "$script_pid" >/dev/null 2>&1 || true wait "$script_pid" >/dev/null 2>&1 || true exit 0 fi if [ "$waited" -ge "$timeout_seconds" ]; then kill "$script_pid" >/dev/null 2>&1 || true wait "$script_pid" >/dev/null 2>&1 || true exit 124 fi sleep 1 waited=$((waited + 1)) done wait "$script_pid" >/dev/null 2>&1 || true exit 0 ) monitor_status=$? if [ "$monitor_status" -eq 124 ]; then printf "Error: Timed out waiting for Auggie to enhance the prompt.\n" >&2 rm -rf "$run_dir" return 124 fi perl -ne ' # Auggie redraws streamed terminal lines with carriage returns. Keep only # the final rendered segment so stale text cannot corrupt the prompt. s/\r$//; s/.*\r//; 1 while s/[^\x08]\x08//g; s/\x08//g; s/\e\][^\a]*(?:\a|\e\\)//g; s/\e\[[0-?]*[ -\/]*[@-~]//g; next if /Script started on/ || /Script done on/; if (/^(?:✨\s*)?Enhanced prompt:\s*(.*)$/) { $capturing = 1; $output .= "$1\n" if length $1; next; } next unless $capturing; exit if /^🤖/ || /Tool call:/ || /Session terminated/; $output .= $_; END { $output =~ s/^\s*\n//; $output =~ s/\s+\z//; print "$output\n" if length $output; } ' "$log_file" > "$parsed_file" if [ ! -s "$parsed_file" ]; then printf "Error: Auggie did not return an enhanced prompt.\n" >&2 rm -rf "$run_dir" return 1 fi if ! command cp "$parsed_file" "$output_file"; then printf "Error: Unable to save the enhanced prompt.\n" >&2 rm -rf "$run_dir" return 1 fi rm -rf "$run_dir" } # ----------------------------------------------------------------------------- # Function: prompt (Enhance a prompt with Auggie and optional project context) # ----------------------------------------------------------------------------- prompt() { local prompt local line local output_file require_cli auggie "Auggie CLI" || return 1 require_cli script "script utility" || return 1 if [ $# -gt 0 ]; then prompt="$*" elif [ ! -t 0 ]; then prompt="$(cat)" else echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished." prompt="" while IFS= read -r line; do [ "$line" = "EOF" ] && break if [ -z "$prompt" ]; then prompt="$line" else prompt="${prompt}"$'\n'"${line}" fi done fi if [ -z "$prompt" ]; then printf "Error: Prompt was empty.\n" >&2 return 1 fi output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1 if ! _enhance_prompt "$prompt" "$output_file"; then rm -f "$output_file" return 1 fi printf "Enhanced prompt:\n" command cat "$output_file" rm -f "$output_file" command auggie account status } # ----------------------------------------------------------------------------- # Function: plan_build (Claude Code multi-agent workflow orchestrator) # ----------------------------------------------------------------------------- plan_build() { local claude_args=() local use_yolo=0 local enhance_payload=0 while [ $# -gt 0 ]; do case "$1" in --yolo) if [ "$use_yolo" -eq 1 ]; then echo "Error: Duplicate argument: --yolo" echo "Usage: plan_build [--yolo] [--prompt]" return 1 fi use_yolo=1 claude_args=(--yolo) ;; --prompt) if [ "$enhance_payload" -eq 1 ]; then echo "Error: Duplicate argument: --prompt" echo "Usage: plan_build [--yolo] [--prompt]" return 1 fi enhance_payload=1 ;; *) echo "Error: Unknown argument: $1" echo "Usage: plan_build [--yolo] [--prompt]" return 1 ;; esac shift done require_cli claude "Claude Code CLI" || return 1 require_cli codex "Codex CLI" || return 1 require_cli coderabbit "CodeRabbit CLI" || return 1 if [ "$enhance_payload" -eq 1 ]; then require_cli auggie "Auggie CLI" || return 1 require_cli script "script utility" || return 1 fi echo "📥 Reading payload... Type 'EOF' on a new line and press Enter when finished." local payload local line payload="" while IFS= read -r line; do [ "$line" = "EOF" ] && break if [ -z "$payload" ]; then payload="$line" else payload="${payload}"$'\n'"${line}" fi done if [ -z "$payload" ]; then echo "❌ Error: Payload was empty." return 1 fi if [ "$enhance_payload" -eq 1 ]; then local enhanced_file local review_reply enhanced_file="$(mktemp "${TMPDIR:-/tmp}/plan-build-enhanced.XXXXXX")" || return 1 echo "✨ Enhancing payload with Auggie..." if ! _enhance_prompt "$payload" "$enhanced_file"; then rm -f "$enhanced_file" echo "❌ Error: Prompt enhancement failed; Claude was not launched." return 1 fi payload="$(cat "$enhanced_file")" rm -f "$enhanced_file" if [ -z "$payload" ]; then echo "❌ Error: Enhanced payload was empty; Claude was not launched." return 1 fi printf '\n%s\n' "━━━━━━━━━━━━━━━━ Auggie enhanced prompt ━━━━━━━━━━━━━━━━" printf '%s\n' "$payload" printf '%s\n\n' "━━━━━━━━━━━━━━━━ End enhanced prompt ━━━━━━━━━━━━━━━━━" if ! (: /dev/null; then echo "❌ Error: Cannot review the enhanced prompt without an interactive terminal; Claude was not launched." return 1 fi printf "Proceed with this enhanced prompt? (y/N) " >/dev/tty if ! IFS= read -r review_reply /dev/tty echo "🛑 Review cancelled; Claude was not launched." return 1 fi case "$review_reply" in y|Y|yes|YES|Yes) echo "✅ Enhanced prompt approved." ;; *) echo "🛑 Enhanced prompt not approved; Claude was not launched." return 0 ;; esac fi echo "🚀 Launching Claude Code with your multi-agent workflow..." claude "${claude_args[@]}" "Please read \`~/.claude/skills/plan-build/SKILL.md\` and strictly follow the 7-step multi-agent workflow to implement the following task: $payload" } # ----------------------------------------------------------------------------- # Function: aicommit (Automated Conventional Commit Engine Wrapper) # ----------------------------------------------------------------------------- aicommit() { local diff engine prompt # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000) if [ -z "$diff" ]; then printf "❌ Error: Nothing staged to commit.\n" >&2 return 1 fi if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2 printf "Please create it and paste your system prompt inside.\n" >&2 return 1 fi engine=${1:-claude} prompt=$(cat "$HOME/.config/ai-commit-prompt.txt") case "$engine" in claude) echo "🤖 Claude is analyzing staged changes..." cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \ | command claude --allowedTools 'Bash(git commit *)' --permission-mode dontAsk -p \ "Read the provided instructions and diff, then generate and execute the commit." ;; codex) echo "🤖 Codex is analyzing staged changes..." cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \ | command codex --ask-for-approval never exec --sandbox workspace-write --ephemeral \ "Read the provided instructions and diff, then generate and execute the commit." ;; copilot) echo "🤖 Copilot is analyzing staged changes..." copilot -p "$prompt Here is the git diff: $diff" -s --no-ask-user --allow-tool='shell(git:*)' ;; opencode) echo "🤖 OpenCode is analyzing staged changes..." cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | opencode run ;; *) printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2 printf "Usage: aicommit [claude | codex | copilot | opencode]\n" >&2 return 1 ;; esac } # ----------------------------------------------------------------------------- # Function: create (Efficiently create file + parent dirs) # ----------------------------------------------------------------------------- create() { if ! command -v install >/dev/null 2>&1; then printf "Error: 'install' coreutil is not available.\n" >&2 return 1 fi if [ $# -eq 0 ]; then printf "Usage: create [file ...]\n" >&2 return 2 fi for p in "$@"; do # Expand tilde manually if shell doesn't [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}" if [ -z "$p" ] || [ "$p" = "/" ]; then printf "create: refusing to operate on '%s'\n" "$p" >&2 continue fi if [ -e "$p" ]; then printf "create: '%s' already exists. Skipping.\n" "$p" >&2 continue fi if install -D /dev/null "$p"; then echo "Created '$p'" fi done } # ----------------------------------------------------------------------------- # Function: kill_port (Kill or check the process using a port) # ----------------------------------------------------------------------------- kill_port() { local action="kill" local signal="TERM" local port local pids local pid case "${1:-}" in -c|--check) action="check" shift ;; -k|--kill) action="kill" shift ;; -f|--force) action="kill" signal="KILL" shift ;; -h|--help|"") printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] \n" >&2 return 1 ;; esac port="${1:-}" if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then printf "Error: port must be a number between 1 and 65535.\n" >&2 printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] \n" >&2 return 1 fi if [[ "$OSTYPE" == "darwin"* ]]; then pids="$( { lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null lsof -nP -tiUDP:"$port" 2>/dev/null } | sort -u )" if [ -z "$pids" ]; then printf "Port %s is FREE\n" "$port" return 0 fi printf "Port %s is in use:\n" "$port" lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null else local port_hex local inodes port_hex="$(printf "%04X" "$port")" inodes="$( awk -v port="$port_hex" ' NR > 1 { split($2, local_addr, ":") if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) { print $10 } } ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u )" if [ -z "$inodes" ]; then printf "Port %s is FREE\n" "$port" return 0 fi pids="" pids="$( find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null | awk -v inodes="$inodes" ' BEGIN { split(inodes, inode_list, " ") for (i in inode_list) { wanted["socket:[" inode_list[i] "]"] = 1 } } $2 in wanted { split($1, path, "/") print path[3] } ' | sort -u )" if [ -z "$pids" ]; then printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2 return 1 fi printf "Port %s is in use:\n" "$port" for pid in $pids; do if [ -r "/proc/$pid/cmdline" ]; then printf " PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")" else printf " PID %s\n" "$pid" fi done fi if [ "$action" = "kill" ]; then for pid in $pids; do if kill "-$signal" "$pid" 2>/dev/null; then printf "Killed PID %s with SIG%s.\n" "$pid" "$signal" else printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2 return 1 fi done fi } # ----------------------------------------------------------------------------- # Function: append_path (Adds to session PATH if exists) # ----------------------------------------------------------------------------- append_path() { local dir="$1" if [ -d "$dir" ]; then case ":$PATH:" in *":$dir:"*) ;; *) PATH="$PATH:$dir" ;; esac elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then echo "Warning: $dir does not exist." >&2 fi } # ----------------------------------------------------------------------------- # Function: add_path_to_config (Fixed awk/sed injection) # ----------------------------------------------------------------------------- add_path_to_config() { local new_path="$1" local config_file="$HOME/.bashrc" [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc" if [ -z "$new_path" ]; then printf "Usage: add_path_to_config \n" >&2 return 1 fi new_path="${new_path/#\~/$HOME}" if [ ! -d "$new_path" ]; then printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path" read -r REPLY [[ ! $REPLY =~ ^[Yy]$ ]] && return 1 fi # Check if already exists in file if grep -Fq "append_path \"$new_path\"" "$config_file"; then printf "Notice: Already in %s.\n" "$config_file" return 0 fi # Robust Injection: Use sed to insert before export PATH or just append if grep -q "^export PATH" "$config_file"; then sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file" else echo "append_path \"$new_path\"" >> "$config_file" echo "export PATH" >> "$config_file" fi printf "✅ Success: Added to %s\n" "$config_file" append_path "$new_path" && export PATH }