#!/usr/bin/env bash set -euo pipefail RSI_TERMINAL_LAUNCHER=1 RSI_TERMINAL_VERSION="0.5.2" RSI_GAME_URL="${RSI_GAME_URL:-https://www.paradigm.xyz/research/rsi/game}" RSI_TERMINAL_SOURCE_URL="${RSI_TERMINAL_SOURCE_URL:-https://rsi.justinwang.xyz}" TERMINAL_BROWSER_INSTALL_URL="${RSI_TERMINAL_BROWSER_INSTALL_URL:-https://terminal-browser.sh/install}" BIN_HOME="${XDG_BIN_HOME:-$HOME/.local/bin}" DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" RSI_BIN="$BIN_HOME/rsi" TTY_PATH="${RSI_TERMINAL_TTY:-}" BROWSER="" BROWSER_INSTALLED_NOW=false say() { printf '%s\n' "$*" } fail() { printf 'rsi: %s\n' "$*" >&2 exit 1 } usage() { cat <<'EOF' Usage: rsi [--version | --help] Launch the Paradigm RSI game in terminal-browser. EOF } unsupported_terminal() { local name="$1" printf '\n %s cannot display terminal-browser.\n\n' "$name" >&2 printf ' Use a terminal with Kitty graphics support, such as:\n' >&2 printf ' Ghostty, Kitty, WezTerm, or VS Code\047s integrated terminal.\n' >&2 printf ' https://github.com/zenbu-labs/terminal-browser\n' >&2 printf '\n Or play in a normal browser:\n' >&2 printf ' %s\n\n' "$RSI_GAME_URL" >&2 exit 1 } require_supported_terminal() { case "${TERM_PROGRAM:-}" in Apple_Terminal) unsupported_terminal "Apple Terminal" ;; iTerm.app) unsupported_terminal "iTerm2" ;; esac } running_from_pipe() { local source_file="${BASH_SOURCE[0]:-}" [[ -z "$source_file" || ! -f "$source_file" ]] } resolve_tty_path() { [[ -n "$TTY_PATH" ]] && return local candidate="" if [[ -t 0 ]]; then candidate="$(tty 2>/dev/null || true)" else # `curl | bash` occupies stdin. Ask stdout for the concrete terminal path; # terminal-browser's detached daemon cannot reopen the /dev/tty alias. exec 9>&1 candidate="$(tty <&9 2>/dev/null || true)" exec 9>&- fi [[ "$candidate" == /dev/* ]] || fail "could not determine the current terminal device" TTY_PATH="$candidate" } prepare_terminal_input() { if [[ ! -t 0 ]]; then resolve_tty_path [[ -r "$TTY_PATH" && -w "$TTY_PATH" ]] || fail "cannot use terminal device $TTY_PATH" fi } run_in_terminal() { if [[ -t 0 ]]; then "$@" else "$@" <"$TTY_PATH" fi } launcher_version_at() { local launcher="$1" [[ -x "$launcher" ]] || return 1 "$launcher" --launcher-version 2>/dev/null } install_launcher() { local installed_version="" installed_version="$(launcher_version_at "$RSI_BIN" || true)" [[ "$installed_version" == "$RSI_TERMINAL_VERSION" ]] && return command -v curl >/dev/null 2>&1 || fail "curl is required to install rsi" mkdir -p "$BIN_HOME" local staged source_file staged="$(mktemp "$BIN_HOME/.rsi.XXXXXX")" source_file="${BASH_SOURCE[0]:-}" if [[ -n "$source_file" && -f "$source_file" ]]; then if ! cp "$source_file" "$staged"; then rm -f "$staged" fail "could not stage the rsi launcher" fi elif ! curl -fsSL --retry 3 --retry-delay 2 "$RSI_TERMINAL_SOURCE_URL" -o "$staged"; then rm -f "$staged" fail "could not download the rsi launcher" fi if ! grep -q '^RSI_TERMINAL_LAUNCHER=1$' "$staged" || ! bash -n "$staged"; then rm -f "$staged" fail "the downloaded rsi launcher is invalid" fi chmod 755 "$staged" mv "$staged" "$RSI_BIN" say "installed rsi $RSI_TERMINAL_VERSION to $RSI_BIN" case ":$PATH:" in *":$BIN_HOME:"*) ;; *) say "add $BIN_HOME to PATH to run rsi later" ;; esac } find_terminal_browser() { local found="${RSI_TERMINAL_BROWSER_BIN:-}" if [[ -n "$found" && -x "$found" ]]; then BROWSER="$found" return fi found="$(command -v terminal-browser 2>/dev/null || true)" if [[ -n "$found" && -x "$found" ]]; then BROWSER="$found" return fi for found in \ "$BIN_HOME/terminal-browser" \ "$DATA_HOME/terminal-browser/app/bin/terminal-browser"; do if [[ -x "$found" ]]; then BROWSER="$found" return fi done return 1 } install_terminal_browser() { command -v curl >/dev/null 2>&1 || fail "curl is required to install terminal-browser" local installer installer="$(mktemp "${TMPDIR:-/tmp}/rsi-terminal-browser.XXXXXX")" if [[ -n "${RSI_TERMINAL_BROWSER_INSTALLER_FILE:-}" ]]; then cp "$RSI_TERMINAL_BROWSER_INSTALLER_FILE" "$installer" || { rm -f "$installer" fail "could not stage the terminal-browser installer" } elif ! curl -fsSL --retry 3 --retry-delay 2 "$TERMINAL_BROWSER_INSTALL_URL" -o "$installer"; then rm -f "$installer" fail "could not download the terminal-browser installer" fi if ! bash -n "$installer"; then rm -f "$installer" fail "the terminal-browser installer is invalid" fi if ! run_in_terminal env TERMINAL_BROWSER_SKIP_EDITOR_SETUP=1 bash "$installer"; then rm -f "$installer" fail "terminal-browser installation failed" fi rm -f "$installer" BROWSER_INSTALLED_NOW=true } ensure_terminal_browser() { find_terminal_browser && return install_terminal_browser find_terminal_browser || fail "terminal-browser was installed but could not be found" } setup_terminal_browser() { "$BROWSER" setup || say "rsi: terminal-browser setup did not complete" >&2 } launch() { say "Starting RSI — press Ctrl+Q to quit." if [[ -t 0 ]]; then exec "$BROWSER" open "$RSI_GAME_URL" --no-toolbar --no-frame else exec "$BROWSER" open "$RSI_GAME_URL" --no-toolbar --no-frame <"$TTY_PATH" fi } main() { case "${1:-}" in --launcher-version) printf '%s\n' "$RSI_TERMINAL_VERSION" return ;; --version|-v) printf 'rsi %s\n' "$RSI_TERMINAL_VERSION" return ;; --help|-h) usage return ;; "") ;; *) fail "unknown argument $1" ;; esac require_supported_terminal prepare_terminal_input install_launcher ensure_terminal_browser if running_from_pipe || [[ "$BROWSER_INSTALLED_NOW" == true ]]; then setup_terminal_browser fi if running_from_pipe; then printf '\nRSI is installed. Launching now.\n\n' printf 'Next time, run:\n\n rsi\n\n' fi launch } main "$@"