File size: 3,522 Bytes
a753e74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env bash
# Shared helpers for Research-Link-AI dev scripts. Source this file.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
RUNTIME="$ROOT/.runtime"
mkdir -p "$RUNTIME"

# Load .env if present (export all keys).
if [ -f "$ROOT/.env" ]; then
  set -a; . "$ROOT/.env"; set +a
fi

# detect_ip β†’ the machine's primary LAN IPv4 (falls back to 127.0.0.1)
detect_ip() {
  local ip
  ip="$(python - <<'PY' 2>/dev/null
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
    s.connect(("8.8.8.8", 80)); print(s.getsockname()[0])
except Exception:
    print("")
finally:
    s.close()
PY
)"
  if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1; then
    ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  fi
  echo "${ip:-127.0.0.1}"
}

# Bind on all interfaces so the server is reachable from other machines.
BIND_HOST="${RESEARCHLINK_BIND_HOST:-0.0.0.0}"
BACKEND_PORT="${RESEARCHLINK_BACKEND_PORT:-8000}"
FRONTEND_PORT="${RESEARCHLINK_FRONTEND_PORT:-5173}"
LLM_MODE="${RESEARCHLINK_LLM_MODE:-offline}"
LLM_MODEL="${RESEARCHLINK_LLM_MODEL:-researchlink-local}"

# Advertised host shown in URLs: explicit override β†’ auto-detected LAN IP.
if [ -n "${RESEARCHLINK_PUBLIC_HOST:-}" ]; then
  ADV_HOST="$RESEARCHLINK_PUBLIC_HOST"
elif [ "$BIND_HOST" = "127.0.0.1" ] || [ "$BIND_HOST" = "localhost" ]; then
  ADV_HOST="127.0.0.1"
else
  ADV_HOST="$(detect_ip)"
fi

BACKEND_URL="http://${ADV_HOST}:${BACKEND_PORT}"          # for the browser
HEALTH_URL="http://127.0.0.1:${BACKEND_PORT}"             # for local health checks
FRONTEND_HOST="${RESEARCHLINK_FRONTEND_HOST:-0.0.0.0}"
FRONTEND_DIR="$ROOT/frontend"   # optional Node frontend (auto-detected)

BACKEND_PID="$RUNTIME/backend.pid"
FRONTEND_PID="$RUNTIME/frontend.pid"
BACKEND_LOG="$RUNTIME/backend.log"
FRONTEND_LOG="$RUNTIME/frontend.log"

c_green() { printf '\033[32m%s\033[0m\n' "$1"; }
c_red()   { printf '\033[31m%s\033[0m\n' "$1"; }
c_yellow(){ printf '\033[33m%s\033[0m\n' "$1"; }
c_dim()   { printf '\033[2m%s\033[0m\n' "$1"; }

# pid_alive <pidfile>  β†’ 0 if the recorded PID is running
pid_alive() {
  local f="$1"
  [ -f "$f" ] || return 1
  local pid; pid="$(cat "$f" 2>/dev/null || true)"
  [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
}

# port_in_use <host> <port> β†’ 0 if something is listening
port_in_use() {
  local host="$1" port="$2"
  if command -v lsof >/dev/null 2>&1; then
    lsof -iTCP:"$port" -sTCP:LISTEN -n >/dev/null 2>&1 && return 0 || return 1
  fi
  (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null && { exec 3>&- 3<&- || true; return 0; } || return 1
}

# key_configured β€” echoes "yes"/"no"/"n/a" for the current LLM_MODE
key_configured() {
  case "$LLM_MODE" in
    anthropic)  [ -n "${ANTHROPIC_API_KEY:-}" ] && echo yes || echo no ;;
    openai)     [ -n "${OPENAI_API_KEY:-}" ] && echo yes || echo no ;;
    openrouter) [ -n "${OPENROUTER_API_KEY:-}" ] && echo yes || echo no ;;
    *)          echo "n/a" ;;
  esac
}

is_external_mode() {
  case "$LLM_MODE" in offline) return 1 ;; *) return 0 ;; esac
}

# http_ok <url> β†’ 0 if the URL returns a 2xx within the timeout
http_ok() {
  python - "$1" <<'PY' 2>/dev/null
import sys, urllib.request
try:
    urllib.request.urlopen(sys.argv[1], timeout=2).read()
except Exception:
    sys.exit(1)
PY
}

# wait_health <url> <tries> β†’ 0 once healthy, 1 if it never comes up
wait_health() {
  local url="$1" tries="${2:-40}"
  for _ in $(seq 1 "$tries"); do
    http_ok "$url" && return 0
    sleep 0.5
  done
  return 1
}