#!/usr/bin/env bash set -euo pipefail usage() { cat >&2 <<'EOF' Usage: tools/pw [--session ] [args...] Session resolution priority: 1) --session 2) PLAYWRIGHT_SHARED_SESSION 3) PLAYWRIGHT_SESSION_OWNER 4) CODEX_THREAD_ID 5) codex-default EOF } if [[ $# -lt 1 ]]; then usage exit 1 fi CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" PWCLI="${PWCLI:-$CODEX_HOME/skills/playwright/scripts/playwright_cli.sh}" LOCK_TIMEOUT="${PLAYWRIGHT_SHARED_LOCK_TIMEOUT:-120}" LOCK_ROOT="${PLAYWRIGHT_SHARED_LOCK_DIR_BASE:-/tmp/pw-session-locks}" INIT_MODE="${PLAYWRIGHT_SHARED_INIT_MODE:-headed}" if [[ ! -d "$LOCK_ROOT" ]]; then mkdir -p "$LOCK_ROOT" fi if ! command -v npx >/dev/null 2>&1; then echo "npx is required." >&2 exit 1 fi if [[ ! -x "$PWCLI" ]]; then echo "Playwright wrapper not found or not executable: $PWCLI" >&2 exit 1 fi sanitize_token() { local value="$1" value="$(printf '%s' "$value" \ | tr '[:upper:]' '[:lower:]' \ | tr -cs 'a-z0-9._-' '-' \ | sed -e 's/^-*//' -e 's/-*$//')" if [[ -z "$value" ]]; then value="default" fi printf '%s' "$value" } derive_session() { if [[ -n "${PLAYWRIGHT_SHARED_SESSION:-}" ]]; then printf '%s' "$PLAYWRIGHT_SHARED_SESSION" return fi local owner="${PLAYWRIGHT_SESSION_OWNER:-${CODEX_THREAD_ID:-default}}" local owner_hash owner_hash="$(printf '%s' "$owner" | shasum -a 256 | awk '{print substr($1,1,12)}')" if [[ -z "$owner_hash" ]]; then owner_hash="default" fi printf 'codex-%s' "$owner_hash" } session_override="" cmd=() while [[ $# -gt 0 ]]; do case "$1" in --session) if [[ $# -lt 2 ]]; then echo "--session requires a value." >&2 exit 1 fi session_override="$2" shift 2 ;; --session=*) session_override="${1#--session=}" shift ;; *) cmd+=("$1") shift ;; esac done if [[ ${#cmd[@]} -lt 1 ]]; then usage exit 1 fi SESSION="${session_override:-$(derive_session)}" SAFE_SESSION="$(sanitize_token "$SESSION")" LOCK_DIR="${LOCK_ROOT}/${SAFE_SESSION}.lock" # Default to a single user-owned Chrome via CDP unless caller overrides. : "${PLAYWRIGHT_MCP_CDP_ENDPOINT:=http://127.0.0.1:9222}" : "${PLAYWRIGHT_MCP_ISOLATED:=false}" export PLAYWRIGHT_MCP_CDP_ENDPOINT export PLAYWRIGHT_MCP_ISOLATED acquire_lock() { local start_ts now lock_pid lock_mtime start_ts="$(date +%s)" while ! mkdir "$LOCK_DIR" 2>/dev/null; do lock_pid="" if [[ -f "$LOCK_DIR/pid" ]]; then lock_pid="$(cat "$LOCK_DIR/pid" 2>/dev/null || true)" fi if [[ -z "$lock_pid" ]]; then lock_mtime="$(stat -f %m "$LOCK_DIR" 2>/dev/null || echo 0)" now="$(date +%s)" if (( now - lock_mtime >= 5 )); then rm -rf "$LOCK_DIR" 2>/dev/null || true continue fi fi if [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then rm -rf "$LOCK_DIR" 2>/dev/null || true continue fi now="$(date +%s)" if (( now - start_ts >= LOCK_TIMEOUT )); then echo "Timeout waiting for Playwright shared-session lock: $LOCK_DIR" >&2 exit 1 fi sleep 1 done printf "%s\n" "$$" > "$LOCK_DIR/pid" trap 'rm -rf "$LOCK_DIR" 2>/dev/null || true' EXIT INT TERM } run_pw() { "$PWCLI" --session "$SESSION" "$@" } is_missing_session_error() { local text="$1" if echo "$text" | rg -qi "session.*not found|no browser|not open|closed"; then return 0 fi return 1 } init_session() { local init_code set +e if [[ "$INIT_MODE" == "headless" ]]; then run_pw open about:blank >/dev/null 2>&1 init_code=$? elif [[ "$INIT_MODE" == "headed" ]]; then run_pw open about:blank --headed >/dev/null 2>&1 init_code=$? if [[ $init_code -ne 0 ]]; then run_pw open about:blank >/dev/null 2>&1 init_code=$? fi else run_pw open about:blank --headed >/dev/null 2>&1 init_code=$? if [[ $init_code -ne 0 ]]; then run_pw open about:blank >/dev/null 2>&1 init_code=$? fi fi set -e if [[ $init_code -ne 0 ]]; then cat >&2 <&2 exit $code fi fi fi set +e out="$(run_pw "${cmd[@]}" 2>&1)" code=$? set -e if [[ $code -eq 0 ]]; then echo "$out" exit 0 fi if is_missing_session_error "$out"; then init_session run_pw "${cmd[@]}" exit 0 fi echo "$out" >&2 exit $code