2026-03-15 18:38:00 +00:00
#!/usr/bin/env bash
2026-03-06 12:55:18 -08:00
# Start the brainstorm server and output connection info
# Usage: start-server.sh [--project-dir <path>] [--host <bind-host>] [--url-host <display-host>] [--foreground] [--background]
#
# Starts server on a random high port, outputs JSON with URL.
# Each session gets its own directory to avoid conflicts.
#
# Options:
# --project-dir <path> Store session files under <path>/.superpowers/brainstorm/
# instead of /tmp. Files persist after server stops.
# --host <bind-host> Host/interface to bind (default: 127.0.0.1).
# Use 0.0.0.0 in remote/containerized environments.
# --url-host <host> Hostname shown in returned URL JSON.
2026-06-09 15:08:09 -07:00
# --idle-timeout-minutes <n> Shut down after n minutes idle (default 240 = 4h).
2026-06-09 15:26:19 -07:00
# --open Auto-open the browser on the first screen (use only
# after the user approves the visual companion).
2026-03-06 12:55:18 -08:00
# --foreground Run server in the current terminal (no backgrounding).
# --background Force background mode (overrides Codex auto-foreground).
SCRIPT_DIR = " $( cd " $( dirname " $0 " ) " && pwd ) "
# Parse arguments
PROJECT_DIR = ""
FOREGROUND = "false"
FORCE_BACKGROUND = "false"
BIND_HOST = "127.0.0.1"
URL_HOST = ""
2026-06-09 15:08:09 -07:00
IDLE_TIMEOUT_MINUTES = ""
2026-03-06 12:55:18 -08:00
while [[ $# -gt 0 ]] ; do
case " $1 " in
--project-dir)
PROJECT_DIR = " $2 "
shift 2
;;
--host)
BIND_HOST = " $2 "
shift 2
;;
--url-host)
URL_HOST = " $2 "
shift 2
;;
2026-06-09 15:08:09 -07:00
--idle-timeout-minutes)
IDLE_TIMEOUT_MINUTES = " $2 "
shift 2
;;
2026-06-09 15:26:19 -07:00
--open)
export BRAINSTORM_OPEN = 1
shift
;;
2026-03-06 12:55:18 -08:00
--foreground| --no-daemon)
FOREGROUND = "true"
shift
;;
--background| --daemon)
FORCE_BACKGROUND = "true"
shift
;;
*)
echo "{\"error\": \"Unknown argument: $1 \"}"
exit 1
;;
esac
done
if [[ -z " $URL_HOST " ]] ; then
if [[ " $BIND_HOST " == "127.0.0.1" || " $BIND_HOST " == "localhost" ]] ; then
URL_HOST = "localhost"
else
URL_HOST = " $BIND_HOST "
fi
fi
2026-06-09 15:08:09 -07:00
if [[ -n " $IDLE_TIMEOUT_MINUTES " ]] ; then
if ! [[ " $IDLE_TIMEOUT_MINUTES " = ~ ^[ 0-9] +$ ]] || [[ " $IDLE_TIMEOUT_MINUTES " -lt 1 ]] ; then
echo "{\"error\": \"--idle-timeout-minutes must be a positive integer\"}"
exit 1
fi
export BRAINSTORM_IDLE_TIMEOUT_MS = $(( IDLE_TIMEOUT_MINUTES * 60 * 1000 ))
fi
2026-03-09 20:29:15 -07:00
# Some environments reap detached/background processes. Auto-foreground when detected.
2026-03-06 12:55:18 -08:00
if [[ -n " ${ CODEX_CI :- } " && " $FOREGROUND " != "true" && " $FORCE_BACKGROUND " != "true" ]] ; then
FOREGROUND = "true"
fi
2026-03-15 18:24:23 +00:00
# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected.
if [[ " $FOREGROUND " != "true" && " $FORCE_BACKGROUND " != "true" ]] ; then
case " ${ OSTYPE :- } " in
msys*| cygwin*| mingw*) FOREGROUND = "true" ;;
esac
if [[ -n " ${ MSYSTEM :- } " ]] ; then
FOREGROUND = "true"
fi
fi
2026-03-06 12:55:18 -08:00
# Generate unique session directory
SESSION_ID = " $$ - $( date +%s) "
if [[ -n " $PROJECT_DIR " ]] ; then
2026-03-24 11:07:59 -07:00
SESSION_DIR = " ${ PROJECT_DIR } /.superpowers/brainstorm/ ${ SESSION_ID } "
2026-06-09 15:22:23 -07:00
# Persist the bound port per project so a restart reuses it and an already-open
# browser tab reconnects to the same URL.
export BRAINSTORM_PORT_FILE = " ${ PROJECT_DIR } /.superpowers/brainstorm/.last-port"
2026-03-06 12:55:18 -08:00
else
2026-03-24 11:07:59 -07:00
SESSION_DIR = "/tmp/brainstorm- ${ SESSION_ID } "
2026-03-06 12:55:18 -08:00
fi
2026-03-24 11:07:59 -07:00
STATE_DIR = " ${ SESSION_DIR } /state"
PID_FILE = " ${ STATE_DIR } /server.pid"
LOG_FILE = " ${ STATE_DIR } /server.log"
2026-03-06 12:55:18 -08:00
2026-03-24 11:07:59 -07:00
# Create fresh session directory with content and state peers
mkdir -p " ${ SESSION_DIR } /content" " $STATE_DIR "
2026-03-06 12:55:18 -08:00
# Kill any existing server
if [[ -f " $PID_FILE " ]] ; then
old_pid = $( cat " $PID_FILE " )
kill " $old_pid " 2>/dev/null
rm -f " $PID_FILE "
fi
cd " $SCRIPT_DIR "
2026-03-11 18:47:47 -07:00
# Resolve the harness PID (grandparent of this script).
# $PPID is the ephemeral shell the harness spawned to run us — it dies
# when this script exits. The harness itself is $PPID's parent.
OWNER_PID = " $( ps -o ppid = -p " $PPID " 2>/dev/null | tr -d ' ' ) "
if [[ -z " $OWNER_PID " || " $OWNER_PID " == "1" ]] ; then
OWNER_PID = " $PPID "
fi
2026-06-01 17:26:22 -04:00
# Windows/MSYS2: Node.js cannot see POSIX PIDs from the MSYS2 namespace.
# Passing a PID node cannot verify causes server to log owner-pid-invalid
# and self-terminate at the 60-second lifecycle check. Clear it so the
# watchdog is disabled and the idle timeout becomes the only shutdown trigger.
case " ${ OSTYPE :- } " in
msys*| cygwin*| mingw*) OWNER_PID = "" ;;
esac
if [[ -n " ${ MSYSTEM :- } " ]] ; then
OWNER_PID = ""
fi
2026-03-06 12:55:18 -08:00
# Foreground mode for environments that reap detached/background processes.
if [[ " $FOREGROUND " == "true" ]] ; then
2026-06-01 17:26:22 -04:00
env BRAINSTORM_DIR = " $SESSION_DIR " BRAINSTORM_HOST = " $BIND_HOST " BRAINSTORM_URL_HOST = " $URL_HOST " BRAINSTORM_OWNER_PID = " $OWNER_PID " node server.cjs &
SERVER_PID = $!
echo " $SERVER_PID " > " $PID_FILE "
wait " $SERVER_PID "
2026-03-06 12:55:18 -08:00
exit $?
fi
# Start server, capturing output to log file
# Use nohup to survive shell exit; disown to remove from job table
2026-03-24 11:07:59 -07:00
nohup env BRAINSTORM_DIR = " $SESSION_DIR " BRAINSTORM_HOST = " $BIND_HOST " BRAINSTORM_URL_HOST = " $URL_HOST " BRAINSTORM_OWNER_PID = " $OWNER_PID " node server.cjs > " $LOG_FILE " 2>& 1 &
2026-03-06 12:55:18 -08:00
SERVER_PID = $!
disown " $SERVER_PID " 2>/dev/null
echo " $SERVER_PID " > " $PID_FILE "
# Wait for server-started message (check log file)
for i in { 1..50} ; do
if grep -q "server-started" " $LOG_FILE " 2>/dev/null; then
# Verify server is still alive after a short window (catches process reapers)
alive = "true"
for _ in { 1..20} ; do
if ! kill -0 " $SERVER_PID " 2>/dev/null; then
alive = "false"
break
fi
sleep 0.1
done
if [[ " $alive " != "true" ]] ; then
echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR /start-server.sh ${ PROJECT_DIR :+ --project-dir $PROJECT_DIR } --host $BIND_HOST --url-host $URL_HOST --foreground\"}"
exit 1
fi
grep "server-started" " $LOG_FILE " | head -1
exit 0
fi
sleep 0.1
done
# Timeout - server didn't start
echo '{"error": "Server failed to start within 5 seconds"}'
exit 1