#!/data/data/com.termux/files/usr/bin/bash # ========================================================= # Bash-Wrangler — Cloudflare Pages/Workers Manager # Termux (F-Droid) | curl pipe compatible # Based on CLI-Wrangler by zetod1ce # # Usage: # curl -fsSL https://bash-wrangler.pages.dev | bash # ========================================================= set -euo pipefail export WRANGLER_SEND_METRICS=false RED=$'\033[1;31m' GREEN=$'\033[1;32m' YELLOW=$'\033[1;33m' CYAN=$'\033[1;36m' MAGENTA=$'\033[1;35m' BLUE=$'\033[1;34m' GRAY=$'\033[0;90m' BOLD=$'\033[1m' RESET=$'\033[0m' TEMP_DIR="${TMPDIR:-/tmp}/bash-wrangler" mkdir -p "$TEMP_DIR" 2>/dev/null || true trap 'rm -rf "$TEMP_DIR"/* 2>/dev/null || true' EXIT INT TERM # ── TTY-safe read (curl pipe compatible) ────────────────── _tty_read() { local __var="$1" __prompt="$2" if [ -t 0 ]; then printf '%s' "$__prompt" IFS= read -r "$__var" else printf '%s' "$__prompt" >/dev/tty IFS= read -r "$__var" /dev/tty fi if [ -t 0 ]; then read -r _; else read -r _ /dev/tty } past_tense() { case "$1" in Installing*) echo "${1/Installing/Installed}" ;; Removing*) echo "${1/Removing/Removed}" ;; Patching*) echo "${1/Patching/Patched}" ;; Fetching*) echo "${1/Fetching/Fetched}" ;; Deleting*) echo "${1/Deleting/Deleted}" ;; Updating*) echo "${1/Updating/Updated}" ;; Upgrading*) echo "${1/Upgrading/Upgraded}" ;; Deploying*) echo "${1/Deploying/Deployed}" ;; Creating*) echo "${1/Creating/Created}" ;; *) echo "$1" ;; esac } check_cmd() { command -v "$1" >/dev/null 2>&1 || true; } # ── Spinner ─────────────────────────────────────────────── show_box_gradient() { local text="$1" local offset="$2" local width="${3:-38}" local theme="${4:-Cyan}" local clean_text clean_text=$(printf '%s' "$text" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') local text_len=${#clean_text} local min_width=$((text_len + 4)) if [ "$min_width" -gt "$width" ]; then width=$min_width; fi local spaces=$((width - text_len - 1)) local total=$((width + 2 + 1 + width + 2 + 1)) local sbTop=" " sbBot=" " local mid_left="" local mid_right="" local c1_r c1_g c1_b c2_r c2_g c2_b for ((k=0; k/dev/tty } spinner_run() { local label="$1"; shift local success_label success_label=$(past_tense "$label") success_label="${success_label/.../}" success_label="${success_label/../}" local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local i=0 tmpout tmperr tmpout=$(mktemp 2>/dev/null || printf '/tmp/bw_out_%s' "$$") tmperr=$(mktemp 2>/dev/null || printf '/tmp/bw_err_%s' "$$") local tok; tok=$(_read_wrangler_token) || tok="" if [ -n "$tok" ]; then export CLOUDFLARE_API_TOKEN="$tok" fi "$@" >"$tmpout" 2>"$tmperr" /dev/tty while kill -0 "$pid" 2>/dev/null; do printf "\r\033[2K %b%s%b %s " "$CYAN" "${frames:$((i % ${#frames})):1}" "$RESET" "$label" >/dev/tty i=$(( i + 1 )) local key="" rc=0 read -rsn1 -t 0.08 key /dev/null printf "\r\033[K\033[?25h" >/dev/tty rm -f "$tmpout" "$tmperr" return 130 fi done printf "\033[?25h" >/dev/tty wait "$pid"; local rc=$? if [ "$rc" -eq 0 ]; then printf "\r %b[✓]%b %s \033[K\n" "$GREEN" "$RESET" "$success_label" >/dev/tty rm -f "$tmperr" else printf "\r %b[✗]%b %s \033[K\n" "$RED" "$RESET" "$success_label" >/dev/tty if [ -s "$tmperr" ]; then printf "%b" "$GRAY" >/dev/tty cat "$tmperr" >/dev/tty printf "%b" "$RESET" >/dev/tty fi rm -f "$tmperr" fi cat "$tmpout" rm -f "$tmpout" return "$rc" } run_bg() { local tmpout="$1" local tmperr="$2" shift 2 "$@" >"$tmpout" 2>"$tmperr" /dev/tty while kill -0 "$pid" 2>/dev/null; do printf "\033[5F" >/dev/tty show_box_gradient "$msg" "$((i * 2))" 38 "$theme" i=$((i+1)) local key="" rc=0 read -rsn1 -t 0.08 key /dev/null printf "\033[5F\033[0J\033[?25h" >/dev/tty return 130 fi done printf "\033[5F\033[0J\033[?25h" >/dev/tty return 0 } status_ok() { printf " %b[✓]%b %s\n" "$GREEN" "$RESET" "$1" >/dev/tty; } status_warn() { printf " %b[!]%b %s\n" "$CYAN" "$RESET" "$1" >/dev/tty; } status_err() { printf " %b[✗]%b %s\n" "$RED" "$RESET" "$1" >/dev/tty; } status_info() { printf " %b[+]%b %s\n" "$CYAN" "$RESET" "$1" >/dev/tty; } status_proc() { printf " %b[*]%b %s\n" "$BLUE" "$RESET" "$1" >/dev/tty; } show_box() { printf "\n" >/dev/tty local clean_text clean_text=$(printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') local color="${2:-$MAGENTA}" width=38 local text_len=${#clean_text} local min_width=$((text_len + 4)) if [ "$min_width" -gt "$width" ]; then width=$min_width; fi local padded padded=$(printf "%-${width}s" " $clean_text") local border="" for ((i=0; i/dev/tty printf " %b│%b\033[1m%s\033[0m%b│%b\n" "$color" "$RESET" "$padded" "$color" "$RESET" >/dev/tty printf " %b╰%s╯%b\n" "$color" "$border" "$RESET" >/dev/tty printf "\n" >/dev/tty } pkg_install() { spinner_run "Installing $1..." bash -c "pkg install -y '$1' >/dev/null 2>&1" } open_url() { local url="$1" if check_cmd cmd.exe; then cmd.exe /c start "" "$url" >/dev/null 2>&1 elif check_cmd termux-open; then termux-open "$url" >/dev/null 2>&1 elif check_cmd xdg-open; then xdg-open "$url" >/dev/null 2>&1 elif check_cmd open; then open "$url" >/dev/null 2>&1 fi } # ── Wrangler config (TOML) ──────────────────────────────── SUBDOMAIN_CACHE="$HOME/.wrangler/subdomain.txt" ACCOUNT_SUBDOMAIN="" _toml_field() { (grep -m1 -E "^[[:space:]]*${1}[[:space:]]*=" "$2" 2>/dev/null || true) \ | sed -E "s/^[[:space:]]*${1}[[:space:]]*=[[:space:]]*\"?([^\"]*)\"?/\1/" \ | tr -d '\r' } _read_wrangler_token() { local paths=( "$HOME/.config/wrangler/config/default.toml" "$HOME/.config/.wrangler/config/default.toml" "$HOME/.wrangler/config/default.toml" ) local tok for p in "${paths[@]}"; do if [ -f "$p" ]; then tok=$(_toml_field "oauth_token" "$p") [ -z "$tok" ] && tok=$(_toml_field "api_token" "$p") if [ -n "$tok" ]; then printf '%s' "$tok" return 0 fi fi done return 1 } _clear_wrangler_config() { rm -f "$SUBDOMAIN_CACHE" 2>/dev/null rm -f "$ACCOUNT_ID_CACHE" 2>/dev/null rm -f "$HOME/.config/wrangler/config/default.toml" 2>/dev/null rm -f "$HOME/.config/.wrangler/config/default.toml" 2>/dev/null rm -f "$HOME/.wrangler/config/default.toml" 2>/dev/null rm -f "$HOME/.wrangler/proxy_url.txt" 2>/dev/null } verify_token() { local tok; tok=$(_read_wrangler_token) || return 1 local http_code resp resp=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $tok" \ "https://api.cloudflare.com/client/v4/accounts") || return 1 http_code=$(printf '%s' "$resp" | tail -n1) if [ "$http_code" = "200" ]; then return 0 fi return 1 } ACCOUNT_ID_CACHE="$HOME/.wrangler/account_id.txt" get_account_id() { [ -f "$ACCOUNT_ID_CACHE" ] && { local a; a=$(cat "$ACCOUNT_ID_CACHE") [ -n "$a" ] && printf '%s' "$a" && return 0 } local tok; tok=$(_read_wrangler_token) || return 1 local accounts account_id # Try /accounts endpoint first accounts=$(curl -sf -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts") || true account_id=$(printf '%s' "$accounts" | jq -r '.result[0]?.id // empty' 2>/dev/null) # Fallback: extract account_id from wrangler's error output if [ -z "$account_id" ]; then local wrgl_err wrgl_err=$(wrangler pages project list 2>&1 | grep -o '/accounts/[a-f0-9]\{32\}/' | head -1 | tr -d '/' || true) account_id=$(printf '%s' "$wrgl_err" | grep -o '[a-f0-9]\{32\}' | head -1 || true) fi [ -z "$account_id" ] && return 1 mkdir -p "$(dirname "$ACCOUNT_ID_CACHE")" printf '%s' "$account_id" > "$ACCOUNT_ID_CACHE" printf '%s' "$account_id" } # ── Detect account subdomain via CF API ─────────────────── detect_subdomain() { [ -f "$SUBDOMAIN_CACHE" ] && { local s; s=$(cat "$SUBDOMAIN_CACHE") [ -n "$s" ] && printf '%s' "$s" && return 0 } local tok; tok=$(_read_wrangler_token) || return 1 local account_id; account_id=$(get_account_id) || return 1 local resp sub if ! resp=$(curl -sf -H "Authorization: Bearer $tok" \ "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/subdomain"); then rm -f "$ACCOUNT_ID_CACHE" 2>/dev/null account_id=$(get_account_id) || return 1 resp=$(curl -sf -H "Authorization: Bearer $tok" \ "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/subdomain") || return 1 fi sub=$(printf '%s' "$resp" | jq -r '.result?.subdomain // empty' 2>/dev/null) [ -z "$sub" ] && return 1 mkdir -p "$(dirname "$SUBDOMAIN_CACHE")" printf '%s' "$sub" > "$SUBDOMAIN_CACHE" printf '%s' "$sub" } get_subdomain() { detect_subdomain "$@"; } _register_subdomain() { local sub="$1" local tok; tok=$(_read_wrangler_token) || return 1 local account_id; account_id=$(get_account_id) || return 1 local resp local body body=$(jq -n --arg sub "$sub" '{subdomain:$sub}' 2>/dev/null) resp=$(curl -sf -X PUT -H "Authorization: Bearer $tok" -H "Content-Type: application/json" \ -d "$body" \ "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/subdomain") || return 1 if printf '%s' "$resp" | jq -e '.success' >/dev/null 2>&1; then return 0 else return 1 fi } # ══════════════════════════════════════════════════════════ # SETUP: Node.js + Wrangler@2 + workerd patch # ══════════════════════════════════════════════════════════ _patch_workerd() { local npm_root f npm_root=$(npm root -g 2>/dev/null) f="${npm_root}/wrangler/node_modules/workerd/lib/main.js" [ -f "$f" ] || f=$(find /data/data/com.termux/files/usr/lib/node_modules \ -path "*/workerd/lib/main.js" 2>/dev/null | head -1) [ -f "$f" ] || return 0 [ -f "${f}.orig" ] || cp "$f" "${f}.orig" sed -i \ -e 's/throw new Error(`Unsupported platform: ${platformKey}`)/return { pkg: "", subpath: "" }/' \ -e 's/throw new Error("Unsupported platform: " + platformKey)/return { pkg: "", subpath: "" }/' \ "$f" 2>/dev/null return 0 } setup_wrangler() { show_box "Setting Up Wrangler" "$CYAN" # Node.js if ! check_cmd node; then pkg_install "nodejs-lts" || pkg_install "nodejs" || { status_err "Node.js install failed"; return 1 } else status_ok "nodejs" fi local node_ver node_ver=$(node -v 2>/dev/null | grep -oE '^v[0-9]+' | tr -d 'v' || true) if [ -n "$node_ver" ] && [ "$node_ver" -lt 18 ]; then status_warn "Node.js version is < 18 (found v${node_ver}). Wrangler may fail." fi check_cmd npm || { status_err "npm not found"; return 1; } local major_ver major_ver=$(wrangler --version 2>/dev/null | grep -oE '^[0-9]+') if [ "$major_ver" != "2" ]; then spinner_run "Installing wrangler..." \ bash -c "npm install -g wrangler@2 >/dev/null 2>&1" || { status_err "wrangler install failed" sleep 5 return 1 } else status_ok "wrangler" fi # Patch workerd so it doesn't throw on Android ARM spinner_run "Patching workerd..." _patch_workerd # wrangler login (only if token is missing or explicitly invalid) if ! verify_token >/dev/null 2>&1; then show_box "Cloudflare Login" "$YELLOW" status_warn "Token is missing or expired. Re-authenticating..." status_info "Browser will open — approve access, then return here." printf "\n" >/dev/tty _clear_wrangler_config wrangler login /dev/tty 2>/dev/tty || true else status_ok "Wrangler session is valid and active" fi # Detect subdomain ACCOUNT_SUBDOMAIN=$(detect_subdomain 2>/dev/null || true) sleep 1 } # ══════════════════════════════════════════════════════════ # HELPERS # ══════════════════════════════════════════════════════════ if [ -d "/data/data/com.termux/files/home" ]; then TEMP_DIR="/data/data/com.termux/files/home/.bw_tmp" else TEMP_DIR="${TMPDIR:-/tmp}/.bw_tmp" fi mkdir -p "$TEMP_DIR" 2>/dev/null clear_tmp() { rm -rf "${TEMP_DIR:?}/$1" 2>/dev/null; } test_pages_status() { local projects=("$@") local count=${#projects[@]} local pids=() local temp_files=() local i # Start curl checks in parallel for ((i=0; i/dev/null || printf '/tmp/bw_check_%s_%d' "$$" "$i") temp_files[i]="$tmpf" ( local code; code=$(curl -s -I -o /dev/null -w "%{http_code}" --max-time 3 "https://${p}.pages.dev" 2>/dev/null || echo "000") printf '%s' "$code" > "$tmpf" ) & pids[i]=$! done # Spinner animation while waiting local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local frame_idx=0 while true; do local running=0 for ((i=0; i/dev/null; then running=1 break fi done [ "$running" -eq 0 ] && break printf "\r\033[2K %b%s%b %s " "$CYAN" "${frames:$((frame_idx % ${#frames})):1}" "$RESET" "Checking status of all pages.dev projects..." >/dev/tty frame_idx=$(( frame_idx + 1 )) sleep 0.08 done # Clean up and print success checkmark printf "\r %b[✓]%b Checked status of all pages.dev projects. \n" "$GREEN" "$RESET" >/dev/tty # Read status codes and return a space-separated list local results=() for ((i=0; i/dev/null || echo "000") rm -f "$tmpf" if [ "$code" = "000" ] || [ "$code" -ge 400 ]; then results[i]=1 else results[i]=0 fi done printf '%s' "${results[*]}" } ask_menu() { local title="$1" local color="$2" local multi="$3" local status_str="$4" shift 4 local items=("$@") local count=${#items[@]} local cur=0 # Colors local ind_color="$MAGENTA" [ "$color" = "Cyan" ] && ind_color="$CYAN" [ "$color" = "Red" ] && ind_color="$RED" [ "$color" = "Yellow" ] && ind_color="$YELLOW" [ "$color" = "Green" ] && ind_color="$GREEN" [ "$color" = "Blue" ] && ind_color="$BLUE" local checked=() local failed_map=($status_str) local i for ((i=0; i/dev/tty if [ -n "$title" ]; then printf " %b↑↓ %b%b%s%b\n" "$CYAN" "$RESET" "$BOLD" "$title" "$RESET" >/dev/tty printf "\n" >/dev/tty fi render_menu() { local idx for ((idx=0; idx%b %b %d. %b%b%b\n" "$RED" "$RESET" "$box" "$((idx+1))" "$RED$BOLD" "${items[idx]}" "$RESET" >/dev/tty else printf " %b %d. %b%s%b\n" "$box" "$((idx+1))" "$RED" "${items[idx]}" "$RESET" >/dev/tty fi else if [ "$idx" -eq "$cur" ]; then printf " %b>%b %b %d. %b%b%b\n" "$RED" "$RESET" "$box" "$((idx+1))" "$BOLD" "${items[idx]}" "$RESET" >/dev/tty else printf " %b %d. %b%s%b\n" "$box" "$((idx+1))" "$GRAY" "${items[idx]}" "$RESET" >/dev/tty fi fi else if [ "$idx" -eq "$cur" ]; then printf " %b●%b %b%d. %b%b\n" "$ind_color" "$RESET" "$BOLD$ind_color" "$((idx+1))" "${items[idx]}" "$RESET" >/dev/tty else printf " %b○%b %d. %s\n" "$GRAY" "$RESET" "$((idx+1))" "${items[idx]}" >/dev/tty fi fi done } render_menu local key while true; do IFS= read -rsn1 -d '' key /dev/tty return 130 fi elif [ -z "$key" ] || [ "$key" = $'\n' ] || [ "$key" = $'\r' ]; then printf "\033[?25h\n" >/dev/tty echo "$cur" return 0 elif [ "$key" = " " ] && [ "$multi" -eq 1 ]; then checked[cur]=$(( 1 - checked[cur] )) elif [[ "$key" =~ ^[1-9]$ ]]; then local val=$(( key - 1 )) if [ "$val" -lt "$count" ]; then cur="$val" if [ "$multi" -eq 1 ]; then checked[cur]=$(( 1 - checked[cur] )) else printf "\033[?25h\n" >/dev/tty echo "$cur" return 0 fi fi fi local idx for ((idx=0; idx/dev/tty done render_menu done } ask_confirm() { local prompt="$1" local color="${2:-$RESET}" local indent="${3:-3}" local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local i=0 local spinIndentStr=" " local succIndentStr=" " printf "\033[?25l" >/dev/tty while true; do printf "\r\033[2K%s%b%s%b %b%s [Y/N]:%b " "$spinIndentStr" "$CYAN" "${frames:$((i % ${#frames})):1}" "$RESET" "$color" "$prompt" "$RESET" >/dev/tty local key="" if read -rsn1 -t 0.08 key /dev/tty return 130 fi elif [ -z "$key" ] || [ "$key" = $'\n' ] || [ "$key" = $'\r' ]; then printf "\r\033[2K%s%b[✓]%b %b%s [Y/N]:%b %bYes%b\n\033[?25h" "$succIndentStr" "$GREEN" "$RESET" "$color" "$prompt" "$RESET" "$GREEN" "$RESET" >/dev/tty return 0 elif [ "$key" = "y" ] || [ "$key" = "Y" ]; then printf "\r\033[2K%s%b[✓]%b %b%s [Y/N]:%b %bYes%b\n\033[?25h" "$succIndentStr" "$GREEN" "$RESET" "$color" "$prompt" "$RESET" "$GREEN" "$RESET" >/dev/tty return 0 elif [ "$key" = "n" ] || [ "$key" = "N" ]; then printf "\r\033[2K%s%b[✗]%b %b%s [Y/N]:%b %bNo%b\n\033[?25h" "$succIndentStr" "$RED" "$RESET" "$color" "$prompt" "$RESET" "$RED" "$RESET" >/dev/tty return 1 fi fi i=$((i+1)) done } ask_input() { local prompt="" default="" color="$RESET" local no_success=0 initial="" error_msg="" while [ $# -gt 0 ]; do case "$1" in --no-success) no_success=1; shift ;; --initial) initial="$2"; shift 2 ;; --error) error_msg="$2"; shift 2 ;; *) if [ -z "$prompt" ]; then prompt="$1" elif [ -z "$default" ]; then default="$1" else color="$1" fi shift ;; esac done local chars="$initial" local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local i=0 printf "\033[?25h" >/dev/tty if [ -n "$error_msg" ]; then printf "\033[1G\033[2K %b[✗]%b %b%s%b%b%s%b\n" "$RED" "$RESET" "$color" "$prompt" "$RESET" "$RED" "$chars" "$RESET" >/dev/tty local col=$(( 8 + ${#prompt} + ${#chars} )) printf "\033[2K %b%s%b\033[1A\033[%dG" "$RED" "$error_msg" "$RESET" "$col" >/dev/tty fi local needs_redraw=1 while true; do if [ -z "$error_msg" ]; then if [ "$needs_redraw" -eq 1 ]; then printf "\033[1G\033[2K %b%s%b %b%s%b%s" "$CYAN" "${frames:$((i % ${#frames})):1}" "$RESET" "$color" "$prompt" "$RESET" "$chars" >/dev/tty needs_redraw=0 else printf "\033[s\033[4G%b%s%b\033[u" "$CYAN" "${frames:$((i % ${#frames})):1}" "$RESET" >/dev/tty fi i=$((i+1)) fi local key="" if read -rsn1 -t 0.08 key /dev/tty fi if [ "$key" = $'\e' ]; then local seq="" read -rsn2 -t 0.05 seq /dev/tty return 130 fi elif [ -z "$key" ] || [ "$key" = $'\n' ] || [ "$key" = $'\r' ]; then if [ "$no_success" -eq 0 ]; then printf "\r\033[2K %b[✓]%b %b%s%b%s\n" "$GREEN" "$RESET" "$color" "$prompt" "$RESET" "$chars" >/dev/tty else printf "\n" >/dev/tty fi break elif [ "$key" = $'\x7f' ] || [ "$key" = $'\b' ]; then if [ ${#chars} -gt 0 ]; then chars="${chars%?}" fi needs_redraw=1 else chars="${chars}${key}" needs_redraw=1 fi fi done chars="${chars//[$'\t\r\n ']}" [ -z "$chars" ] && chars="$default" printf '%s' "$chars" return 0 } # Simple numbered menu (↑↓ not available in bash without ncurses) show_menu_items() { local i=1 for item in "$@"; do printf " %b%d.%b %s\n" "$CYAN" "$i" "$RESET" "$item" i=$(( i + 1 )) done printf "\n" } # ── Generate index.js content ───────────────────────────── make_index_js() { cat <<'EOF' export async function onRequest(context) { if (!context.env.PROJECT_NAME) { return new Response('PROJECT_NAME missing', { status: 500 }); } const proj = context.env.PROJECT_NAME; let url = context.env["URL_" + proj] || null; let mode = context.env["MODE_" + proj] || "redirect"; let proxyUrl = context.env.PROXY_URL || ""; if (!url) return new Response('URL not found in env variables', { status: 500 }); let target = url; if (proxyUrl) { target = proxyUrl + "?url=" + encodeURIComponent(url); } const noCache = { "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" }; const fetchOpts = { cf: { cacheTtl: 0 } }; switch (mode) { case "redirect": { const r = await fetch(target, { redirect: "follow", ...fetchOpts }); return new Response(null, { status: 302, headers: { ...noCache, "Location": r.url } }); } case "text": { const r = await fetch(target, fetchOpts); if (!r.ok) return new Response('Fetch error: ' + r.status, {status: 502, headers: noCache}); return new Response(await r.text(), { headers: { ...noCache, "Content-Type": "text/plain; charset=utf-8" } }); } case "cli": { const ua = context.request.headers.get("user-agent") || ""; if (/Mozilla\/5\.0/i.test(ua)) { return new Response(null, { status: 204, headers: noCache }); } const r = await fetch(target, fetchOpts); if (!r.ok) return new Response('Fetch error: ' + r.status, {status: 502, headers: noCache}); return new Response(await r.text(), { headers: { ...noCache, "Content-Type": "text/plain; charset=utf-8" } }); } case "html": { const r = await fetch(target, fetchOpts); if (!r.ok) return new Response('Fetch error: ' + r.status, {status: 502, headers: noCache}); return new Response(await r.text(), { headers: { ...noCache, "Content-Type": "text/html; charset=utf-8" } }); } default: return new Response("Invalid MODE", { status: 500, headers: noCache }); } } EOF } # ── Check if pages.dev domain is taken ─────────────────── check_domain_available() { local domain="${1}.pages.dev" local frames='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' local frame_idx=0 local tmpf="${TEMP_DIR}/bw_check_domain_$$" ( local account_id; account_id=$(get_account_id) local tok; tok=$(_read_wrangler_token) local code; code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts/${account_id}/pages/projects/$1") echo "$code" > "$tmpf" ) & local pid=$! while kill -0 "$pid" 2>/dev/null; do printf "\r\033[2K %b%s%b %s " "$CYAN" "${frames:$((frame_idx % ${#frames})):1}" "$RESET" "Checking: ${domain%%.*}" >/dev/tty frame_idx=$(( frame_idx + 1 )) local key="" read -rsn1 -t 0.08 key /dev/null printf "\r\033[2K" >/dev/tty rm -f "$tmpf" return 130 fi done wait "$pid" 2>/dev/null || true printf "\r\033[2K" >/dev/tty local code; code=$(cat "$tmpf" 2>/dev/null || echo "1") rm -f "$tmpf" if [ "$code" = "404" ]; then local http_code http_code=$(curl -s -o /dev/null -w "%{http_code}" -m 5 "https://${domain}") if [ "$http_code" = "000" ] || [ "$http_code" = "404" ]; then return 0 else err_msg="Domain already taken globally" return 1 fi elif [ "$code" = "200" ]; then return 2 elif [ "$code" = "401" ] || [ "$code" = "403" ]; then err_msg="Cloudflare Authentication Error ($code)" return 1 elif [ "$code" = "429" ]; then err_msg="Cloudflare Rate Limit Exceeded (429)" return 1 elif [ "$code" -ge 500 ]; then err_msg="Cloudflare Server Error ($code)" return 1 else return 0 fi } _deploy_pages_task() { local proj_dir="$1" proj_name="$2" branch="$3" redirect_url="$4" mode_str="$5" proxy_url="$6" cd "$proj_dir" || exit 1 local cmd="deploy" local ver ver=$(wrangler --version 2>/dev/null | grep -oE '[0-9]+' | head -1) if [ "$ver" = "2" ]; then cmd="publish" fi local tok; tok=$(_read_wrangler_token) || true local account_id; account_id=$(get_account_id) || true local token_found=0 if [ -n "$tok" ] && [ -n "$account_id" ]; then token_found=1 local payload payload=$(jq -n --arg name "$proj_name" --arg branch "$branch" '{name:$name,production_branch:$branch}') curl -s -X POST -H "Authorization: Bearer $tok" -H "Content-Type: application/json" -d "$payload" "https://api.cloudflare.com/client/v4/accounts/${account_id}/pages/projects" >/dev/null 2>&1 || true local env_payload env_payload=$(jq -n \ --arg proj "$proj_name" \ --arg url "$redirect_url" \ --arg mode "$mode_str" \ --arg purl "$proxy_url" \ '{ "deployment_configs": { "production": { "env_vars": { "PROJECT_NAME": {"type": "plain_text", "value": $proj} } }, "preview": { "env_vars": { "PROJECT_NAME": {"type": "plain_text", "value": $proj} } } } } | if ($url != "") then .deployment_configs.production.env_vars["URL_" + $proj] = {"type":"plain_text","value":$url} | .deployment_configs.preview.env_vars["URL_" + $proj] = {"type":"plain_text","value":$url} | .deployment_configs.production.env_vars["MODE_" + $proj] = {"type":"plain_text","value":$mode} | .deployment_configs.preview.env_vars["MODE_" + $proj] = {"type":"plain_text","value":$mode} else . end | if ($purl != "") then .deployment_configs.production.env_vars["PROXY_URL"] = {"type":"plain_text","value":$purl} | .deployment_configs.preview.env_vars["PROXY_URL"] = {"type":"plain_text","value":$purl} else . end ') curl -s -X PATCH -H "Authorization: Bearer $tok" -H "Content-Type: application/json" -d "$env_payload" "https://api.cloudflare.com/client/v4/accounts/${account_id}/pages/projects/$proj_name" >/dev/null 2>&1 || true fi if [ "$token_found" -eq 0 ]; then wrangler pages project create "$proj_name" --production-branch "$branch" >/dev/null 2>&1 || true fi wrangler pages $cmd . --project-name "$proj_name" --branch "$branch" } # ══════════════════════════════════════════════════════════ # FEATURE: Deploy Pages Project # ══════════════════════════════════════════════════════════ invoke_deploy() { local sub sub=$(get_subdomain) local worker_exists=0 local tok; tok=$(_read_wrangler_token) || true local account_id; account_id=$(get_account_id) || true if [ -n "$tok" ] && [ -n "$account_id" ]; then local code code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/scripts/proxy") if [ "$code" = "200" ]; then worker_exists=1 fi fi if [ -z "$sub" ] || [ ! -f "$PROXY_CACHE" ] || [ "$worker_exists" -eq 0 ]; then invoke_proxy_setup sub=$(get_subdomain) if [ -z "$sub" ] || [ ! -f "$PROXY_CACHE" ] || [ "$worker_exists" -eq 0 ]; then # Re-check worker_exists if setup was cancelled or failed local code2 code2=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/scripts/proxy") if [ "$code2" = "200" ]; then worker_exists=1 else return fi fi clear fi show_box "Deploy Pages Project" "$MAGENTA" printf " %b[↑↓] Select | [Enter] Confirm | [Esc] Back%b\n\n" "$GRAY" "$RESET" >/dev/tty # Project name local proj_name="" local branch="production" local err_msg="" local is_mine=1 while true; do if [ -n "$err_msg" ]; then proj_name=$(ask_input --no-success --initial "$proj_name" --error "$err_msg" "Project name: ") else proj_name=$(ask_input --no-success "Project name: ") fi [ -z "$proj_name" ] && return proj_name=$(printf '%s' "$proj_name" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9-]/-/g' -e 's/^-\+//' -e 's/-\+$//') err_msg="" if [ -z "$proj_name" ]; then err_msg="Invalid project name (a-z, 0-9, -)" continue fi if [ "${#proj_name}" -gt 58 ]; then err_msg="Project name too long (max 58)" continue fi check_domain_available "$proj_name" local status=$? if [ "$status" -eq 130 ]; then return elif [ "$status" -eq 0 ]; then printf "\033[1G\033[2K" >/dev/tty printf " %b[✓]%b %bProject name:%b %b%s%b\n\n" "$GREEN" "$RESET" "$RESET" "$GREEN" "$proj_name" "$RESET" >/dev/tty is_mine=0 break elif [ "$status" -eq 2 ]; then printf "\033[1G\033[2K" >/dev/tty printf " %b[✗]%b %bProject name:%b %b%s%b\n" "$RED" "$RESET" "$RESET" "$RED" "$proj_name" "$RESET" >/dev/tty ask_confirm "Overwrite existing Pages project?" "$CYAN" 8 local rc=$? if [ "$rc" -eq 130 ]; then printf "\n" >/dev/tty return elif [ "$rc" -eq 0 ]; then printf "\033[3F\033[2K" >/dev/tty printf " %b[✓]%b %bProject name:%b %b%s%b\n\n" "$GREEN" "$RESET" "$RESET" "$GREEN" "$proj_name" "$RESET" >/dev/tty printf "\033[J" >/dev/tty is_mine=1 break else printf "\033[3F\033[J" >/dev/tty fi else err_msg="$err_msg" fi done [ -z "$proj_name" ] && return # URL for index.js local redirect_url="" local mode_str="redirect" if [ "$is_mine" -eq 0 ]; then local url_err="" while true; do if [ -n "$url_err" ]; then redirect_url=$(ask_input --no-success --error "$url_err" "Enter URL: ") else redirect_url=$(ask_input --no-success "Enter URL: ") fi [ -z "$redirect_url" ] && return if ! printf '%s' "$redirect_url" | grep -qE '^https?://'; then redirect_url="https://${redirect_url}" fi if ! printf '%s' "$redirect_url" | grep -qE '^https?://[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+'; then url_err="Invalid URL format" continue fi break done printf "\033[1G\033[2K" >/dev/tty printf " %b[✓]%b %bEnter URL:%b %b%s%b\n\n" "$GREEN" "$RESET" "$RESET" "$GREEN" "$redirect_url" "$RESET" >/dev/tty # Mode local modes=( "Standard Redirect" "Fetch as Text" "Fetch as Text (Non-Browser / CLI only)" "Fetch as HTML" ) local mode_choice mode_choice=$(ask_menu "" "Purple" 0 "" "${modes[@]}") || return mode_choice=$(( mode_choice + 1 )) case "$mode_choice" in 1) mode_str="redirect" ;; 2) mode_str="text" ;; 3) mode_str="cli" ;; 4) mode_str="html" ;; esac else ask_confirm "Overwrite URL?" "$CYAN" local confirm=$? if [ "$confirm" -eq 130 ]; then return; fi if [ "$confirm" -eq 0 ]; then printf "\033[1F\033[2K" >/dev/tty local url_err="" while true; do if [ -n "$url_err" ]; then redirect_url=$(ask_input --no-success --error "$url_err" "Enter URL: ") else redirect_url=$(ask_input --no-success "Enter URL: ") fi [ -z "$redirect_url" ] && return if ! printf '%s' "$redirect_url" | grep -qE '^https?://'; then redirect_url="https://${redirect_url}" fi if ! printf '%s' "$redirect_url" | grep -qE '^https?://[a-zA-Z0-9-]+\.[a-zA-Z0-9.-]+'; then url_err="Invalid URL format" continue fi break done printf "\033[1G\033[2K" >/dev/tty printf " %b[✓]%b %bEnter URL:%b %b%s%b\n\n" "$GREEN" "$RESET" "$RESET" "$GREEN" "$redirect_url" "$RESET" >/dev/tty # Mode local modes=( "Standard Redirect" "Fetch as Text" "Fetch as Text (Non-Browser / CLI only)" "Fetch as HTML" ) local mode_choice mode_choice=$(ask_menu "" "Purple" 0 "" "${modes[@]}") || return mode_choice=$(( mode_choice + 1 )) case "$mode_choice" in 1) mode_str="redirect" ;; 2) mode_str="text" ;; 3) mode_str="cli" ;; 4) mode_str="html" ;; esac fi fi local proxy_url="" if [ -n "$PROXY_URL" ]; then proxy_url="$PROXY_URL" else local sub sub=$(get_subdomain) if [ -n "$sub" ]; then proxy_url="https://proxy.${sub}.workers.dev" else proxy_url="https://proxy.subdomain.workers.dev" fi fi # Build project structure local proj_dir="${TEMP_DIR}/${proj_name}" local func_dir="${proj_dir}/functions" rm -rf "$proj_dir" mkdir -p "$func_dir" printf "{}\n" > "${proj_dir}/package.json" if ! make_index_js > "${func_dir}/index.js"; then return; fi local tmpout="${TEMP_DIR}/deploy_out.log" local tmperr="${TEMP_DIR}/deploy_err.log" local pid pid=$(run_bg "$tmpout" "$tmperr" _deploy_pages_task "$proj_dir" "$proj_name" "$branch" "$redirect_url" "$mode_str" "$proxy_url") animated_box_wait "$pid" "Deploying: ${proj_name}" "$MAGENTA" local esc_rc=$? wait "$pid" 2>/dev/null; local rc=$? if [ "$esc_rc" -eq 130 ]; then clear_tmp "$proj_name" return fi if [ "$rc" -eq 0 ]; then show_box "Deployment Completed: https://${proj_name}.pages.dev" "$GREEN" else show_box "Deploying: ${proj_name} [ERROR]" "$RED" printf "\n%b[Stdout]%b\n" "$GRAY" "$RESET" >/dev/tty cat "$tmpout" >/dev/tty 2>/dev/null || true printf "\n%b[Stderr]%b\n" "$RED" "$RESET" >/dev/tty cat "$tmperr" >/dev/tty 2>/dev/null || true printf "\n" >/dev/tty fi rm -f "$tmpout" "$tmperr" clear_tmp "$proj_name" _tty_pause " [!] Press any key to continue..." } _deploy_worker_task() { cd "$1" || exit 1 wrangler deploy } # ══════════════════════════════════════════════════════════ # FEATURE: Create Worker (Proxy / Web Server) # ══════════════════════════════════════════════════════════ PROXY_CACHE="$HOME/.wrangler/proxy_url.txt" PROXY_URL="" [ -f "$PROXY_CACHE" ] && PROXY_URL=$(cat "$PROXY_CACHE") invoke_proxy_setup() { show_box "Create Proxy Worker" "$MAGENTA" printf " %b[Esc] Back%b\n\n" "$GRAY" "$RESET" >/dev/tty # Resolve subdomain local subdomain="$ACCOUNT_SUBDOMAIN" if [ -z "$subdomain" ]; then status_proc "Auto-detecting Cloudflare subdomain..." subdomain=$(detect_subdomain 2>/dev/null || true) if [ -n "$subdomain" ]; then ACCOUNT_SUBDOMAIN="$subdomain" printf "\033[1F\033[2K\r" >/dev/tty printf " %b[✓]%b Proxy URL: proxy.%s.workers.dev\n" "$CYAN" "$RESET" "$subdomain" >/dev/tty else status_info "Workers subdomain not initialized on this account." ask_confirm "Register a new subdomain now?" "$CYAN" local rc=$? [ "$rc" -eq 130 ] && return if [ "$rc" -eq 0 ]; then while true; do subdomain=$(ask_input "Desired Cloudflare subdomain: ") [ -z "$subdomain" ] && return subdomain=$(printf '%s' "$subdomain" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9-]/-/g' -e 's/^-\+//' -e 's/-\+$//') if [ -z "$subdomain" ] || [ "${#subdomain}" -gt 63 ]; then status_err "Invalid subdomain" continue fi spinner_run "Registering subdomain ${subdomain}..." _register_subdomain "$subdomain" local reg_rc=$? [ "$reg_rc" -eq 130 ] && return if [ "$reg_rc" -eq 0 ]; then ACCOUNT_SUBDOMAIN="$subdomain" mkdir -p "$(dirname "$SUBDOMAIN_CACHE")" printf '%s' "$subdomain" > "$SUBDOMAIN_CACHE" printf "\033[3F\033[J" >/dev/tty printf " %b[✓]%b Proxy URL: proxy.%s.workers.dev\n" "$CYAN" "$RESET" "$subdomain" >/dev/tty break else status_err "Subdomain taken or invalid. Try another." fi done else return fi fi else status_proc "Auto-detecting Cloudflare subdomain..." printf "\033[1F\033[2K\r" >/dev/tty printf " %b[✓]%b Proxy URL: proxy.%s.workers.dev\n" "$CYAN" "$RESET" "$subdomain" >/dev/tty fi local worker_name="proxy" local custom_name custom_name=$(ask_input --initial "$worker_name" "Worker name: ") || true [ -n "$custom_name" ] && worker_name=$(printf '%s' "$custom_name" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9-]/-/g' -e 's/^-\+//' -e 's/-\+$//') if [ -z "$worker_name" ] || [ "${#worker_name}" -gt 63 ]; then status_err "Invalid worker name" return fi local resolved_url="https://${worker_name}.${subdomain}.workers.dev" # Check if worker already exists local tok; tok=$(_read_wrangler_token) || true local account_id; account_id=$(get_account_id) || true if [ -n "$tok" ] && [ -n "$account_id" ]; then local code code=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts/${account_id}/workers/scripts/${worker_name}") if [ "$code" = "200" ]; then printf "\n %b[✓]%b Worker '%s' already exists!\n" "$GREEN" "$RESET" "$worker_name" >/dev/tty ask_confirm "Redeploy to ensure code is up to date?" "$CYAN" 8 local redep_rc=$? [ "$redep_rc" -eq 130 ] && return if [ "$redep_rc" -ne 0 ]; then mkdir -p "$(dirname "$PROXY_CACHE")" printf '%s' "$resolved_url" > "$PROXY_CACHE" return 0 fi printf "\033[2A\033[J" >/dev/tty fi fi printf "\n" >/dev/tty ask_confirm "Use GitHub token?" "$CYAN" local use_token=$? [ "$use_token" -eq 130 ] && return local token="" if [ "$use_token" -eq 0 ]; then printf "\033[1F\033[2K\r" >/dev/tty token=$(ask_input "Enter GitHub API token: ") [ -z "$token" ] && return fi # Build worker local worker_dir="${TEMP_DIR}/worker-${worker_name}" rm -rf "$worker_dir" mkdir -p "$worker_dir" printf "{}\n" > "${worker_dir}/package.json" cat << EOF > "${worker_dir}/wrangler.toml" name = "${worker_name}" main = "worker.js" compatibility_date = "$(date +%F)" EOF if [ -n "$token" ]; then cat << EOF >> "${worker_dir}/wrangler.toml" [vars] GITHUB_TOKEN = "${token}" EOF fi cat << 'WORKEREOF' > "${worker_dir}/worker.js" export default { async fetch(request, env) { const params = new URL(request.url).searchParams; const url = params.get('url'); if (!url) return new Response('Usage: ?url=YOUR_URL', { status: 400 }); try { const headers = { 'User-Agent': 'CF-Worker' }; let fetchUrl = url; if (url.includes('github.com') || url.includes('raw.githubusercontent.com')) { if (env.GITHUB_TOKEN) headers['Authorization'] = 'token ' + env.GITHUB_TOKEN; const m = url.match(/github\.com\/([^/]+)\/([^/]+)\/(?:raw|blob)\/(?:refs\/heads\/)?([^/]+)\/(.+)/) || url.match(/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+)/); if (m) { const [, owner, repo, branch, file] = m; fetchUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${file}${branch ? '?ref=' + branch : ''}`; headers['Accept'] = 'application/vnd.github.raw'; } } const r = await fetch(fetchUrl, { headers, cf: { cacheTtl: 0 } }); const noCache = { "Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" }; if (!r.ok) return new Response('Fetch error: ' + r.status, { status: 502, headers: noCache }); const ct = r.headers.get('content-type') || 'text/plain; charset=utf-8'; return new Response(await r.text(), { headers: { ...noCache, 'Content-Type': ct } }); } catch (e) { return new Response('EXCEPTION: ' + e.message, { status: 500, headers: { "Cache-Control": "no-store" } }); } } } WORKEREOF local tmpout="${TEMP_DIR}/worker_out.log" local tmperr="${TEMP_DIR}/worker_err.log" # Set GITHUB_TOKEN plaintext in wrangler.toml directly (handled above) local pid pid=$(run_bg "$tmpout" "$tmperr" bash -c "cd '$worker_dir' && CI=true wrangler deploy >/dev/null 2>&1") animated_box_wait "$pid" "Deploying: ${resolved_url#https://} ..." "$MAGENTA" local esc_rc=$? wait "$pid" 2>/dev/null; local rc=$? || true if [ "$esc_rc" -eq 130 ]; then clear_tmp "worker-${worker_name}" return fi if [ "$rc" -eq 0 ]; then show_box "Worker Deployed!" "$GREEN" PROXY_URL="$resolved_url" mkdir -p "$(dirname "$PROXY_CACHE")" echo "$PROXY_URL" > "$PROXY_CACHE" rm -f "$tmperr" else show_box "Deploying: ${worker_name} [ERROR]" "$RED" printf "\n%b[Stdout]%b\n" "$GRAY" "$RESET" >/dev/tty cat "$tmpout" >/dev/tty 2>/dev/null || true printf "\n%b[Stderr]%b\n" "$RED" "$RESET" >/dev/tty cat "$tmperr" >/dev/tty 2>/dev/null || true printf "\n" >/dev/tty rm -f "$tmperr" fi rm -f "$tmpout" clear_tmp "worker-${worker_name}" _tty_pause " [ Press ENTER to continue ]" } # ══════════════════════════════════════════════════════════ # FEATURE: Delete Pages Projects # ══════════════════════════════════════════════════════════ get_projects() { local tmpout="${TEMP_DIR}/fetch_proj_out.log" local tmperr="${TEMP_DIR}/fetch_proj_err.log" local tok; tok=$(_read_wrangler_token) || true local account_id; account_id=$(get_account_id) || true local pid raw="" _do_api_fetch() { local aid="$1" pid=$(run_bg "$tmpout" "$tmperr" bash -c "curl -s -w '\n%{http_code}' -H \"Authorization: Bearer $tok\" \"https://api.cloudflare.com/client/v4/accounts/${aid}/pages/projects?per_page=1000\"") animated_box_wait "$pid" "Fetching Pages projects" "$CYAN" local esc=$? wait "$pid" 2>/dev/null || true [ "$esc" -eq 130 ] && { rm -f "$tmpout" "$tmperr"; return 130; } local full; full=$(cat "$tmpout" 2>/dev/null) local code; code=$(printf '%s' "$full" | tail -n1 | tr -d '\r') rm -f "$tmpout" "$tmperr" if [ "$code" = "200" ]; then raw=$(printf '%s' "$full" | sed '$d') return 0 fi return 1 } # Layer 1: API with known account_id if [ -n "$tok" ] && [ -n "$account_id" ]; then _do_api_fetch "$account_id" || { [ $? -eq 130 ] && return 130; } fi # Layer 2: wrangler CLI — capture table OR extract account_id from stderr for API retry if [ -z "$raw" ]; then pid=$(run_bg "$tmpout" "$tmperr" wrangler pages project list) animated_box_wait "$pid" "Fetching Pages projects" "$CYAN" local esc2=$? wait "$pid" 2>/dev/null || true [ "$esc2" -eq 130 ] && { rm -f "$tmpout" "$tmperr"; return 130; } raw=$(cat "$tmpout" 2>/dev/null) local wrgl_stderr; wrgl_stderr=$(cat "$tmperr" 2>/dev/null) rm -f "$tmpout" "$tmperr" # If wrangler failed, try to extract account_id from its error and retry API if [ -z "$raw" ] && [ -n "$tok" ]; then local extracted_id extracted_id=$(printf '%s' "$wrgl_stderr" | grep -o '/accounts/[a-f0-9]\{32\}/' | head -1 | grep -o '[a-f0-9]\{32\}') if [ -n "$extracted_id" ]; then mkdir -p "$(dirname "$ACCOUNT_ID_CACHE")" printf '%s' "$extracted_id" > "$ACCOUNT_ID_CACHE" _do_api_fetch "$extracted_id" || { [ $? -eq 130 ] && return 130; } fi fi fi # Parse: extract project names if [ -n "$raw" ]; then if printf '%s' "$raw" | grep -q '^{'; then printf '%s' "$raw" | jq -r '.result[]?.name // empty' 2>/dev/null | sort -u else printf '%s' "$raw" | grep -o '^│[[:space:]]*[a-zA-Z0-9-]*[[:space:]]*│' | sed -e 's/^│[[:space:]]*//' -e 's/[[:space:]]*│$//' | grep -v -E '^(Project|Name|build|deploy)$' | grep -v '^$' | sort -u fi fi } _delete_pages_project() { local project="$1" local account_id; account_id=$(get_account_id) || return 1 local tok; tok=$(_read_wrangler_token) || return 1 local code; code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE -H "Authorization: Bearer $tok" "https://api.cloudflare.com/client/v4/accounts/${account_id}/pages/projects/${project}") if [ "$code" = "200" ]; then return 0; else return 1; fi } invoke_delete() { clear local projects=() local raw_projs raw_projs=$(get_projects) local rc=$? [ "$rc" -eq 130 ] && return if [ -n "$raw_projs" ]; then while IFS= read -r name; do [ -n "$name" ] && projects+=("$name") done <<< "$raw_projs" fi if [ "${#projects[@]}" -eq 0 ]; then clear show_box "No Pages projects found" "$RED" printf "\n%b--- API Debug Output ---%b\n" "$YELLOW" "$RESET" >/dev/tty cat "${TEMP_DIR}/debug_api.log" 2>/dev/null >/dev/tty printf "\n%b--- CLI Debug Output ---%b\n" "$YELLOW" "$RESET" >/dev/tty cat "${TEMP_DIR}/debug_cli.log" 2>/dev/null >/dev/tty printf "\n\n" >/dev/tty _tty_pause " [-] Press any key to continue..." return fi clear show_box "Delete Pages Projects" "$RED" printf " %b[↑↓] Select | [Enter] Confirm | [Esc] Back | [Space] Toggle%b\n\n" "$GRAY" "$RESET" >/dev/tty local status_str; status_str=$(test_pages_status "${projects[@]}") local selected selected=$(ask_menu "" "Red" 1 "$status_str" "${projects[@]}") || return local to_delete=($selected) [ "${#to_delete[@]}" -eq 0 ] && return printf "\n%b Selected:%b\n" "$CYAN" "$RESET" for p in "${to_delete[@]}"; do printf " %b └─ %s%b\n" "$RED" "$p" "$RESET" done printf "\n" ask_confirm "Delete ${#to_delete[@]} project(s)?" || return local total="${#to_delete[@]}" i=1 for p in "${to_delete[@]}"; do spinner_run "[${i}/${total}] Deleting ${p}..." _delete_pages_project "${p}" i=$(( i + 1 )) done _tty_pause " [ Press ENTER to continue ]" } # ══════════════════════════════════════════════════════════ # MAIN MENU # ══════════════════════════════════════════════════════════ show_main_menu_header() { clear if [ -n "$ACCOUNT_SUBDOMAIN" ]; then show_box "Subdomain: ${ACCOUNT_SUBDOMAIN}" "$MAGENTA" [ -z "$PROXY_URL" ] && PROXY_URL="https://proxy.${ACCOUNT_SUBDOMAIN}.workers.dev" else printf "\n %b[!]%b Subdomain not detected\n\n" "$CYAN" "$RESET" fi printf " %b[↑↓] Select | [Enter] Confirm | [Esc] Back%b\n\n" "$GRAY" "$RESET" >/dev/tty } # ══════════════════════════════════════════════════════════ # ENTRY POINT # ══════════════════════════════════════════════════════════ install_deps() { local missing=0 for cmd in curl openssl jq; do if ! check_cmd "$cmd"; then missing=1; fi done if [ "$missing" -eq 1 ]; then show_box "Checking Dependencies" "$CYAN" if ! check_cmd "curl"; then pkg_install "curl"; else status_ok "curl"; fi if ! check_cmd "openssl"; then spinner_run "Installing openssl..." bash -c "pkg install -y openssl-tool >/dev/null 2>&1"; else status_ok "openssl"; fi if ! check_cmd "jq"; then pkg_install "jq"; else status_ok "jq"; fi printf "\n" >/dev/tty fi } main() { clear install_deps setup_wrangler local tok; tok=$(_read_wrangler_token) || tok="" if [ -n "$tok" ]; then export CLOUDFLARE_API_TOKEN="$tok" fi # Detect subdomain if not done during setup if [ -z "$ACCOUNT_SUBDOMAIN" ]; then ACCOUNT_SUBDOMAIN=$(detect_subdomain 2>/dev/null || true) fi while true; do show_main_menu_header local menu_items=( "Deploy Pages Project" "Create Proxy Worker" "Delete Pages Projects" "Re-Login to Cloudflare" "Exit" ) local choice choice=$(ask_menu "" "Purple" 0 "" "${menu_items[@]}") if [ "$?" -ne 0 ]; then continue fi case "$choice" in 0) clear; invoke_deploy ;; 1) clear; invoke_proxy_setup ;; 2) clear; invoke_delete ;; 3) clear show_box "Re-authenticating" "$YELLOW" _clear_wrangler_config ACCOUNT_SUBDOMAIN="" _patch_workerd 2>/dev/null wrangler login /dev/tty 2>/dev/tty ACCOUNT_SUBDOMAIN=$(detect_subdomain 2>/dev/null || true) ;; 4) clear; exit 0 ;; esac done } main "$@"