diff --git a/aiwb b/aiwb index 59bc020..d26dd0b 100755 --- a/aiwb +++ b/aiwb @@ -6,6 +6,15 @@ set -o pipefail +# ============================================================================ +# CWD SAFETY (Termux/Android) +# ============================================================================ +# When spawned from a bot or after an Android app switch the original CWD may +# no longer exist, causing pwd/getcwd to fail. Fix it early. +if ! pwd >/dev/null 2>&1; then + cd "${HOME}" 2>/dev/null || cd / 2>/dev/null || true +fi + # ============================================================================ # INTERRUPT HANDLING # ============================================================================ @@ -33,8 +42,8 @@ cleanup_on_interrupt() { # Also try pkill as backup for any orphaned curl processes pkill -P $$ curl 2>/dev/null || true - # Clean up temp files - rm -f /tmp/aiwb_curl_* 2>/dev/null || true + # Clean up temp files (respect $TMPDIR for Termux) + rm -f "${TMPDIR:-/tmp}"/aiwb_curl_* 2>/dev/null || true echo "Goodbye!" exit 130 # Standard exit code for SIGINT @@ -3169,10 +3178,31 @@ cmd_doctor() { success "Health check complete" } +# Helper: copy the latest output file to the --output destination. +# Called both on success and on failure/timeout so partial output is preserved. +_headless_copy_output() { + local output="$1" + [[ -z "$output" ]] && return 0 + + local workspace output_dir latest + workspace="$(get_workspace)" + output_dir="$workspace/outputs" + latest=$(find "$output_dir" -name "*.md" ! -name "*.feedback.md" -type f 2>/dev/null | sort -r | head -1) + if [[ -n "$latest" ]]; then + cp "$latest" "$output" + msg "Output copied to: $output" + return 0 + else + err "headless: no output file found to copy" + return 1 + fi +} + # Headless (non-interactive) command handler for bot/CI use cmd_headless() { local mode="" prompt="" instruct="" model="" provider="" model_name="" local verifier="" check_instruct="" context="" output="" + local timeout_secs="${AIWB_HEADLESS_TIMEOUT}" # Parse flags while [[ $# -gt 0 ]]; do @@ -3187,6 +3217,7 @@ cmd_headless() { --check-instruct) check_instruct="$2"; shift 2 ;; --context) context="$2"; shift 2 ;; --output) output="$2"; shift 2 ;; + --timeout) timeout_secs="$2"; shift 2 ;; *) err "Unknown headless flag: $1"; return 1 ;; esac done @@ -3250,24 +3281,47 @@ cmd_headless() { fi fi + # --- Start watchdog timer for headless timeout --- + local _watchdog_pid="" + if [[ -n "$timeout_secs" ]] && [[ "$timeout_secs" -gt 0 ]] 2>/dev/null; then + debug "headless: pipeline timeout set to ${timeout_secs}s" + ( + sleep "$timeout_secs" 2>/dev/null + # Time's up — try to save whatever output exists, then signal parent + warn "headless: pipeline timed out after ${timeout_secs}s" + kill -USR1 $$ 2>/dev/null || true + ) & + _watchdog_pid=$! + # Trap USR1: on timeout, copy partial output then exit + trap ' + warn "headless: timed out — saving partial output" + _headless_copy_output "'"$output"'" 2>/dev/null || true + kill '"$_watchdog_pid"' 2>/dev/null || true + exit 124 + ' USR1 + fi + # Run init_mode "$mode" - mode_run + local run_exit=0 + mode_run || run_exit=$? - # Copy output to --output path if requested + # Cancel watchdog if still running + if [[ -n "$_watchdog_pid" ]]; then + kill "$_watchdog_pid" 2>/dev/null || true + wait "$_watchdog_pid" 2>/dev/null || true + fi + + # Copy output to --output path if requested. + # Always attempt this — even if mode_run failed (e.g. verifier timed out), + # the generator output may already be saved. Partial success > no output. if [[ -n "$output" ]]; then - local workspace output_dir latest - workspace="$(get_workspace)" - output_dir="$workspace/outputs" - latest=$(find "$output_dir" -name "*.md" ! -name "*.feedback.md" -type f 2>/dev/null | sort -r | head -1) - if [[ -n "$latest" ]]; then - cp "$latest" "$output" - msg "Output copied to: $output" - else - err "headless: no output file found to copy" - return 1 + if ! _headless_copy_output "$output"; then + [[ $run_exit -eq 0 ]] && run_exit=1 fi fi + + return $run_exit } # GitHub command handler diff --git a/aiwb_headless b/aiwb_headless index 7d551be..a8972d1 100755 --- a/aiwb_headless +++ b/aiwb_headless @@ -20,9 +20,19 @@ # --check-instruct path to check instruction file # --context path to context file or directory # --output copy output to this file path +# --timeout pipeline timeout in seconds (default: $AIWB_HEADLESS_TIMEOUT or 300) +# +# Environment variables: +# TMPDIR temp directory (Termux: set to writable path, e.g. $HOME/.clide/tmp) +# AIWB_HEADLESS_TIMEOUT default timeout for the full headless pipeline (seconds) export AIWB_HEADLESS=1 +# Ensure CWD exists (Termux may have a stale CWD after app switches) +if ! pwd >/dev/null 2>&1; then + cd "${HOME}" 2>/dev/null || cd / 2>/dev/null || true +fi + # Resolve real path to aiwb sibling script SCRIPT_DIR="$(cd "$(dirname "$(realpath "${BASH_SOURCE[0]}")")" && pwd)" diff --git a/clear-old-context.sh b/clear-old-context.sh index 823fa9d..d16efb5 100755 --- a/clear-old-context.sh +++ b/clear-old-context.sh @@ -18,8 +18,8 @@ if [[ -f "$HISTORY_FILE" ]]; then rm -f "$HISTORY_FILE" fi -# Also clear any temp context files -find /tmp -name "aiwb_git_audit_*" -delete 2>/dev/null +# Also clear any temp context files (respect $TMPDIR for Termux) +find "${TMPDIR:-/tmp}" -name "aiwb_git_audit_*" -delete 2>/dev/null echo "✅ Old context cleared!" echo "" diff --git a/lib/api.sh b/lib/api.sh index 7fb9b4b..40461b8 100644 --- a/lib/api.sh +++ b/lib/api.sh @@ -300,7 +300,7 @@ call_gemini() { # Handle large prompts by using a temp file instead of command-line args local request_body - local prompt_file=$(mktemp) + local prompt_file=$(aiwb_mktemp) echo -n "$prompt" > "$prompt_file" request_body=$(jq -n \ @@ -318,9 +318,9 @@ call_gemini() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -489,9 +489,9 @@ call_gemini_vision() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -613,7 +613,7 @@ call_claude() { local url="https://api.anthropic.com/v1/messages" # Handle large prompts by using a temp file - local prompt_file=$(mktemp -t aiwb_prompt_XXXXXX) + local prompt_file=$(aiwb_mktemp -t aiwb_prompt_XXXXXX) echo -n "$prompt" > "$prompt_file" local request_body @@ -632,9 +632,9 @@ call_claude() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -770,9 +770,9 @@ call_claude_vision() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -868,7 +868,7 @@ call_openai() { local url="https://api.openai.com/v1/chat/completions" # Handle large prompts by using a temp file - local prompt_file=$(mktemp -t aiwb_prompt_XXXXXX) + local prompt_file=$(aiwb_mktemp -t aiwb_prompt_XXXXXX) echo -n "$prompt" > "$prompt_file" local request_body @@ -887,9 +887,9 @@ call_openai() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -1011,7 +1011,7 @@ call_groq() { local url="https://api.groq.com/openai/v1/chat/completions" # Handle large prompts by using a temp file - local prompt_file=$(mktemp -t aiwb_prompt_XXXXXX) + local prompt_file=$(aiwb_mktemp -t aiwb_prompt_XXXXXX) echo -n "$prompt" > "$prompt_file" local request_body @@ -1030,9 +1030,9 @@ call_groq() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -1147,7 +1147,7 @@ call_xai() { local url="https://api.x.ai/v1/chat/completions" # Handle large prompts by using a temp file - local prompt_file=$(mktemp -t aiwb_prompt_XXXXXX) + local prompt_file=$(aiwb_mktemp -t aiwb_prompt_XXXXXX) echo -n "$prompt" > "$prompt_file" local request_body @@ -1166,9 +1166,9 @@ call_xai() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" @@ -1283,7 +1283,7 @@ call_ollama() { local url="$endpoint/api/generate" # Handle large prompts by using a temp file - local prompt_file=$(mktemp -t aiwb_prompt_XXXXXX) + local prompt_file=$(aiwb_mktemp -t aiwb_prompt_XXXXXX) echo -n "$prompt" > "$prompt_file" local request_body @@ -1299,9 +1299,9 @@ call_ollama() { local response local curl_error curl_output request_file - curl_error=$(mktemp -t aiwb_curl_err_XXXXXX) - curl_output=$(mktemp -t aiwb_curl_out_XXXXXX) - request_file=$(mktemp -t aiwb_curl_req_XXXXXX) + curl_error=$(aiwb_mktemp -t aiwb_curl_err_XXXXXX) + curl_output=$(aiwb_mktemp -t aiwb_curl_out_XXXXXX) + request_file=$(aiwb_mktemp -t aiwb_curl_req_XXXXXX) # Write request body to file to avoid ARG_MAX limits echo "$request_body" > "$request_file" diff --git a/lib/common.sh b/lib/common.sh index 0ea5093..6ba736e 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -270,6 +270,32 @@ spinner() { printf "\r" } +# ============================================================================ +# TEMP FILE UTILITIES (Termux-safe: respects $TMPDIR) +# ============================================================================ + +# Termux-safe mktemp wrapper. On Termux /tmp is read-only, so callers +# must honour $TMPDIR (which Clide sets to $HOME/.clide/tmp). +# Usage mirrors mktemp: +# aiwb_mktemp -> creates a temp file +# aiwb_mktemp -d -> creates a temp directory +# aiwb_mktemp -t NAME -> creates a temp file with template NAME.XXXXXX +aiwb_mktemp() { + # Ensure TMPDIR directory exists (Clide may set it before the dir is created) + if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "$TMPDIR" ]]; then + mkdir -p "$TMPDIR" 2>/dev/null || true + fi + # mktemp honours TMPDIR automatically on all platforms + mktemp "$@" +} + +# Get the platform-appropriate temp directory path +get_tmp_dir() { + local tmp="${TMPDIR:-/tmp}" + [[ ! -d "$tmp" ]] && mkdir -p "$tmp" 2>/dev/null || true + echo "$tmp" +} + # ============================================================================ # PATH UTILITIES # ============================================================================ @@ -298,6 +324,23 @@ get_workspace() { fi } +# Get the current working directory safely. +# On Termux, the CWD may have been deleted (e.g. after an app switch). +# Falls back to the workspace directory or $HOME. +safe_cwd() { + local cwd + cwd="$(pwd 2>/dev/null)" && [[ -d "$cwd" ]] && { echo "$cwd"; return 0; } + # CWD is gone — try reasonable fallbacks + local fallback="${AIWB_WORKSPACE:-${HOME}}" + if [[ -d "$fallback" ]]; then + cd "$fallback" 2>/dev/null || true + echo "$fallback" + else + cd "$HOME" 2>/dev/null || true + echo "$HOME" + fi +} + # Ensure directory exists ensure_dir() { local dir="$1" @@ -331,7 +374,7 @@ json_set() { local value="$3" local tmp - tmp="$(mktemp)" + tmp="$(aiwb_mktemp)" jq --arg k "$key" --arg v "$value" '.[$k] = $v' "$file" > "$tmp" && mv "$tmp" "$file" } diff --git a/lib/config.sh b/lib/config.sh index 99dbfe6..fd1e5f5 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -15,6 +15,7 @@ readonly AIWB_MAX_TOKENS_DEFAULT=16000 # Default max tokens for API calls readonly AIWB_TEMPERATURE_DEFAULT=0.2 # Default temperature (0.0-1.0, lower = more focused) readonly AIWB_API_TIMEOUT=300 # API call timeout in seconds (5 minutes) readonly AIWB_API_CONNECT_TIMEOUT=10 # Connection timeout in seconds +readonly AIWB_HEADLESS_TIMEOUT="${AIWB_HEADLESS_TIMEOUT:-300}" # Headless pipeline timeout (seconds) # Logging Configuration readonly AIWB_LOG_RETENTION=10 # Number of log files to keep @@ -125,7 +126,7 @@ init_workspace() { config_file="$(get_config_file)" if [[ -f "$config_file" ]]; then local tmp - tmp="$(mktemp)" + tmp="$(aiwb_mktemp)" jq --arg ws "$workspace" '.workspace = $ws' "$config_file" > "$tmp" && mv "$tmp" "$config_file" fi fi @@ -214,7 +215,7 @@ load_config() { if [[ -z "$current_ws" || "$current_ws" == "null" ]]; then local tmp - tmp="$(mktemp)" + tmp="$(aiwb_mktemp)" jq --arg ws "$workspace" '.workspace = $ws' "$config_file" > "$tmp" && mv "$tmp" "$config_file" fi } @@ -248,7 +249,7 @@ config_set() { [[ ! -f "$config_file" ]] && load_config local tmp - tmp="$(mktemp)" + tmp="$(aiwb_mktemp)" # Handle nested keys (e.g., "preferences.auto_estimate") if [[ "$key" == *.* ]]; then diff --git a/lib/context_state.sh b/lib/context_state.sh index 91171e1..91ae619 100644 --- a/lib/context_state.sh +++ b/lib/context_state.sh @@ -74,7 +74,7 @@ context_state_touch() { if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) jq --arg ts "$(date -Iseconds)" '.updated_at = $ts' "$context_state_file" > "$tmp_file" mv "$tmp_file" "$context_state_file" chmod 600 "$context_state_file" @@ -117,7 +117,7 @@ context_state_add_file() { if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) jq --arg path "$file_path" \ --arg type "$context_type" \ --arg ts "$(date -Iseconds)" \ @@ -148,7 +148,7 @@ context_state_remove_file() { if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) jq --arg path "$file_path" \ --arg ts "$(date -Iseconds)" \ '.context_files = [.context_files[] | select(.path != $path)] | .updated_at = $ts' \ @@ -194,7 +194,7 @@ context_state_save_scan() { if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) jq --arg type "$scan_type" \ --arg file "$output_file" \ --arg count "$file_count" \ @@ -227,7 +227,7 @@ context_state_add_message() { if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) # Escape content for JSON local escaped_content @@ -440,7 +440,7 @@ context_state_save_from_mode() { # Clear existing context files if command -v jq &>/dev/null; then local tmp_file - tmp_file=$(mktemp) + tmp_file=$(aiwb_mktemp) jq '.context_files = []' "$context_state_file" > "$tmp_file" mv "$tmp_file" "$context_state_file" chmod 600 "$context_state_file" diff --git a/lib/editor.sh b/lib/editor.sh index 8a8e99e..70b74b9 100644 --- a/lib/editor.sh +++ b/lib/editor.sh @@ -21,17 +21,18 @@ AIWB_REPO_ENABLED=false # Detect if we're in a git repository OR any directory detect_repo() { # Default to current directory - ANY folder can be contextualized + # Use safe_cwd to handle missing CWD on Termux AIWB_REPO_ENABLED=true - AIWB_REPO_PATH="$(pwd)" - AIWB_REPO_NAME="$(basename "$AIWB_REPO_PATH")" + AIWB_REPO_PATH="$(safe_cwd)" + AIWB_REPO_NAME="$(basename "$AIWB_REPO_PATH" 2>/dev/null || echo "workspace")" AIWB_REPO_BRANCH="local" AIWB_REPO_REMOTE="" # Check if git is available and if we're in a git repo if have git && git rev-parse --git-dir >/dev/null 2>&1; then # Override with git info if available - AIWB_REPO_PATH="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" - AIWB_REPO_NAME="$(basename "$AIWB_REPO_PATH" 2>/dev/null || basename "$(pwd)")" + AIWB_REPO_PATH="$(git rev-parse --show-toplevel 2>/dev/null || safe_cwd)" + AIWB_REPO_NAME="$(basename "$AIWB_REPO_PATH" 2>/dev/null || echo "workspace")" AIWB_REPO_BRANCH="$(git branch --show-current 2>/dev/null || echo "local")" AIWB_REPO_REMOTE="$(git remote get-url origin 2>/dev/null || echo "")" fi @@ -85,7 +86,7 @@ read_file() { elif is_repo_mode; then full_path="$AIWB_REPO_PATH/$file_path" else - full_path="$(pwd)/$file_path" + full_path="$(safe_cwd)/$file_path" fi if [[ ! -f "$full_path" ]]; then @@ -108,7 +109,7 @@ write_file() { elif is_repo_mode; then full_path="$AIWB_REPO_PATH/$file_path" else - full_path="$(pwd)/$file_path" + full_path="$(safe_cwd)/$file_path" fi # Create directory if needed @@ -131,7 +132,7 @@ list_files() { if is_repo_mode; then base_path="$AIWB_REPO_PATH/$path" else - base_path="$(pwd)/$path" + base_path="$(safe_cwd)/$path" fi find "$base_path" -name "$pattern" -type f 2>/dev/null | head -50 @@ -145,7 +146,7 @@ find_files() { if is_repo_mode; then base_path="$AIWB_REPO_PATH" else - base_path="$(pwd)" + base_path="$(safe_cwd)" fi # Use git ls-files if in repo (respects .gitignore) @@ -171,12 +172,12 @@ show_diff() { elif is_repo_mode; then full_path="$AIWB_REPO_PATH/$file_path" else - full_path="$(pwd)/$file_path" + full_path="$(safe_cwd)/$file_path" fi # Create temp file with new content local tmp_new - tmp_new=$(mktemp) + tmp_new=$(aiwb_mktemp) echo "$new_content" > "$tmp_new" echo "" @@ -345,7 +346,7 @@ create_file_with_ai() { elif is_repo_mode; then full_path="$AIWB_REPO_PATH/$file_path" else - full_path="$(pwd)/$file_path" + full_path="$(safe_cwd)/$file_path" fi if [[ -f "$full_path" ]]; then @@ -543,7 +544,7 @@ EOF if is_repo_mode; then full_path="$AIWB_REPO_PATH/$first_word" else - full_path="$(pwd)/$first_word" + full_path="$(safe_cwd)/$first_word" fi if [[ -f "$full_path" ]]; then diff --git a/lib/github.sh b/lib/github.sh index 7093851..0336535 100644 --- a/lib/github.sh +++ b/lib/github.sh @@ -120,7 +120,7 @@ github_api() { fi local response http_code - local tmp_file=$(mktemp) + local tmp_file=$(aiwb_mktemp) http_code=$(curl "${curl_args[@]}" -w "%{http_code}" -o "$tmp_file" "$url") response=$(cat "$tmp_file") diff --git a/lib/modes.sh b/lib/modes.sh index 2a104e9..7cd9114 100644 --- a/lib/modes.sh +++ b/lib/modes.sh @@ -605,7 +605,7 @@ menu_uploads() { case "$choice" in "Current repo/folder") # Add current directory to uploads - local current_dir="$(pwd)" + local current_dir="$(safe_cwd)" MODE_UPLOADS+=("$current_dir") success "Added current directory: $current_dir" ;; @@ -1306,10 +1306,16 @@ Provide specific, actionable feedback." # Clear blinking cursor ui_clear_line - # Check if interrupted + # Check if interrupted or failed if [[ $verify_exit -eq $AIWB_EXIT_SIGINT ]]; then echo "" - return 130 + # In headless mode, verifier failure is non-fatal — generator output + # is already saved, so the caller can still retrieve it. + if [[ "${AIWB_HEADLESS:-0}" == "1" ]]; then + warn "Verification interrupted — generator output preserved" + else + return 130 + fi fi if [[ $verify_exit -eq 0 ]]; then @@ -1329,6 +1335,8 @@ Provide specific, actionable feedback." local check_output=$(estimate_tokens "$feedback") actual_check_cost=$(calculate_cost "$check_provider" "$check_model" "$check_input" "$check_output") track_usage "$check_provider" "$check_model" "$check_prompt" "$feedback" + elif [[ "${AIWB_HEADLESS:-0}" == "1" ]]; then + warn "Verification failed (exit $verify_exit) — generator output preserved" fi fi diff --git a/lib/security.sh b/lib/security.sh index 27a572a..3a3b078 100644 --- a/lib/security.sh +++ b/lib/security.sh @@ -303,8 +303,9 @@ rotate_keys() { return 1 fi - # Decrypt to temp file - local temp_env="/tmp/aiwb_rotate_$$" + # Decrypt to temp file (use TMPDIR for Termux compatibility) + local temp_env + temp_env="$(aiwb_mktemp -t aiwb_rotate_XXXXXX)" age -d "$keys_file" <<< "$old_passphrase" > "$temp_env" 2>/dev/null if [[ $? -ne 0 ]]; then @@ -543,7 +544,8 @@ audit_git_exposure() { ) local found=false - local temp_results="/tmp/aiwb_git_audit_$$" + local temp_results + temp_results="$(aiwb_mktemp -t aiwb_git_audit_XXXXXX)" for pattern in "${key_patterns[@]}"; do # Search git history for the pattern diff --git a/lib/swarm.sh b/lib/swarm.sh index 6c6e631..bd357b9 100644 --- a/lib/swarm.sh +++ b/lib/swarm.sh @@ -408,7 +408,7 @@ swarm_mapreduce() { local -a chunks=() local lines_per_chunk=$(( $(echo "$prompt" | wc -l) / num_chunks )) - local temp_file=$(mktemp) + local temp_file=$(aiwb_mktemp) echo "$prompt" > "$temp_file" for (( i=0; i "$chunk_dir/chunk_$i.txt" done diff --git a/lib/ui.sh b/lib/ui.sh index af936a9..0934590 100644 --- a/lib/ui.sh +++ b/lib/ui.sh @@ -458,7 +458,7 @@ ui_select_context_files() { local -a display_options=() local -a full_paths=() local workspace - workspace="$(config_get workspace 2>/dev/null || pwd)" + workspace="$(config_get workspace 2>/dev/null || safe_cwd)" for file in "${file_paths[@]}"; do # Try to make path relative to workspace for cleaner display diff --git a/scripts/termux-clean-install.sh b/scripts/termux-clean-install.sh index 1710975..bcf59f5 100755 --- a/scripts/termux-clean-install.sh +++ b/scripts/termux-clean-install.sh @@ -16,13 +16,15 @@ echo "" # Step 2: Backup config and keys echo "2️⃣ Backing up config and keys..." +AIWB_TMP="${TMPDIR:-/tmp}" +mkdir -p "$AIWB_TMP" 2>/dev/null || true if [ -f ~/.aiwb/config.json ]; then - cp ~/.aiwb/config.json /tmp/aiwb-config-backup.json - echo " ✓ Backed up config to /tmp/aiwb-config-backup.json" + cp ~/.aiwb/config.json "$AIWB_TMP/aiwb-config-backup.json" + echo " ✓ Backed up config to $AIWB_TMP/aiwb-config-backup.json" fi if [ -f ~/.aiwb/keys.env ]; then - cp ~/.aiwb/keys.env /tmp/aiwb-keys-backup.env - echo " ✓ Backed up keys to /tmp/aiwb-keys-backup.env" + cp ~/.aiwb/keys.env "$AIWB_TMP/aiwb-keys-backup.env" + echo " ✓ Backed up keys to $AIWB_TMP/aiwb-keys-backup.env" fi echo " ✓ Backup complete" echo "" @@ -58,12 +60,12 @@ echo "" # Step 7: Restore backups if they exist echo "7️⃣ Restoring config and keys..." -if [ -f /tmp/aiwb-config-backup.json ]; then - cp /tmp/aiwb-config-backup.json ~/.aiwb/config.json +if [ -f "$AIWB_TMP/aiwb-config-backup.json" ]; then + cp "$AIWB_TMP/aiwb-config-backup.json" ~/.aiwb/config.json echo " ✓ Restored config from backup" fi -if [ -f /tmp/aiwb-keys-backup.env ]; then - cp /tmp/aiwb-keys-backup.env ~/.aiwb/keys.env +if [ -f "$AIWB_TMP/aiwb-keys-backup.env" ]; then + cp "$AIWB_TMP/aiwb-keys-backup.env" ~/.aiwb/keys.env echo " ✓ Restored keys from backup" fi echo ""