#!/usr/bin/env bash
# Install the signed ChatGPT Linux preview and Codex CLI on Slackware.
# The optional community computer-use patch is enabled by default.
set -Eeuo pipefail
STREAMED_INPUT=0
[[ -n ${BASH_SOURCE[0]:-} ]] || STREAMED_INPUT=1
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
export PATH

DEB_BASE='https://persistent.oaistatic.com/codex-app-prod/linux/deb'
SELF_URL='https://ligand.su/skynet/setup.sh'
PREBUILT_BASE='https://ligand.su/skynet/artifacts/v1'
MIRROR_BASE="$PREBUILT_BASE/mirror"
PREBUILT_DESKTOP_VERSION='26.917.62051'
PREBUILT_DESKTOP_SHA='54d7175f59d087aea2a9a5e39ab20f45c6836129d0cc87ed34ae6569b2cc390a'
PREBUILT_CLI_VERSION='0.156.1'
PREBUILT_CLI_SHA='e7b3e96e9c27b24a2fd152a1f668e7de673f7b43516f784a366ad1dfefb39762'
SUPPORT_BUNDLE_SHA='98a89db917f47fe8544d391e37c3fcb478bc054a80e3ff1dbbbb54f6a50f9c3f'
SUPPORT_BUNDLE_NAME='support-cu-v1-x86_64-glibc228.tar.gz'
DEB_KEY_URL='https://persistent.oaistatic.com/codex-app-prod/linux/repository-signing-key.gpg'
DEB_KEY_FINGERPRINT='3BFA0E4AE8B8CC16A2D9BA684A3B4A566C4660E4'
CODEX_CDN='https://releases.openai.com/codex/channels/latest'
CODEX_API='https://api.github.com/repos/openai/codex/releases/latest'
COMMUNITY_COMMIT='dd8bfecde5e1f9e30b1065579efa02991356caa9'
COMMUNITY_SHA='893ddfcbb508c82a8ff0f98bc0c211f52b2c3a5ef90700e0429b244d2434756d'
COMMUNITY_URL="https://codeload.github.com/ilysenko/codex-desktop-linux/tar.gz/$COMMUNITY_COMMIT"
CUA_BACKEND_COMMIT='c60d5ae7d8d94b1712c25c776b2025024377c60b'
CUA_BACKEND_SHA='8dfe6204cb2b8977e3119a3d4778e85e5296a977b86694bbfb8fea1edb1063a3'
CUA_BACKEND_URL="https://codeload.github.com/agent-sh/computer-use-linux/tar.gz/$CUA_BACKEND_COMMIT"
NPM_TAR_URL='https://registry.npmjs.org/npm/-/npm-12.1.0.tgz'
NPM_TAR_SHA='f08e674e31eb99931df2297aa55c274bf9bebd90679dad57fdbe9683907a3325'
XDOTOOL_URL='https://github.com/jordansissel/xdotool/archive/v4.20260303.1/xdotool-4.20260303.1.tar.gz'
XDOTOOL_SHA='c1f971a384da588eb99ca0755fc4300316d49c1e612537e3f1de52215e104fa3'
WMCTRL_URL='https://codeload.github.com/dancor/wmctrl/tar.gz/0181e8e4a7ed98255f6ef930875e56560f19aa95'
WMCTRL_SHA='345513dc1ba9a6c5660fa2a1578a208bf453b97515f806ba4a601d0429a69868'
DESKTOP_STATE='/var/lib/chatgpt-slackware-install/version'
CLI_STATE='/var/lib/skynet-setup/codex-version'
APP_BIN='/usr/lib/chatgpt/ChatGPT'
CLI_BIN='/opt/openai/codex/bin/codex'
BUILD='1_oai'

MODE='both'
ACTION='install'
COMMUNITY=1
PREBUILT_DESKTOP=0
PREBUILT_CLI=0
SUPPORT_BUNDLE=0
WORKDIR=''
PYTHON=''
PIDS=()
CURRENT_STAGE='START'
MUTATION_STARTED=0
CAN_MOVE_CURSOR=0
TERM_COLS="${COLUMNS:-80}"
[[ "$TERM_COLS" =~ ^[0-9]+$ ]] || TERM_COLS=80
[[ -t 1 && ${TERM:-dumb} != dumb && $TERM_COLS -ge 70 ]] && CAN_MOVE_CURSOR=1
if (( CAN_MOVE_CURSOR )) && [[ -z ${NO_COLOR:-} ]]; then
    GREEN=$'\033[1;32m'; YELLOW=$'\033[1;33m'; RED=$'\033[1;31m'; RESET=$'\033[0m'
else
    GREEN=''; YELLOW=''; RED=''; RESET=''
fi
say()  { printf '%s[ .. ]%s %s\n' "$GREEN" "$RESET" "$*"; }
ok()   { printf '%s[ OK ]%s %s\n' "$GREEN" "$RESET" "$*"; }
warn() { printf '%s[WARN]%s %s\n' "$YELLOW" "$RESET" "$*" >&2; }
die()  {
    printf '\n%s[FAIL]%s %s :: %s\n' "$RED" "$RESET" "$CURRENT_STAGE" "$*" >&2
    if (( MUTATION_STARTED )); then
        warn "STATE: package changes may be partial."
    else
        warn "STATE: no packages were changed."
    fi
    warn "RECOVER: fix the cause, then rerun this script."
    exit 1
}
banner() {
    printf '\n'
    if (( TERM_COLS < 74 )) || [[ ${TERM:-dumb} == dumb ]]; then
        printf 'SKYNET // BOOTSTRAP NODE\n'
        printf 'OPENAI UPLINK: ChatGPT Desktop + Codex CLI / Slackware\n'
    else
        printf '%s' "$RED"
        printf '+--------------------------------------------------------------------+\n'
        printf '| S K Y N E T   //   B O O T S T R A P   N O D E                     |\n'
        printf '|                                                                    |\n'
        printf '|           .------------------------------.                         |\n'
        printf '|       [ O ]  NEURAL LINK / SLACKWARE   [ O ]                       |\n'
        printf '|           `-------------+----------------`                         |\n'
        printf '|                         |                                          |\n'
        printf '|              +----------+----------+                               |\n'
        printf '|              |                     |                               |\n'
        printf '|       CHATGPT DESKTOP          CODEX CLI                           |\n'
        printf '+--------------------------------------------------------------------+\n'
        printf '%s' "$RESET"
    fi
    printf '  MODE: %s   PROTOCOL: signed release + SHA-256\n' "${MODE^^}"
}
portal_open() {
    local desktop_status='STANDBY' cli_status='STANDBY' control_status='OFF'
    [[ "$MODE" == cli ]] || desktop_status='ONLINE'
    [[ "$MODE" == desktop ]] || cli_status='ONLINE'
    if [[ "$MODE" != cli && $COMMUNITY -eq 1 ]]; then control_status='ARMED'; fi
    if (( TERM_COLS < 74 )) || [[ ${TERM:-dumb} == dumb ]]; then
        printf '\nSKYNET // PORTAL OPEN\n'
        printf 'DESKTOP: %s  CODEX: %s  ANY APP: %s\n' "$desktop_status" "$cli_status" "$control_status"
        return 0
    fi
    printf '\n%s' "$RED"
    printf '+--------------------------------------------------------------------+\n'
    printf '|  S K Y N E T  //  P O R T A L   O P E N                            |\n'
    printf '|                                                                    |\n'
    printf '|    LOCAL NODE -------- NEURAL LINK -------- OPENAI                 |\n'
    printf '|         |                                    |                     |\n'
    printf '|         +-------- DESKTOP / CODEX ----------+                      |\n'
    printf '|                                                                    |\n'
    printf '|  ACCESS GRANTED                                    SESSION READY   |\n'
    printf '+--------------------------------------------------------------------+\n'
    printf '%s' "$RESET"
    printf '  DESKTOP: %-8s   CODEX: %-8s   ANY APP: %s\n' "$desktop_status" "$cli_status" "$control_status"
}
phase() {
    CURRENT_STAGE="$2"
    printf '\n%s[%02d/07] %-10s%s %s\n' "$GREEN" "$1" "$2" "$RESET" "$3"
}
cleanup() {
    local pid had_children=0
    for pid in "${PIDS[@]-}"; do
        [[ -n "$pid" ]] || continue
        had_children=1
        kill -TERM "$pid" 2>/dev/null || true
    done
    if (( had_children )); then
        # A just-started curl can still be alive after TERM. Reap it before
        # removing its output directory, with a bounded KILL fallback.
        sleep 1 2>/dev/null || true
        for pid in "${PIDS[@]-}"; do
            [[ -n "$pid" ]] || continue
            if kill -0 "$pid" 2>/dev/null; then
                kill -KILL "$pid" 2>/dev/null || true
            fi
        done
        for pid in "${PIDS[@]-}"; do
            [[ -n "$pid" ]] || continue
            wait "$pid" 2>/dev/null || true
        done
    fi
    [[ -z "$WORKDIR" || ! -d "$WORKDIR" ]] || rm -rf -- "$WORKDIR" || true
}
trap cleanup EXIT
trap 'die "Unexpected command failure at line $LINENO."' ERR

usage() {
    cat <<'EOF'
SKYNET SETUP - ChatGPT Desktop + Codex CLI for Slackware

Usage: bash skynet_setup.sh [option]
       sudo bash skynet_setup.sh [option]

  (no option)      Install or update ChatGPT Desktop and Codex CLI
  --cli-only       Install or update only Codex CLI
  --desktop-only   Install or update only ChatGPT Desktop
  --official-only  Use unmodified OpenAI Desktop (no Any App patch)
  --diagnose       Show local compatibility checks (no root needed)
  --uninstall      Remove packages installed by this script
  --help           Show this help

Default desktop install includes the pinned, unofficial Linux Any App patch.
The prebuilt x86_64 Slackware 15 fast path skips local compilation.
Other compatible systems use a source-build fallback that takes longer.
The Linux desktop app is a public preview. Slackware is not officially
supported. Browser actions are upstream; Any App is community maintained.
EOF
}

for arg in "$@"; do
    case "$arg" in
        --cli-only) [[ "$MODE" == both ]] || die "Choose one install mode."; MODE='cli' ;;
        --desktop-only) [[ "$MODE" == both ]] || die "Choose one install mode."; MODE='desktop' ;;
        --official-only) COMMUNITY=0 ;;
        --diagnose) [[ "$ACTION" == install ]] || die "Choose one action."; ACTION='diagnose' ;;
        --uninstall) [[ "$ACTION" == install ]] || die "Choose one action."; ACTION='uninstall' ;;
        --help|-h) ACTION='help' ;;
        *) die "Unknown option: $arg (try --help)" ;;
    esac
done

need() { command -v "$1" >/dev/null 2>&1 || die "Missing '$1'. Install the Slackware package that provides it."; }
root_or_sudo() {
    (( EUID == 0 )) && return 0
    if (( STREAMED_INPUT )) || [[ ! -f "$0" ]]; then
        die "For a streamed install use: curl -fsSL $SELF_URL | sudo bash"
    fi
    if command -v sudo >/dev/null 2>&1; then
        exec sudo -- bash "$0" "$@"
    fi
    die "Root is required. Use 'su -', then: bash '$0' ${*:-}"
}
secure_tmpdir() {
    local owner mode
    [[ -d /var/tmp && ! -L /var/tmp ]] || die "/var/tmp must be a real directory."
    owner="$(stat -c %u /var/tmp)"
    mode="$(stat -c %a /var/tmp)"
    [[ "$owner" == 0 ]] || die "/var/tmp must be owned by root."
    (( (8#$mode & 0022) == 0 || (8#$mode & 01000) != 0 )) ||
        die "/var/tmp is writable by others without the sticky bit."
}
lock_pkgtools() {
    need flock
    mkdir -p /var/lib/skynet-setup
    [[ -d /var/lib/skynet-setup && ! -L /var/lib/skynet-setup &&
       $(stat -c %u /var/lib/skynet-setup) == 0 ]] ||
        die "Package lock directory must be a real root-owned directory."
    exec 9> /var/lib/skynet-setup/setup.lock
    flock -n 9 || die "Another skynet_setup.sh process is changing packages."
}
detect_arch() {
    case "$(uname -m)" in
        x86_64|amd64) DEB_ARCH='amd64'; SLACK_ARCH='x86_64'; CODEX_ARCH='x86_64' ;;
        aarch64|arm64) DEB_ARCH='arm64'; SLACK_ARCH='aarch64'; CODEX_ARCH='aarch64' ;;
        *) die "OpenAI does not publish these Linux builds for $(uname -m)." ;;
    esac
}
check_slackware() {
    [[ -r /etc/slackware-version ]] ||
        die "This installer uses Slackware pkgtools; /etc/slackware-version is absent."
    need makepkg; need upgradepkg; need removepkg
}
check_glibc() {
    [[ "$MODE" == cli ]] && return 0
    local version major minor
    version="$(getconf GNU_LIBC_VERSION 2>/dev/null || true)"
    [[ -n "$version" ]] || version="$(ldd --version 2>/dev/null | awk 'NR==1 {print; exit}' || true)"
    [[ "$version" == *"GNU libc"* || "$version" == glibc* ]] ||
        die "This desktop build requires glibc; detected: ${version:-unknown}."
    [[ "$version" =~ ([0-9]+)\.([0-9]+)$ ]] ||
        die "Cannot determine glibc version for the desktop app. Use --cli-only if this is an old Slackware."
    major="${BASH_REMATCH[1]}"; minor="${BASH_REMATCH[2]}"
    GLIBC_MAJOR="$major"; GLIBC_MINOR="$minor"
    (( major > 2 || (major == 2 && minor >= 30) )) ||
        die "ChatGPT Desktop computer-use runtime needs glibc 2.30+; this system has $version. Codex CLI can still use --cli-only."
}
choose_python() {
    if command -v python3 >/dev/null 2>&1; then
        PYTHON=python3
    elif command -v python >/dev/null 2>&1; then
        PYTHON=python
    else
        die "Python 2 or 3 is needed to parse official release metadata."
    fi
}
available_kib() { df -Pk "$1" | awk 'NR==2 {print $4}'; }
mountpoint_of() { df -Pk "$1" | awk 'NR==2 {print $6}'; }
space_check() {
    local tmp_need=0 usr_need=0 opt_need=0 tmp_mount opt_probe=/opt
    [[ -d /opt ]] || opt_probe=/
    if (( WANT_DESKTOP )); then
        usr_need=1700000
        if (( PREBUILT_DESKTOP )); then tmp_need=$((tmp_need + 900000))
        else tmp_need=$((tmp_need + 2500000)); fi
    fi
    (( WANT_DESKTOP == 0 || COMMUNITY == 0 || PREBUILT_DESKTOP )) || tmp_need=$((tmp_need + 4000000))
    if (( WANT_CLI )); then
        opt_need=500000
        if (( PREBUILT_CLI )); then tmp_need=$((tmp_need + 300000))
        else tmp_need=$((tmp_need + 700000)); fi
    fi
    tmp_mount="$(mountpoint_of "$WORKDIR")"
    if [[ "$tmp_mount" == "$(mountpoint_of /usr)" ]]; then
        tmp_need=$((tmp_need + usr_need)); usr_need=0
    fi
    if [[ "$tmp_mount" == "$(mountpoint_of "$opt_probe")" ]]; then
        tmp_need=$((tmp_need + opt_need)); opt_need=0
    fi
    (( $(available_kib "$WORKDIR") >= tmp_need )) ||
        die "Need at least $((tmp_need / 1000)) MB free in /var/tmp."
    (( usr_need == 0 || $(available_kib /usr) >= usr_need )) ||
        die "Not enough free space under /usr for ChatGPT Desktop."
    (( opt_need == 0 || $(available_kib "$opt_probe") >= opt_need )) ||
        die "Not enough free space under /opt for Codex CLI."
}

# curl runs in separate jobs so the two metadata requests and the two payloads
# overlap. Output is kept separate to avoid tangled progress/errors.
fetch() {
    local retry_args=(--retry 4 --retry-delay 2)
    if curl --help all 2>/dev/null | grep -F -- '--retry-connrefused' >/dev/null; then
        retry_args+=(--retry-connrefused)
    fi
    curl -fLsS "${retry_args[@]}" \
        --connect-timeout 20 --max-time 1200 \
        --proto '=https' --proto-redir '=https' \
        -A 'skynet-setup/1.0' -o "$2" "$1"
}
transport_note() {
    local line="[ROUTE] $*"
    printf '%s\n' "$line" >> "${WORKDIR:-/dev/null}/transport.log" 2>/dev/null || true
    printf '%s\n' "$line" >&2
}
probe_route() {
    local url="$1" scratch="$2" expected_size="$3"
    local stats code bytes speed content_range total
    curl --help all 2>/dev/null | grep -F -- '--max-filesize' >/dev/null || return 1
    stats="$(curl -fLsS --range 0-65535 --max-filesize 65536 \
        --connect-timeout 3 --max-time 6 \
        --proto '=https' --proto-redir '=https' \
        -A 'skynet-setup/1.0 probe' -D "$scratch.headers" \
        -o "$scratch.body" -w '%{http_code} %{size_download} %{speed_download}' \
        "$url" 2>/dev/null)" || return 1
    read -r code bytes speed <<< "$stats"
    [[ "$code" == 206 && "$bytes" == 65536 && "$speed" =~ ^[0-9]+$ ]] || return 1
    content_range="$(awk '
        /^HTTP\// { value="" }
        tolower($1) == "content-range:" {
            sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); value=$0
        }
        END { print value }
    ' "$scratch.headers")"
    [[ "$content_range" =~ ^bytes[[:space:]]+0-65535/([0-9]+)$ ]] || return 1
    total="${BASH_REMATCH[1]}"
    (( total > 65536 )) || return 1
    (( expected_size == 0 || total == expected_size )) || return 1
    printf '%s\n' "$speed"
}
fetch_route_verified() {
    local url="$1" output="$2" size="$3" expected="$4" actual
    if ! curl -fLsS --connect-timeout 12 --max-time 900 \
        --speed-limit 1024 --speed-time 30 \
        --proto '=https' --proto-redir '=https' \
        -A 'skynet-setup/1.0' -o "$output" "$url"; then
        # Resume once against the same origin and same route-local temp file.
        # The combined wall-clock cap is 20 minutes; alternate origins never
        # share partial bytes.
        [[ -s "$output" ]] || return 1
        curl -fLsS -C - --connect-timeout 12 --max-time 300 \
            --speed-limit 1024 --speed-time 30 \
            --proto '=https' --proto-redir '=https' \
            -A 'skynet-setup/1.0' -o "$output" "$url" || return 1
    fi
    [[ -s "$output" ]] || return 1
    (( size == 0 )) || [[ "$(file_bytes "$output")" == "$size" ]] || return 1
    actual="$(sha256sum "$output")"; actual="${actual%% *}"
    [[ "$actual" == "$expected" ]]
}
fetch_adaptive() {
    local direct="$1" mirror="$2" output="$3" label="$4" size="$5" expected="$6" probe_large="$7"
    local direct_probe="${output}.direct.probe" mirror_probe="${output}.mirror.probe"
    local direct_speed='' mirror_speed='' primary alternate route attempt
    local direct_pid mirror_pid

    if [[ -z "$mirror" ]]; then
        attempt="${output}.route.$$"
        fetch_route_verified "$direct" "$attempt" "$size" "$expected" || { rm -f -- "$attempt"; return 1; }
        mv -f -- "$attempt" "$output"
        transport_note "$label selected=direct mirror=unavailable"
        return 0
    fi

    primary="$direct"; alternate="$mirror"; route='direct'
    if (( probe_large == 0 )); then
        # Pinned source archives are small; probing each origin can cost more
        # bytes than the payload. Use the immutable static mirror first.
        primary="$mirror"; alternate="$direct"; route='ligand.su'
        transport_note "$label selected=$route probe=skipped-small-pinned"
    else
        # Each probe consumes exactly 64 KiB only after a validated 206 response.
        # They run together, so selection adds at most six seconds total.
        probe_route "$direct" "${direct_probe}.data" "$size" > "$direct_probe" & direct_pid=$!
        probe_route "$mirror" "${mirror_probe}.data" "$size" > "$mirror_probe" & mirror_pid=$!
        wait "$direct_pid" 2>/dev/null || true
        wait "$mirror_pid" 2>/dev/null || true
        [[ ! -s "$direct_probe" ]] || direct_speed="$(<"$direct_probe")"
        [[ ! -s "$mirror_probe" ]] || mirror_speed="$(<"$mirror_probe")"
        rm -f -- "$direct_probe" "$mirror_probe" "${direct_probe}.data.headers" \
            "${direct_probe}.data.body" "${mirror_probe}.data.headers" "${mirror_probe}.data.body"

        # Prefer the stable mirror on a near tie (within 10%); otherwise take the
        # measured faster route. Failed probes never displace a reachable route.
        if [[ -n "$mirror_speed" && ( -z "$direct_speed" ||
              $((mirror_speed * 100)) -ge $((direct_speed * 90)) ) ]]; then
            primary="$mirror"; alternate="$direct"; route='ligand.su'
        fi
        transport_note "$label selected=$route direct_Bps=${direct_speed:-unavailable} mirror_Bps=${mirror_speed:-unavailable}"
    fi
    attempt="${output}.route.$$"
    if fetch_route_verified "$primary" "$attempt" "$size" "$expected"; then
        mv -f -- "$attempt" "$output"
        return 0
    fi
    rm -f -- "$attempt"
    if [[ "$route" == direct ]]; then route='ligand.su'; else route='direct'; fi
    transport_note "$label primary_failed retry=$route"
    attempt="${output}.route.$$"
    fetch_route_verified "$alternate" "$attempt" "$size" "$expected" || {
        rm -f -- "$attempt"
        transport_note "$label failed routes=direct,ligand.su"
        printf 'Adaptive download failed for %s (tried direct and ligand.su).\n' "$label" >&2
        return 1
    }
    mv -f -- "$attempt" "$output"
}
job_failed() {
    local pid="$1" label="$2" log="$3"
    if ! wait "$pid"; then
        [[ ! -s "$log" ]] || cat "$log" >&2
        die "$label download failed. Check network/proxy and retry."
    fi
}
file_bytes() {
    [[ -f "$1" ]] && stat -c %s "$1" || printf '0\n'
}
show_progress() {
    local desk_file="$1" desk_size="$2" cli_file="$3" cli_size="$4" alive pid
    (( CAN_MOVE_CURSOR )) || return 0
    while :; do
        alive=0
        for pid in "${PIDS[@]-}"; do
            [[ -n "$pid" ]] || continue
            kill -0 "$pid" 2>/dev/null && alive=1
        done
        printf '\033[2K'; progress_line 'Desktop' "$desk_file" "$desk_size"
        printf '\033[2K'; progress_line 'Codex CLI' "$cli_file" "$cli_size"
        (( alive )) || break
        sleep 1
        printf '\033[2A'
    done
}
progress_line() {
    local label="$1" file="$2" size="$3" bytes pct='--' mb bar='' i filled=0
    bytes="$(file_bytes "$file")"
    mb="$(( bytes / 1048576 ))"
    if (( size > 0 )); then
        pct="$(( 100 * bytes / size ))"
        (( pct > 100 )) && pct=100
        filled="$(( pct / 5 ))"
        pct="$pct%"
    fi
    for ((i=0; i<20; i++)); do
        if (( i < filled )); then bar+='#'; else bar+='.'; fi
    done
    printf '  %-9s [%s] %4s %4s MiB\n' "$label" "$bar" "$pct" "$mb"
}
verify_payload() {
    local file="$1" size="$2" expected="$3" label="$4" actual
    [[ -s "$file" ]] || die "$label was not downloaded."
    (( size == 0 )) || [[ "$(file_bytes "$file")" == "$size" ]] || die "$label size mismatch."
    actual="$(sha256sum "$file")"; actual="${actual%% *}"
    [[ "$actual" == "$expected" ]] || die "$label SHA-256 mismatch; refusing to install."
    ok "$label :: SHA-256 verified"
}
payload_matches() {
    local actual
    [[ -s "$1" ]] || return 1
    actual="$(sha256sum "$1")"; actual="${actual%% *}"
    [[ "$actual" == "$2" ]]
}

read_desktop_metadata() {
    local fields="$WORKDIR/desktop.fields"
    "$PYTHON" - "$WORKDIR/Packages" "$DEB_ARCH" > "$fields" <<'PY'
from __future__ import print_function
import io, re, sys
text = io.open(sys.argv[1], encoding="utf-8").read()
arch = sys.argv[2]
for stanza in re.split(r"\n\s*\n", text):
    fields = dict(line.split(": ", 1) for line in stanza.splitlines() if ": " in line and not line.startswith(" "))
    if fields.get("Package") == "chatgpt" and fields.get("Architecture") == arch:
        version, path, digest, size = (fields.get(k, "") for k in ("Version", "Filename", "SHA256", "Size"))
        if not re.match(r"^[0-9A-Za-z.+:~_-]+$", version):
            raise SystemExit("Invalid desktop package version")
        if path != "pool/main/c/chatgpt/chatgpt_" + version + "_" + arch + ".deb":
            raise SystemExit("Invalid desktop package path")
        if not re.match(r"^[0-9a-fA-F]{64}$", digest) or not size.isdigit() or int(size) < 1000000:
            raise SystemExit("Invalid desktop package checksum/size")
        print(version, path, digest.lower(), size, sep="\n")
        break
else:
    raise SystemExit("No official ChatGPT package for " + arch)
PY
    mapfile -t DEB_FIELDS < "$fields"
    DESKTOP_VERSION="${DEB_FIELDS[0]}"
    DEB_PATH="${DEB_FIELDS[1]}"
    DEB_SHA="${DEB_FIELDS[2]}"
    DEB_SIZE="${DEB_FIELDS[3]}"
}

verify_signed_desktop_index() {
    local fingerprint fields="$WORKDIR/index.fields" index_path
    index_path="main/binary-$DEB_ARCH/Packages"
    mkdir -m 0700 "$WORKDIR/gnupg"
    fingerprint="$(gpg --homedir "$WORKDIR/gnupg" --batch --no-default-keyring \
        --keyring "$WORKDIR/repo-key.gpg" --with-colons --fingerprint 2>/dev/null |
        awk -F: '$1 == "pub" {primary=1} $1 == "fpr" && primary {print $10; primary=0}')"
    [[ "$fingerprint" == "$DEB_KEY_FINGERPRINT" ]] ||
        die "OpenAI repository key fingerprint does not match the pinned value."
    gpgv --keyring "$WORKDIR/repo-key.gpg" "$WORKDIR/InRelease" > "$WORKDIR/gpgv.log" 2>&1 ||
        { cat "$WORKDIR/gpgv.log" >&2; die "OpenAI repository signature is invalid."; }
    "$PYTHON" - "$WORKDIR/InRelease" "$index_path" > "$fields" <<'PY'
from __future__ import print_function
import io, re, sys
path = sys.argv[2]
in_hashes = False
for line in io.open(sys.argv[1], encoding="utf-8"):
    if line == "SHA256:\n":
        in_hashes = True
        continue
    if in_hashes and line and line[0] not in " \t":
        break
    if in_hashes:
        parts = line.split()
        if len(parts) == 3 and parts[2] == path:
            digest, size = parts[:2]
            if not re.match(r"^[0-9a-fA-F]{64}$", digest) or not size.isdigit() or int(size) <= 0:
                raise SystemExit("Invalid signed Packages digest")
            print(digest.lower(), size, sep="\n")
            break
else:
    raise SystemExit("Packages is absent from signed InRelease")
PY
    [[ -s "$fields" ]] || die "Signed index metadata is incomplete."
    mapfile -t INDEX_FIELDS < "$fields"
    [[ ${#INDEX_FIELDS[@]} -eq 2 ]] || die "Signed index metadata is incomplete."
    verify_payload "$WORKDIR/Packages" "${INDEX_FIELDS[1]}" "${INDEX_FIELDS[0]}" 'Signed desktop index'
    ok "OpenAI repository signature verified"
}
read_cli_metadata() {
    local fields="$WORKDIR/cli.fields"
    "$PYTHON" - "$WORKDIR/release.json" "$CODEX_ARCH" > "$fields" <<'PY'
from __future__ import print_function
import json, re, sys
j = json.load(open(sys.argv[1]))
tag = j.get("tag_name", "")
if not re.match(r"^rust-v[0-9]+\.[0-9]+\.[0-9]+(?:[-.][0-9A-Za-z]+)*$", tag):
    raise SystemExit("Unexpected Codex release tag")
name = "codex-package-" + sys.argv[2] + "-unknown-linux-musl.tar.gz"
assets = [a for a in j.get("assets", []) if a.get("name") == name]
if len(assets) != 1:
    raise SystemExit("Official Codex package not found in latest release")
a = assets[0]
digest = a.get("digest", "")
url = a.get("browser_download_url", "")
size = a.get("size", 0)
if not re.match(r"^sha256:[0-9a-fA-F]{64}$", digest):
    raise SystemExit("Codex release has no usable SHA-256 digest")
allowed = (
    "https://releases.openai.com/codex/releases/" + tag[6:] + "/" + name,
    "https://github.com/openai/codex/releases/download/" + tag + "/" + name,
)
if url not in allowed:
    raise SystemExit("Unexpected Codex release URL")
if not isinstance(size, int) or size < 0:
    raise SystemExit("Unexpected Codex package size")
print(tag[6:], url, digest[7:].lower(), size, sep="\n")
PY
    mapfile -t CLI_FIELDS < "$fields"
    CLI_VERSION="${CLI_FIELDS[0]}"
    CLI_URL="${CLI_FIELDS[1]}"
    CLI_SHA="${CLI_FIELDS[2]}"
    CLI_SIZE="${CLI_FIELDS[3]}"
    local asset="codex-package-$CODEX_ARCH-unknown-linux-musl.tar.gz"
    if [[ "$CLI_URL" == https://releases.openai.com/* ]]; then
        CLI_ALT_URL="https://github.com/openai/codex/releases/download/rust-v$CLI_VERSION/$asset"
    else
        CLI_ALT_URL="https://releases.openai.com/codex/releases/$CLI_VERSION/$asset"
    fi
}

fetch_metadata() {
    local desk_pid='' cli_pid='' release_pid='' key_pid=''
    say "Checking official OpenAI releases (in parallel)"
    if [[ "$MODE" != cli ]]; then
        fetch "$DEB_BASE/dists/stable/main/binary-$DEB_ARCH/Packages" "$WORKDIR/Packages" > "$WORKDIR/desktop.log" 2>&1 &
        desk_pid=$!; PIDS+=("$desk_pid")
        fetch "$DEB_BASE/dists/stable/InRelease" "$WORKDIR/InRelease" > "$WORKDIR/inrelease.log" 2>&1 &
        release_pid=$!; PIDS+=("$release_pid")
        fetch "$DEB_KEY_URL" "$WORKDIR/repo-key.gpg" > "$WORKDIR/key.log" 2>&1 &
        key_pid=$!; PIDS+=("$key_pid")
    fi
    if [[ "$MODE" != desktop ]]; then
        { fetch "$CODEX_CDN" "$WORKDIR/release.json" ||
          fetch "$CODEX_API" "$WORKDIR/release.json"; } > "$WORKDIR/cli.log" 2>&1 &
        cli_pid=$!; PIDS+=("$cli_pid")
    fi
    [[ -z "$desk_pid" ]] || job_failed "$desk_pid" 'Desktop index' "$WORKDIR/desktop.log"
    [[ -z "$release_pid" ]] || job_failed "$release_pid" 'Signed release' "$WORKDIR/inrelease.log"
    [[ -z "$key_pid" ]] || job_failed "$key_pid" 'Repository key' "$WORKDIR/key.log"
    [[ -z "$cli_pid" ]] || job_failed "$cli_pid" 'Codex release metadata' "$WORKDIR/cli.log"
    PIDS=()
    [[ "$MODE" == cli ]] || { verify_signed_desktop_index; read_desktop_metadata; }
    [[ "$MODE" == desktop ]] || read_cli_metadata
}

already_installed() {
    WANT_DESKTOP=0; WANT_CLI=0
    if [[ "$MODE" != cli ]]; then
        DESKTOP_INSTALL_ID="$DESKTOP_VERSION"
        (( COMMUNITY == 0 )) || DESKTOP_INSTALL_ID="$DESKTOP_VERSION+cu.${COMMUNITY_COMMIT:0:12}"
        WANT_DESKTOP=1
        if [[ -x "$APP_BIN" && -x /usr/bin/chatgpt &&
              -s /usr/share/pixmaps/chatgpt.png &&
              -r "$DESKTOP_STATE" && "$(<"$DESKTOP_STATE")" == "$DESKTOP_INSTALL_ID" &&
              ( $COMMUNITY -eq 0 || ( -x /usr/lib/chatgpt/start.sh &&
                -x /usr/lib/chatgpt/resources/plugins/openai-bundled/plugins/unified-computer-use/bin/codex-computer-use-linux &&
                -x /usr/lib/chatgpt/.codex-linux/bin/xdotool &&
                -x /usr/lib/chatgpt/.codex-linux/bin/wmctrl ) ) ]]; then
            WANT_DESKTOP=0; say "ChatGPT Desktop $DESKTOP_VERSION is already installed"
        else
            if (( COMMUNITY )); then
                say "ChatGPT Desktop $DESKTOP_VERSION + Linux Any App patch is available"
            else
                say "ChatGPT Desktop $DESKTOP_VERSION is available"
            fi
        fi
    fi
    if [[ "$MODE" != desktop ]]; then
        WANT_CLI=1
        if [[ -x "$CLI_BIN" && -x /opt/openai/codex/codex-path/rg &&
              -x /opt/openai/codex/codex-resources/bwrap &&
              -e /usr/local/bin/codex && -r "$CLI_STATE" &&
              "$(<"$CLI_STATE")" == "$CLI_VERSION" ]]; then
            WANT_CLI=0; say "Codex CLI $CLI_VERSION is already installed"
        else
            say "Codex CLI $CLI_VERSION is available"
        fi
    fi
}
select_prebuilts() {
    PREBUILT_DESKTOP=0; PREBUILT_CLI=0
    if (( WANT_DESKTOP && COMMUNITY )) && [[ "$CODEX_ARCH" == x86_64 &&
          "$DESKTOP_VERSION" == "$PREBUILT_DESKTOP_VERSION" &&
          "$PREBUILT_DESKTOP_SHA" =~ ^[0-9a-f]{64}$ ]] &&
       (( GLIBC_MAJOR > 2 || (GLIBC_MAJOR == 2 && GLIBC_MINOR >= 33) )); then
        PREBUILT_DESKTOP=1
        say "Fast path: prebuilt patched Desktop for x86_64 / glibc 2.33+"
    fi
    if (( WANT_CLI )) && [[ "$CODEX_ARCH" == x86_64 &&
          "$CLI_VERSION" == "$PREBUILT_CLI_VERSION" &&
          "$PREBUILT_CLI_SHA" =~ ^[0-9a-f]{64}$ ]]; then
        PREBUILT_CLI=1
        say "Fast path: prebuilt Codex CLI Slackware package"
    fi
}
guard_existing_files() {
    local name path found=0 record
    for name in chatgpt codex-cli; do
        [[ "$name" == chatgpt && $WANT_DESKTOP -eq 1 ||
           "$name" == codex-cli && $WANT_CLI -eq 1 ]] || continue
        for path in /var/lib/pkgtools/packages /var/log/packages; do
            [[ -d "$path" ]] || continue
            for record in "$path"/"$name"-*-"$SLACK_ARCH"-*; do
                [[ -e "$record" ]] || continue
                if [[ "${record##*/}" != *-"$BUILD" ]]; then
                    die "Existing $name Slackware package is managed outside this script: ${record##*/}."
                fi
                found=1
            done
        done
        if (( found == 0 )); then
            if [[ "$name" == chatgpt && ( -e /usr/lib/chatgpt || -e /usr/bin/chatgpt ) ]]; then
                die "Existing ChatGPT Desktop files are outside this installer. Remove them before proceeding."
            fi
            if [[ "$name" == codex-cli && ( -e /opt/openai/codex || -e /usr/local/bin/codex || -L /usr/local/bin/codex ) ]]; then
                die "Existing Codex CLI files are outside this installer. Remove them before proceeding."
            fi
        fi
        found=0
    done
}
fetch_payloads() {
    local desk_pid='' cli_pid='' community_pid='' backend_pid='' npm_pid=''
    local xdotool_pid='' wmctrl_pid='' support_pid=''
    local desk_size=0 cli_size=0 desktop_mirror='' cli_mirror=''
    DEB_FILE="$WORKDIR/chatgpt.deb"
    CLI_FILE="$WORKDIR/codex-package.tar.gz"
    DESKTOP_PACKAGE=''; CLI_PACKAGE=''
    (( WANT_DESKTOP == 0 )) || DESKTOP_PACKAGE="$WORKDIR/chatgpt-${DESKTOP_VERSION//:/_}-$SLACK_ARCH-$BUILD.tgz"
    (( WANT_CLI == 0 )) || CLI_PACKAGE="$WORKDIR/codex-cli-$CLI_VERSION-$SLACK_ARCH-$BUILD.tgz"
    COMMUNITY_FILE="$WORKDIR/community-source.tar.gz"
    BACKEND_FILE="$WORKDIR/computer-use-source.tar.gz"
    NPM_FILE="$WORKDIR/npm.tar.gz"
    XDOTOOL_FILE="$WORKDIR/xdotool.tar.gz"
    WMCTRL_FILE="$WORKDIR/wmctrl.tar.gz"
    SUPPORT_FILE="$WORKDIR/$SUPPORT_BUNDLE_NAME"
    (( WANT_DESKTOP && ! PREBUILT_DESKTOP )) && desk_size="$DEB_SIZE"
    (( WANT_CLI && ! PREBUILT_CLI )) && cli_size="$CLI_SIZE"
    if (( WANT_DESKTOP )) && [[ "$DEB_ARCH" == amd64 ]]; then
        desktop_mirror="$MIRROR_BASE/chatgpt-$DESKTOP_VERSION-amd64.deb"
    fi
    if (( WANT_CLI )) && [[ "$CODEX_ARCH" == x86_64 ]]; then
        cli_mirror="$MIRROR_BASE/codex-$CLI_VERSION-x86_64-unknown-linux-musl.tar.gz"
    fi
    (( WANT_DESKTOP || WANT_CLI )) || return 0
    say "Downloading required components simultaneously"
    if (( WANT_DESKTOP )); then
        if (( PREBUILT_DESKTOP )); then
            if ! payload_matches "$DESKTOP_PACKAGE" "$PREBUILT_DESKTOP_SHA"; then
                fetch "$PREBUILT_BASE/${DESKTOP_PACKAGE##*/}" "$DESKTOP_PACKAGE" > "$WORKDIR/desktop-download.log" 2>&1 &
                desk_pid=$!; PIDS+=("$desk_pid")
            fi
        elif ! payload_matches "$DEB_FILE" "$DEB_SHA"; then
            fetch_adaptive "$DEB_BASE/$DEB_PATH" "$desktop_mirror" "$DEB_FILE" \
                'ChatGPT Desktop' "$DEB_SIZE" "$DEB_SHA" 1 > "$WORKDIR/desktop-download.log" 2>&1 &
            desk_pid=$!; PIDS+=("$desk_pid")
        fi
    fi
    if (( WANT_CLI )); then
        if (( PREBUILT_CLI )); then
            if ! payload_matches "$CLI_PACKAGE" "$PREBUILT_CLI_SHA"; then
                fetch "$PREBUILT_BASE/${CLI_PACKAGE##*/}" "$CLI_PACKAGE" > "$WORKDIR/cli-download.log" 2>&1 &
                cli_pid=$!; PIDS+=("$cli_pid")
            fi
        elif ! payload_matches "$CLI_FILE" "$CLI_SHA"; then
            { fetch_adaptive "$CLI_URL" "$cli_mirror" "$CLI_FILE" \
                  'Codex CLI' "$CLI_SIZE" "$CLI_SHA" 1 ||
              fetch_adaptive "$CLI_ALT_URL" '' "$CLI_FILE" \
                  'Codex CLI alternate origin' "$CLI_SIZE" "$CLI_SHA" 0; } > "$WORKDIR/cli-download.log" 2>&1 &
            cli_pid=$!; PIDS+=("$cli_pid")
        fi
    fi
    if (( WANT_DESKTOP && COMMUNITY && ! PREBUILT_DESKTOP )); then
        if ! payload_matches "$COMMUNITY_FILE" "$COMMUNITY_SHA"; then
            fetch_adaptive "$COMMUNITY_URL" "$MIRROR_BASE/codex-desktop-linux-dd8bfecde5e1.tar.gz" \
                "$COMMUNITY_FILE" 'Community patch source' 0 "$COMMUNITY_SHA" 0 > "$WORKDIR/community-download.log" 2>&1 &
            community_pid=$!; PIDS+=("$community_pid")
        fi
        if ! payload_matches "$BACKEND_FILE" "$CUA_BACKEND_SHA"; then
            fetch_adaptive "$CUA_BACKEND_URL" "$MIRROR_BASE/computer-use-linux-c60d5ae7d8d9.tar.gz" \
                "$BACKEND_FILE" 'Computer-use source' 0 "$CUA_BACKEND_SHA" 0 > "$WORKDIR/backend-download.log" 2>&1 &
            backend_pid=$!; PIDS+=("$backend_pid")
        fi
        if ! payload_matches "$NPM_FILE" "$NPM_TAR_SHA"; then
            fetch_adaptive "$NPM_TAR_URL" "$MIRROR_BASE/npm-12.1.0.tgz" \
                "$NPM_FILE" 'ASAR tooling' 0 "$NPM_TAR_SHA" 0 > "$WORKDIR/npm-download.log" 2>&1 &
            npm_pid=$!; PIDS+=("$npm_pid")
        fi
        if ! payload_matches "$XDOTOOL_FILE" "$XDOTOOL_SHA"; then
            fetch_adaptive "$XDOTOOL_URL" "$MIRROR_BASE/xdotool-4.20260303.1.tar.gz" \
                "$XDOTOOL_FILE" 'X11 input source' 0 "$XDOTOOL_SHA" 0 > "$WORKDIR/xdotool-download.log" 2>&1 &
            xdotool_pid=$!; PIDS+=("$xdotool_pid")
        fi
        if ! payload_matches "$WMCTRL_FILE" "$WMCTRL_SHA"; then
            fetch_adaptive "$WMCTRL_URL" "$MIRROR_BASE/wmctrl-0181e8e4a7ed.tar.gz" \
                "$WMCTRL_FILE" 'X11 window source' 0 "$WMCTRL_SHA" 0 > "$WORKDIR/wmctrl-download.log" 2>&1 &
            wmctrl_pid=$!; PIDS+=("$wmctrl_pid")
        fi
        if [[ "$CODEX_ARCH" == x86_64 ]] &&
           (( GLIBC_MAJOR > 2 || (GLIBC_MAJOR == 2 && GLIBC_MINOR >= 28) )); then
            if ! payload_matches "$SUPPORT_FILE" "$SUPPORT_BUNDLE_SHA"; then
                fetch "$PREBUILT_BASE/$SUPPORT_BUNDLE_NAME" "$SUPPORT_FILE" > "$WORKDIR/support-download.log" 2>&1 &
                support_pid=$!; PIDS+=("$support_pid")
            fi
        fi
    fi
    if (( PREBUILT_DESKTOP )); then
        local progress_desk="$DESKTOP_PACKAGE"
    else
        local progress_desk="$DEB_FILE"
    fi
    if (( PREBUILT_CLI )); then
        local progress_cli="$CLI_PACKAGE"
    else
        local progress_cli="$CLI_FILE"
    fi
    show_progress "$progress_desk" "$desk_size" "$progress_cli" "$cli_size"
    local fallback=0
    if [[ -n "$desk_pid" ]]; then
        if (( PREBUILT_DESKTOP )); then
            if ! wait "$desk_pid" || ! payload_matches "$DESKTOP_PACKAGE" "$PREBUILT_DESKTOP_SHA"; then
                warn "Prebuilt Desktop unavailable or checksum mismatch; using verified source build."
                rm -f -- "$DESKTOP_PACKAGE"
                PREBUILT_DESKTOP=0; fallback=1
            fi
        else
            job_failed "$desk_pid" 'Desktop package' "$WORKDIR/desktop-download.log"
        fi
    fi
    if [[ -n "$cli_pid" ]]; then
        if (( PREBUILT_CLI )); then
            if ! wait "$cli_pid" || ! payload_matches "$CLI_PACKAGE" "$PREBUILT_CLI_SHA"; then
                warn "Prebuilt Codex unavailable or checksum mismatch; using official source package."
                rm -f -- "$CLI_PACKAGE"
                PREBUILT_CLI=0; fallback=1
            fi
        else
            job_failed "$cli_pid" 'Codex package' "$WORKDIR/cli-download.log"
        fi
    fi
    [[ -z "$community_pid" ]] || job_failed "$community_pid" 'Community patch source' "$WORKDIR/community-download.log"
    [[ -z "$backend_pid" ]] || job_failed "$backend_pid" 'Computer-use source' "$WORKDIR/backend-download.log"
    [[ -z "$npm_pid" ]] || job_failed "$npm_pid" 'ASAR tooling' "$WORKDIR/npm-download.log"
    [[ -z "$xdotool_pid" ]] || job_failed "$xdotool_pid" 'X11 input source' "$WORKDIR/xdotool-download.log"
    [[ -z "$wmctrl_pid" ]] || job_failed "$wmctrl_pid" 'X11 window source' "$WORKDIR/wmctrl-download.log"
    SUPPORT_BUNDLE=0
    if [[ -n "$support_pid" ]]; then
        if wait "$support_pid" && payload_matches "$SUPPORT_FILE" "$SUPPORT_BUNDLE_SHA"; then
            SUPPORT_BUNDLE=1
        else
            warn "Prebuilt Linux support bundle unavailable; compiling locally."
            rm -f -- "$SUPPORT_FILE"
        fi
    elif (( WANT_DESKTOP && COMMUNITY && ! PREBUILT_DESKTOP )) &&
         payload_matches "$SUPPORT_FILE" "$SUPPORT_BUNDLE_SHA"; then
        SUPPORT_BUNDLE=1
    fi
    if [[ -s "$WORKDIR/transport.log" ]]; then
        say "Adaptive route decisions"
        sed 's/^/  /' "$WORKDIR/transport.log"
        : > "$WORKDIR/transport.log"
    fi
    PIDS=()
    if (( fallback )); then
        space_check
        fetch_payloads
        return 0
    fi
    phase 5 INTEGRITY "Check downloaded bytes and SHA-256"
    if (( WANT_DESKTOP )); then
        if (( PREBUILT_DESKTOP )); then
            verify_payload "$DESKTOP_PACKAGE" 0 "$PREBUILT_DESKTOP_SHA" 'Prebuilt patched Desktop'
        else
            verify_payload "$DEB_FILE" "$DEB_SIZE" "$DEB_SHA" 'ChatGPT Desktop'
        fi
    fi
    if (( WANT_CLI )); then
        if (( PREBUILT_CLI )); then
            verify_payload "$CLI_PACKAGE" 0 "$PREBUILT_CLI_SHA" 'Prebuilt Codex CLI'
        else
            verify_payload "$CLI_FILE" "$CLI_SIZE" "$CLI_SHA" 'Codex CLI'
        fi
    fi
    if (( WANT_DESKTOP && COMMUNITY && ! PREBUILT_DESKTOP )); then
        verify_payload "$COMMUNITY_FILE" 0 "$COMMUNITY_SHA" 'Community UI patch source'
        verify_payload "$BACKEND_FILE" 0 "$CUA_BACKEND_SHA" 'Computer-use backend source'
        verify_payload "$NPM_FILE" 0 "$NPM_TAR_SHA" 'ASAR tooling'
        verify_payload "$XDOTOOL_FILE" 0 "$XDOTOOL_SHA" 'X11 input source'
        verify_payload "$WMCTRL_FILE" 0 "$WMCTRL_SHA" 'X11 window source'
        (( SUPPORT_BUNDLE == 0 )) || verify_payload "$SUPPORT_FILE" 0 "$SUPPORT_BUNDLE_SHA" 'Prebuilt computer-use support'
    fi
}

extract_deb() {
    local member archive
    member="$(ar t "$DEB_FILE" | awk '/^data\.tar(\.(gz|xz|zst|bz2))?$/ && !seen {print; seen=1}')"
    [[ -n "$member" ]] || die "The official .deb has no data.tar archive."
    archive="$WORKDIR/$member"
    ar p "$DEB_FILE" "$member" > "$archive"
    mkdir -p "$WORKDIR/extracted"
    if [[ "$member" == *.zst ]]; then
        if [[ "$(tar --help 2>/dev/null)" == *--zstd* ]]; then
            tar --zstd -xf "$archive" -C "$WORKDIR/extracted"
        elif command -v zstd >/dev/null 2>&1; then
            zstd -dc "$archive" | tar -xf - -C "$WORKDIR/extracted"
        else
            die "Install Slackware's zstd package to unpack this release."
        fi
    else
        tar -xf "$archive" -C "$WORKDIR/extracted"
    fi
    rm -f -- "$archive"
}
missing_desktop_libraries() {
    local binary="$1" bundle="$2" lib cache='' missing=0
    command -v readelf >/dev/null 2>&1 || return 0
    command -v ldconfig >/dev/null 2>&1 || return 0
    cache="$(ldconfig -p 2>/dev/null || true)"
    [[ -n "$cache" ]] || return 0
    while IFS= read -r lib; do
        [[ -n "$lib" ]] || continue
        [[ ! -e "$bundle/$lib" ]] || continue
        if [[ "$cache" != *"$lib ("* ]]; then
            printf '  missing: %s\n' "$lib" >&2
            missing=1
        fi
    done < <(readelf -d "$binary" 2>/dev/null | awk -F '[][]' '/NEEDED/ {print $2}')
    return "$missing"
}
check_desktop_runtime() {
    local binary="$WORKDIR/extracted/usr/lib/chatgpt/ChatGPT"
    [[ -x "$binary" ]] || die "Desktop package has no ChatGPT executable."
    [[ -e "$WORKDIR/extracted/usr/bin/chatgpt" || -L "$WORKDIR/extracted/usr/bin/chatgpt" ]] ||
        die "Desktop package has no chatgpt launcher."
    [[ -f "$WORKDIR/extracted/usr/share/applications/chatgpt.desktop" ]] ||
        die "Desktop package has no menu entry."
    missing_desktop_libraries "$binary" "$WORKDIR/extracted/usr/lib/chatgpt" ||
        die "This Slackware is missing desktop libraries. Install them, or run --cli-only."
}
prepare_community_sources() {
    local node="$WORKDIR/extracted/usr/lib/chatgpt/resources/cua_node/bin/node"
    for cmd in realpath import xprop; do need "$cmd"; done
    [[ -x "$node" ]] || die "The official desktop payload has no bundled Node runtime."
    mkdir -p "$WORKDIR/community" "$WORKDIR/npm" "$WORKDIR/tools"
    tar -xzf "$COMMUNITY_FILE" -C "$WORKDIR/community" --strip-components=1 --no-same-owner
    tar -xzf "$NPM_FILE" -C "$WORKDIR/npm" --strip-components=1 --no-same-owner
    ln -s "$node" "$WORKDIR/tools/node"
    cat > "$WORKDIR/tools/dpkg-deb" <<'SH'
#!/usr/bin/env bash
set -Eeuo pipefail
[[ $# -ge 2 ]] || exit 2
action="$1"; deb="$2"; member=''; listing="$(ar t "$deb")"
case "$action" in
    -f|--field) prefix='control.tar' ;;
    -x|--extract) prefix='data.tar' ;;
    *) echo 'Unsupported dpkg-deb operation' >&2; exit 2 ;;
esac
while IFS= read -r entry; do
    case "$entry" in "$prefix"|"$prefix".*) member="$entry"; break ;; esac
done <<< "$listing"
[[ -n "$member" ]] || { echo "Missing $prefix in $deb" >&2; exit 1; }
archive="$(mktemp "${TMPDIR:-/tmp}/skynet-deb-member.XXXXXXXX")"
trap 'rm -f -- "$archive"' EXIT
ar p "$deb" "$member" > "$archive"
if [[ "$action" == -x || "$action" == --extract ]]; then
    [[ $# -eq 3 ]] || exit 2
    mkdir -p "$3"
    tar -xf "$archive" -C "$3" --no-same-owner
else
    control="$(tar -xOf "$archive" ./control 2>/dev/null || tar -xOf "$archive" control)"
    if [[ $# -eq 2 ]]; then
        printf '%s\n' "$control"
    else
        printf '%s\n' "$control" | awk -v wanted="$3" '
          BEGIN { found=0 }
          /^[^[:space:]][^:]*:/ {
            name=$0; sub(/:.*/, "", name)
            if (tolower(name)==tolower(wanted)) {
              value=$0; sub(/^[^:]*:[[:space:]]*/, "", value); print value; found=1; next
            }
            if (found) exit
          }
          found && /^[[:space:]]/ { sub(/^[[:space:]]/, ""); print; next }
          found { exit }
        '
    fi
fi
SH
    chmod 0755 "$WORKDIR/tools/dpkg-deb"
}
prepare_asar() {
    local node="$WORKDIR/tools/node" npm="$WORKDIR/npm/bin/npm-cli.js"
    say "Preparing pinned ASAR tooling with the bundled Node runtime"
    [[ -f "$npm" ]] || die "Pinned npm archive is incomplete."
    mkdir -p "$WORKDIR/asar"
    cat > "$WORKDIR/asar/package.json" <<'JSON'
{"name":"skynet-asar-build","version":"1.0.0","private":true,"dependencies":{"@electron/asar":"4.3.0"}}
JSON
    (cd "$WORKDIR/asar" && "$node" "$npm" install --ignore-scripts --no-audit \
        --no-fund --save-exact --cache "$WORKDIR/npm-cache" > "$WORKDIR/npm-install.log" 2>&1) ||
        { cat "$WORKDIR/npm-install.log" >&2; die "Could not prepare ASAR tooling."; }
    [[ -f "$WORKDIR/asar/node_modules/@electron/asar/bin/asar.mjs" ]] ||
        die "ASAR tooling is incomplete."
    cat > "$WORKDIR/tools/asar" <<EOF
#!/usr/bin/env bash
exec '$node' '$WORKDIR/asar/node_modules/@electron/asar/bin/asar.mjs' "\$@"
EOF
    chmod 0755 "$WORKDIR/tools/asar"
}
build_cua_backend() {
    local rustup_url="https://static.rust-lang.org/rustup/dist/$CODEX_ARCH-unknown-linux-gnu/rustup-init"
    local expected actual
    for cmd in gcc make pkg-config; do need "$cmd"; done
    mkdir -p "$WORKDIR/backend"
    tar -xzf "$BACKEND_FILE" -C "$WORKDIR/backend" --strip-components=1 --no-same-owner
    say "Compiling Linux computer-use backend for this Slackware"
    fetch "$rustup_url" "$WORKDIR/rustup-init"
    fetch "$rustup_url.sha256" "$WORKDIR/rustup-init.sha256"
    expected="$(awk 'NR==1 {print $1}' "$WORKDIR/rustup-init.sha256")"
    [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || die "Invalid rustup checksum metadata."
    actual="$(sha256sum "$WORKDIR/rustup-init")"; actual="${actual%% *}"
    [[ "${actual,,}" == "${expected,,}" ]] || die "Rust bootstrap checksum mismatch."
    chmod 0755 "$WORKDIR/rustup-init"
    export CARGO_HOME="$WORKDIR/cargo-home" RUSTUP_HOME="$WORKDIR/rustup-home"
    "$WORKDIR/rustup-init" -y --no-modify-path --profile minimal \
        --default-toolchain 1.98.1 > "$WORKDIR/rustup.log" 2>&1 ||
        { cat "$WORKDIR/rustup.log" >&2; die "Could not install temporary Rust toolchain."; }
    (cd "$WORKDIR/backend" && CARGO_TARGET_DIR="$WORKDIR/cargo-target" \
        "$CARGO_HOME/bin/cargo" build --release --locked > "$WORKDIR/cargo-build.log" 2>&1) ||
        { tail -n 60 "$WORKDIR/cargo-build.log" >&2; die "Computer-use backend build failed."; }
    CUA_BINARY="$WORKDIR/cargo-target/release/computer-use-linux"
    CUA_COSMIC_BINARY="$WORKDIR/cargo-target/release/computer-use-linux-cosmic"
    [[ -x "$CUA_BINARY" && -x "$CUA_COSMIC_BINARY" ]] ||
        die "Computer-use backend build produced no binaries."
    ok "Computer-use backend compiled for local glibc"
}
build_x11_tools() {
    local jobs="$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 2)"
    for cmd in gcc make pkg-config; do need "$cmd"; done
    mkdir -p "$WORKDIR/xdotool" "$WORKDIR/wmctrl"
    tar -xzf "$XDOTOOL_FILE" -C "$WORKDIR/xdotool" --strip-components=1 --no-same-owner
    tar -xzf "$WMCTRL_FILE" -C "$WORKDIR/wmctrl" --strip-components=1 --no-same-owner
    [[ "$jobs" =~ ^[0-9]+$ ]] || jobs=2
    (( jobs <= 8 )) || jobs=8
    say "Building X11 input and window tools"
    (cd "$WORKDIR/xdotool" && make -j "$jobs" xdotool.static > "$WORKDIR/xdotool-build.log" 2>&1 &&
     make PREFIX="$WORKDIR/x11-tools/usr" install-static >> "$WORKDIR/xdotool-build.log" 2>&1) ||
        { tail -n 50 "$WORKDIR/xdotool-build.log" >&2; die "xdotool build failed."; }
    (cd "$WORKDIR/wmctrl" && ./configure > "$WORKDIR/wmctrl-build.log" 2>&1 &&
     make -j "$jobs" >> "$WORKDIR/wmctrl-build.log" 2>&1 &&
     install -Dm0755 wmctrl "$WORKDIR/x11-tools/usr/bin/wmctrl") ||
        { tail -n 50 "$WORKDIR/wmctrl-build.log" >&2; die "wmctrl build failed."; }
    [[ -x "$WORKDIR/x11-tools/usr/bin/xdotool" && -x "$WORKDIR/x11-tools/usr/bin/wmctrl" ]] ||
        die "X11 tools build is incomplete."
}
unpack_support_bundle() {
    local dest="$WORKDIR/support-bundle" entry
    mkdir -p "$dest" "$WORKDIR/x11-tools/usr/bin"
    while IFS= read -r entry; do
        case "$entry" in
            ''|/*|../*|*/../*|*/..|./../*) die "Unsafe path in support bundle." ;;
        esac
    done < <(tar -tzf "$WORKDIR/$SUPPORT_BUNDLE_NAME")
    tar -xzf "$WORKDIR/$SUPPORT_BUNDLE_NAME" -C "$dest" --no-same-owner
    CUA_BINARY="$dest/bin/computer-use-linux"
    CUA_COSMIC_BINARY="$dest/bin/computer-use-linux-cosmic"
    for entry in "$CUA_BINARY" "$CUA_COSMIC_BINARY" "$dest/bin/xdotool" "$dest/bin/wmctrl"; do
        [[ -f "$entry" && ! -L "$entry" && -x "$entry" ]] || die "Support bundle is incomplete."
    done
    install -m0755 "$dest/bin/xdotool" "$WORKDIR/x11-tools/usr/bin/xdotool"
    install -m0755 "$dest/bin/wmctrl" "$WORKDIR/x11-tools/usr/bin/wmctrl"
    ok "Prebuilt Linux computer-use and X11 tools are ready"
}
patch_community_desktop() {
    local candidate="$WORKDIR/patched-app" report="$WORKDIR/patch-report.json"
    say "Applying pinned Linux Any App interface and launcher patch"
    printf '{"enabled":["computer-use-linux"]}\n' > "$WORKDIR/community-features.json"
    mkdir -m0700 "$WORKDIR/community-tmp"
    (cd "$WORKDIR/community" &&
      PATH="$WORKDIR/tools:$PATH" CODEX_APP_ID=chatgpt \
      CODEX_APP_DISPLAY_NAME='ChatGPT Community' \
      CODEX_INSTALL_TRANSACTION_ACTIVE=1 CODEX_INSTALL_DIR="$candidate" \
      CODEX_LINUX_FEATURES_CONFIG="$WORKDIR/community-features.json" \
      CODEX_ASAR_BIN="$WORKDIR/tools/asar" \
      CODEX_COMPUTER_USE_BINARY_SOURCE="$CUA_BINARY" \
      CODEX_COMPUTER_USE_COSMIC_BINARY_SOURCE="$CUA_COSMIC_BINARY" \
      CODEX_PATCH_REPORT_JSON="$report" \
      CODEX_UPSTREAM_LINUX_METADATA_JSON="$WORKDIR/upstream-linux-package.json" \
      TMPDIR="$WORKDIR/community-tmp" UPSTREAM_DEB="$DEB_FILE" bash ./install.sh > "$WORKDIR/community-build.log" 2>&1) ||
        { tail -n 80 "$WORKDIR/community-build.log" >&2; die "Community desktop patch failed."; }
    "$PYTHON" - "$report" <<'PY'
from __future__ import print_function
import json, sys
j = json.load(open(sys.argv[1]))
patches = j.get('patches', [])
features = [p for p in patches if p.get('featureId') == 'computer-use-linux']
if 'computer-use-linux' not in j.get('enabledFeatures', []) or len(features) != 8:
    raise SystemExit('Incomplete community computer-use patch report')
if any(p.get('status') != 'applied' for p in features):
    raise SystemExit('A community computer-use patch did not apply')
print('Verified %d computer-use interface patches' % len(features))
PY
    local plugin="$candidate/resources/plugins/openai-bundled/plugins/unified-computer-use"
    [[ -x "$candidate/start.sh" && -x "$plugin/bin/codex-computer-use-linux" &&
       -x "$plugin/bin/codex-computer-use-cosmic" &&
       -x "$candidate/.codex-linux/launcher.d/computer-use-linux-host-service.sh" ]] ||
        die "Community desktop payload is incomplete."
    ok "Linux Any App patch and backend are staged"
}
stage_community_x11() {
    local root="$1" bin
    bin="$root/usr/lib/chatgpt/.codex-linux/bin"
    mkdir -p "$bin"
    install -m0755 "$WORKDIR/x11-tools/usr/bin/xdotool" "$bin/xdotool"
    install -m0755 "$WORKDIR/x11-tools/usr/bin/wmctrl" "$bin/wmctrl"
    cat > "$bin/gnome-screenshot" <<'SH'
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ "${1:-}" == --version ]]; then printf 'gnome-screenshot (Skynet X11 bridge) 1.0\n'; exit 0; fi
[[ $# -eq 2 && "$1" == -f ]] || { echo 'Expected: gnome-screenshot -f FILE' >&2; exit 2; }
exec import -window root "png:$2"
SH
    chmod 0755 "$bin/gnome-screenshot"
    cat > "$root/usr/bin/chatgpt" <<'SH'
#!/usr/bin/env bash
set -Eeuo pipefail
export PATH="/usr/lib/chatgpt/.codex-linux/bin:$PATH"
export CODEX_LINUX_DISABLE_USAGE_REPORTING=1
exec /usr/lib/chatgpt/start.sh "$@"
SH
    chmod 0755 "$root/usr/bin/chatgpt"
}
stage_desktop() {
    local src="$WORKDIR/extracted" root="$WORKDIR/desktop-root" f rel feature_line
    say "Unpacking ChatGPT Desktop and checking its runtime"
    extract_deb
    check_desktop_runtime
    mkdir -p "$root/usr/lib" "$root/usr/bin" "$root/usr/share/applications" "$root/install"
    feature_line='Browser actions are available; desktop app control is not yet.'
    if (( COMMUNITY )); then
        prepare_community_sources
        prepare_asar
        if (( SUPPORT_BUNDLE )); then
            unpack_support_bundle
        else
            build_x11_tools
            build_cua_backend
        fi
        patch_community_desktop
        mv "$WORKDIR/patched-app" "$root/usr/lib/chatgpt"
        stage_community_x11 "$root"
        feature_line='Community Linux Any App computer-use patch is enabled.'
    else
        mv "$src/usr/lib/chatgpt" "$root/usr/lib/chatgpt"
        mv "$src/usr/bin/chatgpt" "$root/usr/bin/chatgpt"
    fi
    mv "$src/usr/share/applications/chatgpt.desktop" "$root/usr/share/applications/chatgpt.desktop"
    if [[ -d "$src/usr/share/icons" ]]; then
        while IFS= read -r -d '' f; do
            rel="${f#"$src/"}"
            mkdir -p "$root/$(dirname "$rel")"
            mv "$f" "$root/$rel"
        done < <(find "$src/usr/share/icons" -type f -iname '*chatgpt*' -print0)
    fi
    for rel in usr/share/doc/chatgpt usr/share/licenses/chatgpt; do
        if [[ -d "$src/$rel" ]]; then
            mkdir -p "$root/$(dirname "$rel")"
            mv "$src/$rel" "$root/$rel"
        fi
    done
    for rel in usr/share/pixmaps/chatgpt.png \
               usr/share/metainfo/com.openai.chatgpt.metainfo.xml \
               usr/share/swcatalog/xml/com.openai.chatgpt.xml; do
        if [[ -f "$src/$rel" ]]; then
            mkdir -p "$root/$(dirname "$rel")"
            mv "$src/$rel" "$root/$rel"
        fi
    done
    if [[ -f /etc/apparmor.d/abi/4.0 && -f "$src/etc/apparmor.d/chatgpt" ]]; then
        mkdir -p "$root/etc/apparmor.d"
        mv "$src/etc/apparmor.d/chatgpt" "$root/etc/apparmor.d/chatgpt"
    fi
    mkdir -p "$root/$(dirname "$DESKTOP_STATE")"
    printf '%s\n' "$DESKTOP_INSTALL_ID" > "$root$DESKTOP_STATE"
    cat > "$root/install/slack-desc" <<EOF
chatgpt: chatgpt (OpenAI ChatGPT Desktop)
chatgpt:
chatgpt: Official Linux preview payload repackaged for Slackware.
chatgpt: Slackware is not an upstream-supported distribution.
chatgpt: $feature_line
chatgpt:
chatgpt: Source: $DEB_BASE
chatgpt: Version: $DESKTOP_VERSION
chatgpt:
chatgpt:
chatgpt:
EOF
    cat > "$root/install/doinst.sh" <<'EOF'
if command -v update-desktop-database >/dev/null 2>&1; then
  update-desktop-database /usr/share/applications >/dev/null 2>&1 || true
fi
if command -v gtk-update-icon-cache >/dev/null 2>&1 && [ -d /usr/share/icons/hicolor ]; then
  gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor >/dev/null 2>&1 || true
fi
if [ -f /etc/apparmor.d/abi/4.0 ] && command -v aa-enabled >/dev/null 2>&1 &&
   command -v apparmor_parser >/dev/null 2>&1 && aa-enabled --quiet; then
  apparmor_parser -r -W -T /etc/apparmor.d/chatgpt >/dev/null 2>&1 || true
fi
EOF
    chmod 0755 "$root/install/doinst.sh"
    DESKTOP_PACKAGE="$WORKDIR/chatgpt-${DESKTOP_VERSION//:/_}-$SLACK_ARCH-$BUILD.tgz"
    say "Compressing desktop package with Slackware makepkg"
    (cd "$root" && makepkg -l y -c n "$DESKTOP_PACKAGE" > "$WORKDIR/makepkg-desktop.log" 2>&1) ||
        { cat "$WORKDIR/makepkg-desktop.log" >&2; die "Could not build desktop Slackware package."; }
    [[ -s "$DESKTOP_PACKAGE" ]] || die "Desktop Slackware package is empty."
}

stage_cli() {
    local root="$WORKDIR/cli-root" app="$WORKDIR/cli-root/opt/openai/codex"
    say "Unpacking complete Codex CLI package"
    mkdir -p "$app" "$root/usr/local/bin" "$root/install" "$root/$(dirname "$CLI_STATE")"
    # The verified upstream archive has direct bin/, codex-path/ and
    # codex-resources/ members. Keep their relative layout intact.
    tar -xzf "$CLI_FILE" -C "$app" --no-same-owner
    [[ -x "$app/bin/codex" && -x "$app/codex-path/rg" && -x "$app/codex-resources/bwrap" ]] ||
        die "Codex package is incomplete; refusing to install."
    if [[ ( -e /usr/local/bin/codex || -L /usr/local/bin/codex ) && ! -r "$CLI_STATE" ]]; then
        die "/usr/local/bin/codex already exists outside this installer. Move it yourself or use --desktop-only."
    fi
    ln -s /opt/openai/codex/bin/codex "$root/usr/local/bin/codex"
    printf '%s\n' "$CLI_VERSION" > "$root$CLI_STATE"
    cat > "$root/install/slack-desc" <<EOF
codex-cli: codex-cli (OpenAI Codex CLI)
codex-cli:
codex-cli: Official standalone Linux musl package for Codex.
codex-cli: Includes bundled rg, bwrap, code-mode and voice components.
codex-cli: No Node.js or npm required.
codex-cli:
codex-cli: Source: https://github.com/openai/codex/releases
codex-cli: Version: $CLI_VERSION
codex-cli:
codex-cli:
codex-cli:
EOF
    CLI_PACKAGE="$WORKDIR/codex-cli-$CLI_VERSION-$SLACK_ARCH-$BUILD.tgz"
    say "Compressing Codex package with Slackware makepkg"
    (cd "$root" && makepkg -l y -c n "$CLI_PACKAGE" > "$WORKDIR/makepkg-cli.log" 2>&1) ||
        { cat "$WORKDIR/makepkg-cli.log" >&2; die "Could not build Codex Slackware package."; }
    [[ -s "$CLI_PACKAGE" ]] || die "Codex Slackware package is empty."
}

diagnose() {
    local bad=0 version
    printf '\n'
    say "Local compatibility report"
    if [[ -r /etc/slackware-version ]]; then
        printf 'Slackware: %s\n' "$(< /etc/slackware-version)"
    else
        warn "This does not appear to be Slackware."
    fi
    printf 'Architecture: %s\n' "$(uname -m)"
    if [[ -n "${DISPLAY:-}" ]]; then
        printf 'Graphical session: X11 (%s)\n' "$DISPLAY"
    elif [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
        printf 'Graphical session: Wayland (%s)\n' "$WAYLAND_DISPLAY"
    else
        warn "No graphical display in this shell; launch ChatGPT from your desktop session."
    fi
    if command -v getconf >/dev/null 2>&1; then
        printf 'C library: %s\n' "$(getconf GNU_LIBC_VERSION 2>/dev/null || echo unknown)"
    fi
    if [[ -x "$APP_BIN" ]]; then
        version='unknown'; [[ ! -r "$DESKTOP_STATE" ]] || version="$(cat "$DESKTOP_STATE")"
        printf 'ChatGPT Desktop: installed (%s)\n' "$version"
        if missing_desktop_libraries "$APP_BIN" /usr/lib/chatgpt; then
            printf 'Desktop direct libraries: OK\n'
        else
            bad=1; warn "Desktop libraries missing"
        fi
        if [[ "$version" == *+cu.* ]]; then
            local plugin='/usr/lib/chatgpt/resources/plugins/openai-bundled/plugins/unified-computer-use/bin'
            local x11='/usr/lib/chatgpt/.codex-linux/bin'
            if [[ -x /usr/lib/chatgpt/start.sh && -x "$plugin/codex-computer-use-linux" &&
                  -x "$plugin/codex-computer-use-cosmic" && -x "$x11/xdotool" &&
                  -x "$x11/wmctrl" && -x "$x11/gnome-screenshot" ]]; then
                printf 'Linux Any App patch: installed (X11 tools bundled)\n'
            else
                bad=1; warn "Linux Any App patch is incomplete; rerun setup."
            fi
        fi
    else
        printf 'ChatGPT Desktop: not installed\n'
    fi
    if [[ -x "$CLI_BIN" ]]; then
        version='unknown'; [[ ! -r "$CLI_STATE" ]] || version="$(cat "$CLI_STATE")"
        printf 'Codex CLI: installed (%s)\n' "$version"
        "$CLI_BIN" --version || { bad=1; warn "Codex CLI did not start."; }
    else
        printf 'Codex CLI: not installed\n'
    fi
    command -v git >/dev/null 2>&1 || warn "git is missing; Codex project work will be limited."
    command -v xdg-open >/dev/null 2>&1 || warn "xdg-open is missing; browser sign-in may fail."
    if [[ -r /proc/sys/kernel/unprivileged_userns_clone ]] &&
       [[ "$(< /proc/sys/kernel/unprivileged_userns_clone)" == 0 ]]; then
        warn "Unprivileged user namespaces are disabled; Linux sandbox/browser features may fail."
    fi
    return "$bad"
}

find_records() {
    local name="$1" f dir
    for dir in /var/lib/pkgtools/packages /var/log/packages; do
        [[ -d "$dir" ]] || continue
        for f in "$dir"/"$name"-*-"$SLACK_ARCH"-"$BUILD"; do
            [[ ! -e "$f" ]] || basename "$f"
        done
    done
}
verify_installed_components() {
    if [[ "$MODE" != cli ]]; then
        [[ -x "$APP_BIN" && -x /usr/bin/chatgpt &&
           -r "$DESKTOP_STATE" && "$(<"$DESKTOP_STATE")" == "$DESKTOP_INSTALL_ID" &&
           -n "$(find_records chatgpt)" ]] ||
            die "ChatGPT Desktop did not finish installing; rerun setup after checking pkgtools output."
    fi
    if [[ "$MODE" != desktop ]]; then
        [[ -x "$CLI_BIN" && -x /usr/local/bin/codex &&
           -r "$CLI_STATE" && "$(<"$CLI_STATE")" == "$CLI_VERSION" &&
           -n "$(find_records codex-cli)" ]] ||
            die "Codex CLI did not finish installing; rerun setup after checking pkgtools output."
    fi
}
uninstall() {
    root_or_sudo "$@"
    check_slackware
    lock_pkgtools
    detect_arch
    local pkg name removed=0
    for name in codex-cli chatgpt; do
        [[ "$MODE" != cli || "$name" == codex-cli ]] || continue
        [[ "$MODE" != desktop || "$name" == chatgpt ]] || continue
        while IFS= read -r pkg; do
            [[ -n "$pkg" ]] || continue
            say "Removing $pkg"
            removepkg "$pkg"
            removed=1
        done < <(find_records "$name" | sort -u)
    done
    (( removed )) || say "No selected packages from this installer were found"
}
install_all() {
    root_or_sudo "$@"
    banner
    phase 1 SYSTEM "Scan Slackware, architecture and runtime"
    check_slackware
    lock_pkgtools
    detect_arch
    check_glibc
    for cmd in curl sha256sum tar awk grep find stat df mktemp readlink dirname; do need "$cmd"; done
    if [[ "$MODE" != cli ]]; then
        for cmd in ar gpg gpgv; do need "$cmd"; done
    fi
    choose_python
    secure_tmpdir
    WORKDIR="$(mktemp -d /var/tmp/skynet-setup.XXXXXXXX)"
    ok "Local node: Slackware $(< /etc/slackware-version) / $SLACK_ARCH"
    phase 2 UPLINK "Read signed OpenAI release channels"
    fetch_metadata
    phase 3 LOADOUT "Resolve installed and available components"
    already_installed
    select_prebuilts
    guard_existing_files
    (( WANT_DESKTOP == 0 )) || need xdg-open
    if (( WANT_DESKTOP == 0 && WANT_CLI == 0 )); then
        ok "All requested components are up to date"
        phase 4 TRANSFER "[SKIP] No new payloads"
        phase 5 INTEGRITY "[SKIP] Installed files already checked"
        phase 6 FORGE "[SKIP] No package to build"
        phase 7 ACTIVATE "[SKIP] No package changes"
    else
        space_check
        phase 4 TRANSFER "Fetch payloads in parallel"
        fetch_payloads
        phase 6 FORGE "Build native Slackware packages"
        (( WANT_DESKTOP == 0 || PREBUILT_DESKTOP )) || stage_desktop
        (( WANT_CLI == 0 || PREBUILT_CLI )) || stage_cli
        (( PREBUILT_DESKTOP == 0 )) || ok "Prebuilt Desktop package is ready"
        (( PREBUILT_CLI == 0 )) || ok "Prebuilt Codex CLI package is ready"
        phase 7 ACTIVATE "Install or upgrade with pkgtools"
        MUTATION_STARTED=1
        (( WANT_CLI == 0 )) || { say "Installing Codex CLI"; upgradepkg --install-new --reinstall "$CLI_PACKAGE"; }
        (( WANT_DESKTOP == 0 )) || { say "Installing ChatGPT Desktop"; upgradepkg --install-new --reinstall "$DESKTOP_PACKAGE"; }
    fi
    verify_installed_components
    diagnose || die "Installed components failed the final health check; review the report above."
    portal_open
    if [[ "$MODE" != cli ]]; then
        printf '  ChatGPT Desktop %s: launch from menu or run chatgpt\n' "$DESKTOP_VERSION"
    fi
    if [[ "$MODE" != desktop ]]; then
        printf '  Codex CLI %s: run codex\n' "$CLI_VERSION"
    fi
    printf '  Run both as your normal desktop user and sign in.\n'
    if (( STREAMED_INPUT )); then
        printf '  Update: curl -fsSL %s | sudo bash\n' "$SELF_URL"
        printf '  Diagnose: curl -fsSL %s | bash -s -- --diagnose\n' "$SELF_URL"
    else
        printf '  Update: rerun this script.  Diagnose: bash %q --diagnose\n' "$0"
    fi
    if [[ "$MODE" != cli && $COMMUNITY -eq 1 ]]; then
        printf '  Any App: in ChatGPT Settings, enable Computer use; use an X11 session.\n'
        printf '  Community patch: %s\n' "${COMMUNITY_COMMIT:0:12}"
        warn "Any App is an unofficial community patch; account feature access still depends on OpenAI."
    elif [[ "$MODE" != cli ]]; then
        warn "Official Linux preview supports browser actions; other app control is not yet available."
    fi
}

if (( STREAMED_INPUT )) || [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    case "$ACTION" in
        help) usage ;;
        diagnose) diagnose ;;
        uninstall) uninstall "$@" ;;
        install) install_all "$@" ;;
    esac
fi
