Last active
September 3, 2026 15:26
-
-
Save jotafeldmann/be24e4f66ceedd7f534757e358624e25 to your computer and use it in GitHub Desktop.
ai-status.sh: Quick diagnostic for the four major AI providers plus the shared layers underneath. Generated by Qwen3.8-27B Locally on an NVIDIA RTX 5090
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ai-status.sh | |
| # Quick diagnostic for the four major AI providers plus the shared layers underneath. | |
| # Separates an edge failure (CDN/DNS) from an inference failure (provider status page). | |
| # | |
| # Usage: | |
| # ./ai-status.sh # run once, everything | |
| # ./ai-status.sh edge # run once, edge only | |
| # ./ai-status.sh --watch # loop every 300s (default) | |
| # ./ai-status.sh --watch 60 # loop every 60s | |
| # ./ai-status.sh --watch 60 edge # loop every 60s, edge section only | |
| # ./ai-status.sh 60 # shorthand: bare number implies --watch | |
| # ./ai-status.sh --watch --clear # loop and redraw in place | |
| # | |
| # Sections: all (default) | edge | status | infra | |
| # Requires: curl. Optional: dig (dnsutils), jq (clean status page output). | |
| set -uo pipefail | |
| RED=$'\033[31m'; GRN=$'\033[32m'; YEL=$'\033[33m'; DIM=$'\033[2m'; BLD=$'\033[1m'; RST=$'\033[0m' | |
| TIMEOUT=8 | |
| MODE="all" | |
| WATCH=0 | |
| INTERVAL="${AI_STATUS_INTERVAL:-300}" | |
| CLEAR=0 | |
| have() { command -v "$1" >/dev/null 2>&1; } | |
| is_num() { [[ "${1:-}" =~ ^[0-9]+$ ]]; } | |
| hr() { printf '%s\n' "${DIM}$(printf '=%.0s' {1..64})${RST}"; } | |
| head2() { printf '\n%s%s%s\n' "$BLD" "$1" "$RST"; } | |
| usage() { | |
| awk 'NR==1 { next } /^#/ { sub(/^# ?/, ""); print; next } { exit }' "$0" | |
| exit 0 | |
| } | |
| # ---------- argument parsing ---------- | |
| parse_args() { | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -w|--watch) | |
| WATCH=1 | |
| # optional interval right after the flag | |
| if is_num "${2:-}"; then INTERVAL="$2"; shift; fi | |
| ;; | |
| -n|--interval) | |
| if is_num "${2:-}"; then INTERVAL="$2"; WATCH=1; shift | |
| else printf '%serror: --interval needs a number of seconds%s\n' "$RED" "$RST" >&2; exit 2; fi | |
| ;; | |
| -c|--clear) CLEAR=1 ;; | |
| -1|--once) WATCH=0 ;; | |
| -h|--help) usage ;; | |
| all|edge|status|infra) MODE="$1" ;; | |
| *) | |
| if is_num "$1"; then WATCH=1; INTERVAL="$1" | |
| else printf '%serror: unknown argument %s (try --help)%s\n' "$RED" "$1" "$RST" >&2; exit 2; fi | |
| ;; | |
| esac | |
| shift | |
| done | |
| if [[ "$INTERVAL" -lt 10 ]]; then | |
| printf '%swarning: interval below 10s risks rate limiting, raising to 10s%s\n' "$YEL" "$RST" >&2 | |
| INTERVAL=10 | |
| fi | |
| } | |
| # ---------- layer 1: DNS + edge ---------- | |
| edge_check() { | |
| local label="$1" host="$2" | |
| printf ' %-22s' "$label" | |
| # name resolution | |
| local ips="" | |
| if have dig; then | |
| ips=$(dig +short +time=3 +tries=1 A "$host" 2>/dev/null | grep -E '^[0-9]+\.' | head -3 | paste -sd, -) | |
| else | |
| ips=$(getent ahostsv4 "$host" 2>/dev/null | awk '{print $1}' | sort -u | head -3 | paste -sd, -) | |
| [[ -z "$ips" ]] && ips=$(getent hosts "$host" 2>/dev/null | awk '{print $1}' | head -3 | paste -sd, -) | |
| fi | |
| if [[ -z "$ips" ]]; then | |
| printf '%sDNS FAILED%s (name does not resolve)\n' "$RED" "$RST" | |
| return | |
| fi | |
| # HTTP headers | |
| local hdrs code server edge | |
| hdrs=$(curl -sS -m "$TIMEOUT" -o /dev/null -D - "https://$host" 2>/dev/null) | |
| code=$(printf '%s' "$hdrs" | awk 'toupper($1) ~ /^HTTP/ {c=$2} END{print c}') | |
| server=$(printf '%s' "$hdrs" | grep -i '^server:' | head -1 | cut -d' ' -f2- | tr -d '\r') | |
| # who sits in front | |
| if printf '%s' "$hdrs" | grep -qi '^cf-ray:'; then edge="Cloudflare" | |
| elif printf '%s' "$hdrs" | grep -qi 'x-amz-cf-id\|^x-amz-cf-pop:'; then edge="AWS CloudFront" | |
| elif printf '%s' "$hdrs" | grep -qi '^x-served-by:.*fastly\|^fastly-'; then edge="Fastly" | |
| elif printf '%s' "$hdrs" | grep -qi '^x-azure-ref:'; then edge="Azure Front Door" | |
| elif printf '%s' "$hdrs" | grep -qi '^via:.*google'; then edge="Google" | |
| else edge="${server:-?}" | |
| fi | |
| local color="$GRN" | |
| [[ -z "$code" ]] && { code="no response"; color="$RED"; } | |
| [[ "$code" =~ ^5 ]] && color="$RED" | |
| [[ "$code" =~ ^4 ]] && color="$YEL" | |
| printf '%s%-12s%s edge:%-17s ip:%s\n' "$color" "$code" "$RST" "$edge" "${ips%%,*}" | |
| } | |
| run_edge() { | |
| head2 "EDGE (DNS + CDN + HTTP)" | |
| printf ' %sHTTP 401/403 on an API endpoint is expected, it means the edge is alive.%s\n' "$DIM" "$RST" | |
| edge_check "Claude web" "claude.ai" | |
| edge_check "Claude API" "api.anthropic.com" | |
| edge_check "ChatGPT web" "chatgpt.com" | |
| edge_check "OpenAI API" "api.openai.com" | |
| edge_check "Grok web" "grok.com" | |
| edge_check "xAI API" "api.x.ai" | |
| edge_check "Gemini web" "gemini.google.com" | |
| edge_check "Google AI API" "generativelanguage.googleapis.com" | |
| } | |
| # ---------- layer 2: provider status pages ---------- | |
| # Atlassian Statuspage convention: /api/v2/summary.json | |
| statuspage() { | |
| local label="$1" url="$2" | |
| printf ' %-22s' "$label" | |
| local json | |
| json=$(curl -sS -m "$TIMEOUT" "$url" 2>/dev/null) | |
| if [[ -z "$json" ]]; then | |
| printf '%s? no response%s\n' "$YEL" "$RST" | |
| return | |
| fi | |
| local desc unresolved | |
| if have jq; then | |
| desc=$(printf '%s' "$json" | jq -r '.status.description // "?"' 2>/dev/null) | |
| unresolved=$(printf '%s' "$json" \ | |
| | jq -r '[.incidents[]? | select(.status!="resolved") | .name] | join(" | ")' 2>/dev/null) | |
| else | |
| desc=$(printf '%s' "$json" | grep -o '"description":"[^"]*"' | head -1 | cut -d'"' -f4) | |
| unresolved=$(printf '%s' "$json" | grep -o '"name":"[^"]*"' | head -3 | cut -d'"' -f4 | paste -sd' | ' -) | |
| fi | |
| local color="$GRN" | |
| [[ "$desc" != "All Systems Operational" ]] && color="$YEL" | |
| [[ "$desc" == *"Major"* ]] && color="$RED" | |
| printf '%s%s%s\n' "$color" "${desc:-?}" "$RST" | |
| [[ -n "${unresolved:-}" && "$unresolved" != "null" ]] && printf ' %s-> %s%s\n' "$DIM" "$unresolved" "$RST" | |
| } | |
| run_status() { | |
| head2 "PROVIDER STATUS PAGES" | |
| statuspage "Anthropic / Claude" "https://status.claude.com/api/v2/summary.json" | |
| statuspage "OpenAI" "https://status.openai.com/api/v2/summary.json" | |
| statuspage "xAI / Grok" "https://status.x.ai/api/v2/summary.json" | |
| # Google does not use Statuspage | |
| printf ' %-22s' "Google Cloud / AI" | |
| local g | |
| g=$(curl -sS -m "$TIMEOUT" "https://status.cloud.google.com/incidents.json" 2>/dev/null) | |
| if [[ -z "$g" ]]; then | |
| printf '%s? no response%s\n' "$YEL" "$RST" | |
| elif have jq; then | |
| local open | |
| open=$(printf '%s' "$g" | jq -r '[.[] | select(.end==null) | .external_desc] | join(" | ")' 2>/dev/null) | |
| if [[ -z "$open" || "$open" == "null" ]]; then | |
| printf '%sNo open incidents%s\n' "$GRN" "$RST" | |
| else | |
| printf '%s%s%s\n' "$YEL" "${open:0:80}" "$RST" | |
| fi | |
| else | |
| printf '%sfetched, install jq to parse%s\n' "$DIM" "$RST" | |
| fi | |
| } | |
| # ---------- layer 3: shared infrastructure ---------- | |
| run_infra() { | |
| head2 "SHARED INFRASTRUCTURE (root cause usually lives here)" | |
| statuspage "Cloudflare" "https://www.cloudflarestatus.com/api/v2/summary.json" | |
| statuspage "AWS" "https://health.aws.amazon.com/api/v2/summary.json" | |
| statuspage "Azure" "https://azure.status.microsoft/api/v2/summary.json" | |
| statuspage "Fastly" "https://status.fastly.com/api/v2/summary.json" | |
| # local resolver sanity check | |
| printf '\n %-22s' "Local DNS resolver" | |
| if have dig; then | |
| local t | |
| t=$(dig +short +time=3 +tries=1 cloudflare.com 2>/dev/null | head -1) | |
| [[ -n "$t" ]] && printf '%sok%s\n' "$GRN" "$RST" || printf '%sfailing, retry against 1.1.1.1%s\n' "$RED" "$RST" | |
| else | |
| printf '%sdig not installed%s\n' "$DIM" "$RST" | |
| fi | |
| } | |
| # ---------- how to read it ---------- | |
| verdict() { | |
| head2 "HOW TO READ THIS" | |
| cat <<'EOF' | |
| Edge 5xx across all four at once -> shared CDN or DNS, check Cloudflare first. | |
| Edge fine but status page degraded -> provider-internal inference failure. | |
| One provider only, edge and status clean -> your network, VPN, proxy or account. | |
| Login failing while API works -> identity plane, switching models will not help. | |
| EOF | |
| } | |
| # ---------- run one full pass ---------- | |
| run_once() { | |
| local iter="${1:-1}" | |
| [[ "$CLEAR" -eq 1 ]] && printf '\033[2J\033[H' | |
| hr | |
| if [[ "$WATCH" -eq 1 ]]; then | |
| printf '%sAI STATUS%s %s %s(pass %s, every %ss, ctrl-c to stop)%s\n' \ | |
| "$BLD" "$RST" "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$DIM" "$iter" "$INTERVAL" "$RST" | |
| else | |
| printf '%sAI STATUS%s %s\n' "$BLD" "$RST" "$(date '+%Y-%m-%d %H:%M:%S %Z')" | |
| fi | |
| if [[ "$iter" -eq 1 ]]; then | |
| have dig || printf '%s warning: dig missing (apt install dnsutils), falling back to getent%s\n' "$YEL" "$RST" | |
| have jq || printf '%s warning: jq missing (apt install jq / brew install jq), degraded output%s\n' "$YEL" "$RST" | |
| fi | |
| hr | |
| case "$MODE" in | |
| edge) run_edge ;; | |
| status) run_status ;; | |
| infra) run_infra ;; | |
| *) | |
| run_edge; run_status; run_infra | |
| # the reading guide is static, show it on the first pass only | |
| [[ "$iter" -eq 1 ]] && verdict | |
| ;; | |
| esac | |
| printf '\n' | |
| } | |
| main() { | |
| parse_args "$@" | |
| if [[ "$WATCH" -eq 0 ]]; then | |
| run_once 1 | |
| return | |
| fi | |
| trap 'printf "\n%sstopped after %s passes%s\n" "$DIM" "$i" "$RST"; exit 0' INT TERM | |
| local i=1 | |
| while :; do | |
| run_once "$i" | |
| [[ "$CLEAR" -eq 0 ]] && printf '%s next pass in %ss%s\n' "$DIM" "$INTERVAL" "$RST" | |
| sleep "$INTERVAL" | |
| i=$((i + 1)) | |
| done | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment