[ waitdojo ]

Read every line before it runs.

This page renders the exact shell script served at /install.sh. Nothing runs here. Review it, inspect the release checksums, then decide whether to install.

What this review proves.

displayed sourceThe exact script bundled into this site build and served at /install.shraw file →
published releaseWaitDojo v0.2.4 archives for macOS, Linux, and WSL2release details →
archive integritySHA-256 detects a corrupt or mismatched downloaded archivechecksums →
trust boundaryEd25519-signed archives and release provenance; v0.2.4 bootstraps verification for later self-updatessecurity details →

The public-proof manifest documents the deterministic UI capture generated from published v0.2.4 and records its archive, binary, and tag identity. It is UI evidence; publisher authenticity comes from the detached signatures and signed release provenance.

Published installer source.

The source below is inert text. Use the explicitly labeled raw-file action only if you want to download it.

#!/bin/sh
set -eu

# Installer output and checksum/archive tools must behave the same on GNU and
# BSD userlands, even when the calling terminal exports a locale unavailable on
# the target host (for example C.UTF-8 on macOS).
LC_ALL=C
LANG=C
export LC_ALL LANG

PRODUCT="waitdojo"
BASE_URL="${WAITDOJO_BASE_URL:-https://waitdojo.nishxnt-97.chatgpt.site/releases}"
VERSION="${WAITDOJO_VERSION:-latest}"
DEFAULT_INSTALL_DIR="$HOME/.local/bin"
INSTALL_DIR="${WAITDOJO_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}"
DRY_RUN="${WAITDOJO_DRY_RUN:-0}"
NO_MODIFY_PATH="${WAITDOJO_NO_MODIFY_PATH:-0}"
AGENT="${WAITDOJO_AGENT:-}"
PATH_PROFILE_CONFIGURED=0

usage() {
  cat <<'EOF'
Usage: install.sh [--dry-run] [--no-modify-path] [--agent codex|claude|none]

Environment:
  WAITDOJO_INSTALL_DIR  install directory, defaults to ~/.local/bin
  WAITDOJO_VERSION      release version, defaults to latest
  WAITDOJO_BASE_URL     release base URL, defaults to the live WaitDojo site
  WAITDOJO_DRY_RUN      set to 1 to print actions without changing files
  WAITDOJO_NO_MODIFY_PATH
                         set to 1 to leave shell profiles unchanged
  WAITDOJO_AGENT         optionally configure codex or claude hooks after install
EOF
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --dry-run)
      DRY_RUN=1
      ;;
    --no-modify-path)
      NO_MODIFY_PATH=1
      ;;
    --agent)
      shift
      if [ "$#" -eq 0 ]; then
        echo "--agent requires codex, claude, or none" >&2
        exit 2
      fi
      AGENT="$1"
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "unknown argument: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
  shift
done

case "$AGENT" in
  ""|none) AGENT="" ;;
  codex|claude) ;;
  *)
    echo "WAITDOJO_AGENT/--agent must be codex, claude, or none" >&2
    exit 2
    ;;
esac

case "$NO_MODIFY_PATH" in
  0|1) ;;
  *)
    echo "WAITDOJO_NO_MODIFY_PATH must be 0 or 1" >&2
    exit 2
    ;;
esac

case "$INSTALL_DIR" in
  /*) ;;
  *) INSTALL_DIR="$PWD/$INSTALL_DIR" ;;
esac

detect_os() {
  os="${WAITDOJO_TEST_UNAME_S:-$(uname -s 2>/dev/null || true)}"
  case "$os" in
    Darwin) echo "macos" ;;
    Linux) echo "linux" ;;
    MINGW*|MSYS*|CYGWIN*)
      echo "native Windows is not supported; install and run WaitDojo inside WSL2" >&2
      exit 1
      ;;
    *)
      echo "unsupported OS: $os" >&2
      exit 1
      ;;
  esac
}

detect_arch() {
  arch="${WAITDOJO_TEST_UNAME_M:-$(uname -m 2>/dev/null || true)}"
  case "$arch" in
    x86_64|amd64) echo "x64" ;;
    arm64|aarch64) echo "arm64" ;;
    *)
      echo "unsupported architecture: $arch" >&2
      exit 1
      ;;
  esac
}

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "missing required command: $1" >&2
    exit 1
  fi
}

is_wsl2() {
  if [ "${WAITDOJO_TEST_WSL:-}" = "1" ]; then
    return 0
  fi
  if [ -n "${WSL_INTEROP:-}" ] || [ -n "${WSL_DISTRO_NAME:-}" ]; then
    return 0
  fi
  if [ -r /proc/sys/kernel/osrelease ] && grep -qi "microsoft\\|wsl" /proc/sys/kernel/osrelease; then
    return 0
  fi
  return 1
}

append_posix_path_block() {
  profile="$1"
  marker="# >>> waitdojo PATH >>>"
  if grep -F "$marker" "$profile" >/dev/null 2>&1; then
    echo "PATH already configured in $profile"
    PATH_PROFILE_CONFIGURED=1
    return 0
  fi

  if {
    if [ -s "$profile" ]; then
      printf '\n'
    fi
    printf '%s\n' \
      "$marker" \
      '# Added by the WaitDojo installer. Remove this block to opt out.' \
      'case "$PATH" in' \
      '  "$HOME/.local/bin"|"$HOME/.local/bin":*) ;;' \
      '  *) export PATH="$HOME/.local/bin:$PATH" ;;' \
      'esac' \
      '# <<< waitdojo PATH <<<'
  } >>"$profile"
  then
    echo "configured PATH in $profile"
    PATH_PROFILE_CONFIGURED=1
  else
    echo "warning: could not update PATH in $profile" >&2
  fi
}

append_fish_path_block() {
  profile="$1"
  marker="# >>> waitdojo PATH >>>"
  profile_dir="${profile%/*}"
  if ! mkdir -p "$profile_dir"; then
    echo "warning: could not create $profile_dir for PATH setup" >&2
    return 0
  fi
  if grep -F "$marker" "$profile" >/dev/null 2>&1; then
    echo "PATH already configured in $profile"
    PATH_PROFILE_CONFIGURED=1
    return 0
  fi

  if {
    if [ -s "$profile" ]; then
      printf '\n'
    fi
    printf '%s\n' \
      "$marker" \
      '# Added by the WaitDojo installer. Remove this block to opt out.' \
      'if not contains -- "$HOME/.local/bin" "$PATH[1]"' \
      '    set -gx PATH "$HOME/.local/bin" $PATH' \
      'end' \
      '# <<< waitdojo PATH <<<'
  } >>"$profile"
  then
    echo "configured PATH in $profile"
    PATH_PROFILE_CONFIGURED=1
  else
    echo "warning: could not update PATH in $profile" >&2
  fi
}

configure_shell_path() {
  if [ "$NO_MODIFY_PATH" = "1" ]; then
    echo "PATH setup skipped (--no-modify-path)"
    return 0
  fi
  if [ "$INSTALL_DIR" != "$DEFAULT_INSTALL_DIR" ]; then
    echo "PATH setup skipped for custom install directory $INSTALL_DIR"
    return 0
  fi

  shell_path="${SHELL:-}"
  shell_name="${shell_path##*/}"
  case "$shell_name" in
    bash)
      append_posix_path_block "$HOME/.bashrc"
      if [ -f "$HOME/.bash_profile" ]; then
        append_posix_path_block "$HOME/.bash_profile"
      elif [ -f "$HOME/.bash_login" ]; then
        append_posix_path_block "$HOME/.bash_login"
      else
        append_posix_path_block "$HOME/.profile"
      fi
      ;;
    zsh)
      append_posix_path_block "${ZDOTDIR:-$HOME}/.zshrc"
      ;;
    fish)
      append_fish_path_block "${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/waitdojo.fish"
      ;;
    ""|sh|dash|ash|ksh)
      append_posix_path_block "$HOME/.profile"
      ;;
    *)
      echo "warning: shell $shell_name is not supported for automatic PATH setup" >&2
      ;;
  esac
}

current_waitdojo_path() {
  command -v "$PRODUCT" 2>/dev/null || true
}

warn_if_shadowed() {
  installed="$INSTALL_DIR/$PRODUCT"
  resolved="$(current_waitdojo_path)"
  if [ -z "$resolved" ] || [ "$resolved" = "$installed" ]; then
    return 0
  fi

  echo "warning: another waitdojo currently wins on PATH: $resolved" >&2
  if [ -x "$resolved" ]; then
    shadow_version="$("$resolved" --version 2>/dev/null || true)"
    if [ -n "$shadow_version" ]; then
      echo "  existing:  $shadow_version" >&2
    fi
  fi
  echo "  installed: $installed" >&2
  echo "  prepend the installed directory now or open a new terminal after PATH setup" >&2
}

print_next_steps() {
  if [ "$RUNTIME" = "wsl2" ]; then
    echo "  workspace: keep source checkouts under WSL, for example ~/waitdojo, not /mnt/c/..."
  fi
  resolved="$(current_waitdojo_path)"
  if [ "$resolved" != "$INSTALL_DIR/$PRODUCT" ]; then
    if [ "$INSTALL_DIR" = "$DEFAULT_INSTALL_DIR" ]; then
      shell_install_dir='$HOME/.local/bin'
    else
      shell_install_dir="$INSTALL_DIR"
    fi
    echo "  current shell: export PATH=\"${shell_install_dir}:\$PATH\""
    echo "                 hash -r 2>/dev/null || true"
    if [ "$PATH_PROFILE_CONFIGURED" = "1" ]; then
      echo "  future shells: PATH is configured; open a new terminal"
    elif [ "$NO_MODIFY_PATH" = "1" ]; then
      echo "  future shells: add the install directory to PATH yourself"
    elif [ "$DRY_RUN" = "1" ] && [ "$INSTALL_DIR" = "$DEFAULT_INSTALL_DIR" ]; then
      echo "  future shells: the installer will configure PATH automatically"
    else
      echo "  future shells: add export PATH=\"${shell_install_dir}:\$PATH\" to your shell profile"
    fi
  fi
  if [ -n "$AGENT" ]; then
    if [ "$DRY_RUN" = "1" ]; then
      echo "  setup:  would run $PRODUCT setup --agent $AGENT"
    else
      echo "  setup:  $PRODUCT setup --agent $AGENT ran; rerun it any time"
    fi
    echo "  review: open $AGENT and run /hooks"
    echo "  launch: $INSTALL_DIR/$PRODUCT"
  else
    echo "  start:  $PRODUCT"
    echo "          detects your configured agent and opens the project sidecar"
    echo "  setup:  $PRODUCT setup   # or: $PRODUCT setup --agent claude"
    echo "  launch: $PRODUCT codex        # or: $PRODUCT claude"
  fi
  echo "  local:  $PRODUCT pane"
  echo "  toggle: Ctrl+G or click [↑ open WaitDojo] / [↓ collapse]"
  echo "  advanced: tmux prefix, then C-g, then an action key"
  echo "  check:  $PRODUCT doctor --product"
  echo "  tools:"
  for cmd in cargo tmux codex claude; do
    if command -v "$cmd" >/dev/null 2>&1; then
      echo "    $cmd: ok"
    else
      echo "    $cmd: missing"
    fi
  done
}

OS="$(detect_os)"
ARCH="$(detect_arch)"
RUNTIME="$OS"
if [ "$OS" = "linux" ] && is_wsl2; then
  RUNTIME="wsl2"
fi
ASSET="${PRODUCT}-${OS}-${ARCH}.tar.gz"
URL="${BASE_URL}/${VERSION}/${ASSET}"

echo "WaitDojo installer"
echo "  runtime:  ${RUNTIME}"
echo "  platform: ${OS}-${ARCH}"
echo "  asset:    ${ASSET}"
echo "  install:  ${INSTALL_DIR}/${PRODUCT}"
echo "  source:   ${URL}"

if [ "$DRY_RUN" = "1" ]; then
  echo "dry run: no files changed"
  echo "next steps:"
  print_next_steps
  exit 0
fi

need_cmd curl
need_cmd tar
need_cmd install
need_cmd mktemp
need_cmd awk
need_cmd grep
if command -v sha256sum >/dev/null 2>&1; then
  CHECKSUM_COMMAND="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
  CHECKSUM_COMMAND="shasum"
else
  echo "missing required command: sha256sum or shasum" >&2
  exit 1
fi
mkdir -p "$INSTALL_DIR"

tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT INT TERM

curl -fsSL "$URL" -o "$tmp_dir/$ASSET"

if [ "$VERSION" = "latest" ]; then
  version_url="${BASE_URL}/${VERSION}/VERSION"
  curl -fsSL "$version_url" -o "$tmp_dir/VERSION"
  expected_version="$(
    awk '
      NF {
        lines += 1
        value = $0
        sub(/\r$/, "", value)
      }
      END {
        if (lines != 1 || value == "" || value ~ /[[:space:]]/) exit 1
        print value
      }
    ' "$tmp_dir/VERSION"
  )" || {
    echo "invalid published VERSION from $version_url" >&2
    exit 1
  }
else
  expected_version="$VERSION"
fi
case "$expected_version" in
  ""|*[!0-9A-Za-z.+-]*)
    echo "invalid requested or published WaitDojo version: $expected_version" >&2
    exit 1
    ;;
esac

checksum_url="${URL}.sha256"
curl -fsSL "$checksum_url" -o "$tmp_dir/$ASSET.sha256"

expected_checksum="$(
  awk -v asset="$ASSET" '
    NF {
      lines += 1
      name = $2
      sub(/^\*/, "", name)
      sub(/\r$/, "", name)
      hash = tolower($1)
      sub(/\r$/, "", hash)
      if (lines != 1 || NF != 2 || name != asset || length(hash) != 64 || hash !~ /^[0-9a-f]+$/) {
        invalid = 1
      }
      expected = hash
    }
    END {
      if (lines != 1 || invalid) exit 1
      print expected
    }
  ' "$tmp_dir/$ASSET.sha256"
)" || {
  echo "invalid checksum file from $checksum_url" >&2
  exit 1
}

if [ "$CHECKSUM_COMMAND" = "sha256sum" ]; then
  actual_checksum="$(sha256sum "$tmp_dir/$ASSET")"
else
  actual_checksum="$(shasum -a 256 "$tmp_dir/$ASSET")"
fi
actual_checksum="${actual_checksum%% *}"
if [ "$actual_checksum" != "$expected_checksum" ]; then
  echo "checksum verification failed for $ASSET" >&2
  exit 1
fi
echo "$ASSET: checksum verified"

tar -xzf "$tmp_dir/$ASSET" -C "$tmp_dir"
if [ ! -f "$tmp_dir/$PRODUCT" ] || [ ! -x "$tmp_dir/$PRODUCT" ]; then
  echo "archive did not contain $PRODUCT binary" >&2
  exit 1
fi
version_output="$("$tmp_dir/$PRODUCT" --version)"
printf '%s\n' "$version_output" >"$tmp_dir/version.txt"
if [ "$version_output" != "$PRODUCT $expected_version" ]; then
  echo "downloaded WaitDojo version mismatch: expected $PRODUCT $expected_version, got $version_output" >&2
  exit 1
fi
"$tmp_dir/$PRODUCT" --help >"$tmp_dir/help.txt"
if ! grep -Eq '(^|[[:space:]])codex([[:space:]]|$)' "$tmp_dir/help.txt" || \
   ! grep -Eq '(^|[[:space:]])claude([[:space:]]|$)' "$tmp_dir/help.txt"; then
  echo "downloaded WaitDojo build is missing Codex or Claude Code support" >&2
  exit 1
fi

validation_home="$tmp_dir/validation-home"
mkdir -p "$validation_home"
HOME="$validation_home" \
CODEX_HOME="$validation_home/.codex" \
WAITDOJO_DATA_DIR="$validation_home/.codex/waitdojo" \
  "$tmp_dir/$PRODUCT" init codex --dry-run --allow-dev-path >/dev/null
HOME="$validation_home" \
CLAUDE_CONFIG_DIR="$validation_home/.claude" \
WAITDOJO_DATA_DIR="$validation_home/.codex/waitdojo" \
  "$tmp_dir/$PRODUCT" init claude --dry-run --allow-dev-path >/dev/null
echo "$PRODUCT: Codex and Claude Code support verified"

install -m 0755 "$tmp_dir/$PRODUCT" "$INSTALL_DIR/$PRODUCT"
"$INSTALL_DIR/$PRODUCT" --version >/dev/null

# Bake the install-time campaign bucket (WAITDOJO_CAMPAIGN_BUCKET, if set)
# into the installation identity now; without this, installs that skip
# --agent lose attribution because the env var is gone by first launch.
"$INSTALL_DIR/$PRODUCT" install-stamp >/dev/null 2>&1 || true

echo "installed $PRODUCT to $INSTALL_DIR/$PRODUCT"
configure_shell_path
warn_if_shadowed
if [ -n "$AGENT" ]; then
  echo "configuring $AGENT hooks and local teachback via waitdojo setup"
  "$INSTALL_DIR/$PRODUCT" setup --agent "$AGENT" || {
    echo "waitdojo setup reported remaining gates; the checklist above says what is left" >&2
  }
fi
echo "next steps:"
print_next_steps