Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 65 additions & 23 deletions scripts/discover-servers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,54 @@
# No marimo installation required.
set -euo pipefail

# Locate the servers directory
is_windows=false
if [[ "$OSTYPE" == msys* || "$OSTYPE" == cygwin* ]]; then
is_windows=true
servers_dir="$HOME/.marimo/servers"
else
servers_dir="${XDG_STATE_HOME:-$HOME/.local/state}/marimo/servers"
# Build the candidate list of registry directories. Several shells can read
# marimo's registry, and they don't all agree on where it lives:
# * Linux/macOS native -> $XDG_STATE_HOME/marimo/servers (POSIX path)
# * MSYS2 / Cygwin / Git Bash -> $HOME/.marimo/servers (Windows-native marimo)
# * WSL pointed at Windows -> $USERPROFILE translated via wslpath/cygpath
# -> .../.marimo/servers
# OSTYPE-based detection misses the WSL case (OSTYPE=linux-gnu there), and
# selecting just the first candidate that *contains files* fails when one
# candidate has only stale entries while another has the live server. So we
# scan every candidate and aggregate live entries across all of them.
candidates=(
"${XDG_STATE_HOME:-$HOME/.local/state}/marimo/servers"
"$HOME/.marimo/servers"
)
if [[ -n "${USERPROFILE:-}" ]]; then
if command -v wslpath >/dev/null 2>&1; then
win_home=$(wslpath -u "$USERPROFILE" 2>/dev/null) || win_home=""
elif command -v cygpath >/dev/null 2>&1; then
win_home=$(cygpath -u "$USERPROFILE" 2>/dev/null) || win_home=""
else
win_home=""
fi
[[ -n "$win_home" ]] && candidates+=("$win_home/.marimo/servers")
fi

if [[ ! -d "$servers_dir" ]]; then
echo "[]"
exit 0
fi
# Dedupe by string. On Git Bash $HOME and the cygpath translation of
# $USERPROFILE typically resolve to the same path (e.g. /c/Users/foo), and
# without dedupe the same server is listed twice. O(n^2) loop is fine — the
# candidate list never exceeds a handful.
deduped=()
for d in "${candidates[@]}"; do
is_dup=false
for e in "${deduped[@]+${deduped[@]}}"; do
if [[ "$e" == "$d" ]]; then
is_dup=true
break
fi
done
$is_dup || deduped+=("$d")
done
candidates=("${deduped[@]}")

# Liveness check. On POSIX, `kill -0 $pid` is cheap and reliable. On Windows
# (Git Bash/MSYS2) `kill` operates on Cygwin PIDs, not the native Windows PIDs
# marimo writes, so fall back to an HTTP probe against marimo's /health.
# (Git Bash/MSYS2/WSL pointed at Windows) `kill` operates on Cygwin/Linux PIDs,
# not the native Windows PIDs marimo writes, so fall back to an HTTP probe
# against marimo's /health. The ``is_windows`` flag is set per candidate
# directory below — a registry living at .../.marimo/servers always houses
# Windows-native PIDs regardless of which shell is reading it.
check_live() {
local f="$1"
if [[ "$is_windows" == false ]]; then
Expand All @@ -36,19 +67,30 @@ check_live() {
fi
}

# Walk every candidate, applying per-candidate liveness rules, and collect
# every live entry. A stale POSIX/XDG registry can't shadow a live Windows-
# native one this way, and a user with two live marimos in different
# registries (rare but possible) sees both.
results="[]"
for f in "$servers_dir"/*.json; do
[[ -e "$f" ]] || continue
for d in "${candidates[@]}"; do
[[ -d "$d" ]] || continue
case "$d" in
*/.marimo/servers) is_windows=true ;;
*) is_windows=false ;;
esac
for f in "$d"/*.json; do
[[ -e "$f" ]] || continue

if ! check_live "$f"; then
# On Windows the HTTP probe can fail transiently (slow start, busy server),
# so keep the entry; only POSIX `kill -0` is reliable enough to delete on.
[[ "$is_windows" == false ]] && rm -f "$f"
continue
fi
if ! check_live "$f"; then
# On Windows the HTTP probe can fail transiently (slow start, busy server),
# so keep the entry; only POSIX `kill -0` is reliable enough to delete on.
[[ "$is_windows" == false ]] && rm -f "$f"
continue
fi

entry=$(jq '.' "$f" 2>/dev/null) || continue
results=$(echo "$results" | jq --argjson e "$entry" '. + [$e]')
entry=$(jq '.' "$f" 2>/dev/null) || continue
results=$(echo "$results" | jq --argjson e "$entry" '. + [$e]')
done
done

echo "$results" | jq .
126 changes: 92 additions & 34 deletions scripts/execute-code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,54 @@ if [[ -n "$url" ]]; then
*) echo "Warning: connecting to non-local server '${url_host}'. Ensure this is trusted." >&2 ;;
esac
else
# Locate the servers directory
is_windows=false
if [[ "$OSTYPE" == msys* || "$OSTYPE" == cygwin* ]]; then
is_windows=true
servers_dir="$HOME/.marimo/servers"
else
servers_dir="${XDG_STATE_HOME:-$HOME/.local/state}/marimo/servers"
# Build the candidate list of registry directories. Several shells can read
# marimo's registry, and they don't all agree on where it lives:
# * Linux/macOS native -> $XDG_STATE_HOME/marimo/servers (POSIX path)
# * MSYS2 / Cygwin / Git Bash -> $HOME/.marimo/servers (Windows-native marimo)
# * WSL pointed at Windows -> $USERPROFILE translated via wslpath/cygpath
# -> .../.marimo/servers
# OSTYPE-based detection misses the WSL case (OSTYPE=linux-gnu there), and
# selecting just the first candidate that *contains files* fails when one
# candidate has only stale entries while another has the live server. So we
# scan every candidate and aggregate live entries across all of them.
candidates=(
"${XDG_STATE_HOME:-$HOME/.local/state}/marimo/servers"
"$HOME/.marimo/servers"
)
if [[ -n "${USERPROFILE:-}" ]]; then
if command -v wslpath >/dev/null 2>&1; then
win_home=$(wslpath -u "$USERPROFILE" 2>/dev/null) || win_home=""
elif command -v cygpath >/dev/null 2>&1; then
win_home=$(cygpath -u "$USERPROFILE" 2>/dev/null) || win_home=""
else
win_home=""
fi
[[ -n "$win_home" ]] && candidates+=("$win_home/.marimo/servers")
fi

# Dedupe by string. On Git Bash $HOME and the cygpath translation of
# $USERPROFILE typically resolve to the same path (e.g. /c/Users/foo), and
# without dedupe the same server is counted twice. O(n^2) loop is fine —
# the candidate list never exceeds a handful.
deduped=()
for d in "${candidates[@]}"; do
is_dup=false
for e in "${deduped[@]+${deduped[@]}}"; do
if [[ "$e" == "$d" ]]; then
is_dup=true
break
fi
done
$is_dup || deduped+=("$d")
done
candidates=("${deduped[@]}")

# Liveness check. On POSIX, `kill -0 $pid` is cheap and reliable. On Windows
# (Git Bash/MSYS2) `kill` operates on Cygwin PIDs, not the native Windows PIDs
# marimo writes, so fall back to an HTTP probe against marimo's /health.
# (Git Bash/MSYS2/WSL pointed at Windows) `kill` operates on Cygwin/Linux PIDs,
# not the native Windows PIDs marimo writes, so fall back to an HTTP probe
# against marimo's /health. The ``is_windows`` flag is set per candidate
# directory below — a registry living at .../.marimo/servers always houses
# Windows-native PIDs regardless of which shell is reading it.
check_live() {
local f="$1"
if [[ "$is_windows" == false ]]; then
Expand All @@ -86,33 +122,43 @@ else
fi
}

# Find a live registry entry
# Walk every candidate, applying per-candidate liveness rules, and find live
# registry entries. ``--port`` short-circuits on first match; otherwise we
# tally across all candidates so a stale POSIX/XDG registry can't shadow a
# live Windows-native one (and vice versa).
entry=""
count=0
for f in "$servers_dir"/*.json; do
[[ -e "$f" ]] || continue

if ! check_live "$f"; then
# On Windows the HTTP probe can fail transiently (slow start, busy server),
# so keep the entry; only POSIX `kill -0` is reliable enough to delete on.
[[ "$is_windows" == false ]] && rm -f "$f"
continue
fi
for d in "${candidates[@]}"; do
[[ -d "$d" ]] || continue
case "$d" in
*/.marimo/servers) is_windows=true ;;
*) is_windows=false ;;
esac
for f in "$d"/*.json; do
[[ -e "$f" ]] || continue

if ! check_live "$f"; then
# On Windows the HTTP probe can fail transiently (slow start, busy server),
# so keep the entry; only POSIX `kill -0` is reliable enough to delete on.
[[ "$is_windows" == false ]] && rm -f "$f"
continue
fi

e=$(cat "$f")
e=$(cat "$f")

if [[ -n "$port" ]]; then
e_port=$(echo "$e" | jq -r '.port')
if [[ "$e_port" == "$port" ]]; then
entry="$e"
count=1
break
if [[ -n "$port" ]]; then
e_port=$(echo "$e" | jq -r '.port')
if [[ "$e_port" == "$port" ]]; then
entry="$e"
count=1
break 2
fi
continue
fi
continue
fi

entry="$e"
count=$((count + 1))
entry="$e"
count=$((count + 1))
done
done

if [[ $count -eq 0 ]]; then
Expand All @@ -122,10 +168,17 @@ else

if [[ $count -gt 1 ]]; then
echo "Multiple instances found. Use --port to specify:" >&2
for f in "$servers_dir"/*.json; do
[[ -e "$f" ]] || continue
check_live "$f" || continue
jq -r '.server_id' "$f" >&2
for d in "${candidates[@]}"; do
[[ -d "$d" ]] || continue
case "$d" in
*/.marimo/servers) is_windows=true ;;
*) is_windows=false ;;
esac
for f in "$d"/*.json; do
[[ -e "$f" ]] || continue
check_live "$f" || continue
jq -r '.server_id' "$f" >&2
done
done
exit 1
fi
Expand Down Expand Up @@ -175,6 +228,11 @@ exit_code=0
current_event=""
done_received=false
while IFS= read -r line && [[ "$done_received" == false ]]; do
# marimo's HTTP server uses CRLF line terminators on the SSE stream
# (per the spec). bash's `read -r` strips the LF but leaves the CR,
# so the literal-prefix patterns below would never match on Windows
# (Git Bash and WSL) without this trim. Cheap to do unconditionally.
line="${line%$'\r'}"
case "$line" in
event:*)
current_event="${line#event: }"
Expand Down