Running a dozen Next.js devservers without losing your mind

Right now it seems to me the best way to work is with a bunch of Claude Code instances via Conductor. I’m getting out north of 30 PRs a day this way, some of them very significant in scope. What this means, though, is a lot of context switching. I am not going to remember that “fix-card-image-bg-loading” is running on port 3002 and “test-dropbox-cache-bust” is running on port 3010. So Claude and I wrote a shell script to keep us sane.

The basic idea is that you should be able to refer to any branch you are working on, by its branch name, and the script translates that into a running devserver and a browser pointing at it and local Inngest, too, isolated to just one devserver, if you need that. I also generate zsh completions so I can tab complete the branch name in all these commands.

#!/bin/bash
#
# mydev - Manage devservers across workspaces
#
# Usage:
#   mydev [workspace]               Start devserver and open browser (default)
#   mydev open [workspace]          Same as above (explicit)
#   mydev start [workspace]         Start devserver if not running (no browser, idempotent)
#   mydev cd [workspace]            cd to workspace/repo root
#   mydev edit [workspace]          Open workspace in editor ($VISUAL or $EDITOR)
#   mydev list                      List all running devservers and Inngest status
#   mydev inngest [workspace]       Start Inngest dev server for a specific workspace
#   mydev inngest-open              Open Inngest dashboard in browser
#   mydev stop [workspace]          Stop devserver for workspace
#   mydev stop-all                  Stop all devservers
#   mydev help                      Show this help
#
# Workspace can be specified as:
#   - Workspace name (e.g., "feature-1", "bugfix-2")
#   - Branch name (e.g., "add-user-auth") - finds matching workspace
#   - "main" - the main repo checkout at ~/my-project
#   - Omitted - detects from current directory or git branch
#
# Inngest:
#   The Inngest dev server connects to ONE app instance at a time.
#   This prevents confusion when multiple instances are running - only the
#   connected one will handle Inngest jobs.
#
#   To use Inngest:
#     1. Start your devserver: mydev my-branch
#     2. In another terminal, connect Inngest: mydev inngest my-branch
#     3. To switch to a different instance: mydev inngest other-branch
#
#   Dashboard: http://localhost:8288
#
# Setup:
#   Add to your ~/.zshrc:
#
#     export PATH="$HOME/my-project/scripts:$PATH"
#     source "$HOME/my-project/scripts/mydev-completion.zsh"
#
#   This gives you:
#     - mydev command available everywhere
#     - Tab completion for workspace names, branch names, and commands

set -e

REGISTRY_FILE="$HOME/.mydev-devservers.json"
INNGEST_LOCK_FILE="$HOME/.mydev-inngest-lock"
WORKSPACES_DIR="$HOME/workspaces"
MAIN_REPO="$HOME/my-project"
MAIN_WORKSPACE_NAME="main"
APP_SUBDIR="apps/frontend"  # Path to your app within the repo
MIN_PORT=3000
MAX_PORT=3020
INNGEST_PORT=8288

# Initialize registry file if it doesn't exist
init_registry() {
  if [[ ! -f "$REGISTRY_FILE" ]]; then
    echo '{}' > "$REGISTRY_FILE"
  fi
}

# Find workspace by branch name (scans all workspaces including main repo)
find_workspace_by_branch() {
  local target_branch="$1"

  # Check main repo first
  if [[ -d "$MAIN_REPO/.git" ]]; then
    local branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [[ "$branch" == "$target_branch" ]]; then
      echo "$MAIN_WORKSPACE_NAME"
      return
    fi
  fi

  # Check workspaces
  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    if [[ -d "$workspace_dir/.git" ]] || [[ -f "$workspace_dir/.git" ]]; then
      local branch=$(git -C "$workspace_dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
      if [[ "$branch" == "$target_branch" ]]; then
        basename "$workspace_dir"
        return
      fi
    fi
  done

  echo ""
}

# Get workspace name from current directory or argument
get_workspace() {
  local input="$1"

  if [[ -n "$input" ]]; then
    if [[ "$input" == "$MAIN_WORKSPACE_NAME" ]]; then
      echo "$MAIN_WORKSPACE_NAME"
      return
    fi

    if [[ -d "$WORKSPACES_DIR/$input" ]]; then
      echo "$input"
      return
    fi

    local ws=$(find_workspace_by_branch "$input")
    if [[ -n "$ws" ]]; then
      echo "$ws"
      return
    fi

    echo ""
    return
  fi

  # Try to detect from current directory
  local cwd="$(pwd)"
  if [[ "$cwd" == "$MAIN_REPO"* ]]; then
    echo "$MAIN_WORKSPACE_NAME"
    return
  fi

  if [[ "$cwd" == "$WORKSPACES_DIR/"* ]]; then
    local rel_path="${cwd#$WORKSPACES_DIR/}"
    local workspace="${rel_path%%/*}"
    echo "$workspace"
    return
  fi

  # Fallback: try to match current git branch to a workspace's branch
  if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null; then
    local current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [[ -n "$current_branch" ]]; then
      local ws=$(find_workspace_by_branch "$current_branch")
      if [[ -n "$ws" ]]; then
        echo "$ws"
        return
      fi
    fi
  fi

  echo ""
}

# Get app directory for a workspace
get_app_dir() {
  local workspace="$1"

  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    echo "$MAIN_REPO/$APP_SUBDIR"
  else
    echo "$WORKSPACES_DIR/$workspace/$APP_SUBDIR"
  fi
}

# Get workspace root directory
get_workspace_root() {
  local workspace="$1"

  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    echo "$MAIN_REPO"
  else
    echo "$WORKSPACES_DIR/$workspace"
  fi
}

# Validate workspace exists and has the app
validate_workspace() {
  local workspace="$1"
  local app_path=$(get_app_dir "$workspace")

  if [[ ! -d "$app_path" ]]; then
    echo "Error: App not found at $app_path" >&2
    echo "" >&2
    echo "Available workspaces:" >&2

    if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
      local main_branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
      echo "  $main_branch ($MAIN_WORKSPACE_NAME) [main repo]" >&2
    fi

    for ws_dir in "$WORKSPACES_DIR"/*/; do
      local ws_name=$(basename "$ws_dir")
      local ws_branch=$(git -C "$ws_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
      echo "  $ws_branch ($ws_name)" >&2
    done
    return 1
  fi
  return 0
}

# Check if a port is in use
is_port_in_use() {
  local port="$1"
  lsof -i ":$port" >/dev/null 2>&1
}

# Find the first available port
find_available_port() {
  for port in $(seq $MIN_PORT $MAX_PORT); do
    if ! is_port_in_use "$port"; then
      echo "$port"
      return
    fi
  done
  echo ""
}

# Get PID of process using a port
get_pid_on_port() {
  local port="$1"
  lsof -ti ":$port" 2>/dev/null | head -1
}

# Find existing devserver for a workspace by scanning processes
find_existing_devserver() {
  local workspace="$1"
  local workspace_root=$(get_workspace_root "$workspace")

  local search_pattern
  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    search_pattern="$MAIN_REPO/$APP_SUBDIR"
  else
    search_pattern="$workspace/$APP_SUBDIR"
  fi

  local process_line=$(ps aux | grep -E "$search_pattern.*/next.*dev|next.*dev.*$search_pattern" | grep -v grep | head -1)

  if [[ -z "$process_line" ]]; then
    process_line=$(ps aux | grep -E "$search_pattern.*next-server|next-server.*$search_pattern" | grep -v grep | head -1)
  fi

  if [[ -n "$process_line" ]]; then
    local pid=$(echo "$process_line" | awk '{print $2}')
    local port=$(echo "$process_line" | grep -oE '\-p [0-9]+' | tail -1 | awk '{print $2}')

    if [[ -z "$port" ]]; then
      port=3000
    fi

    if [[ -n "$pid" ]] && [[ -n "$port" ]]; then
      echo "$port:$pid"
      return
    fi
  fi

  echo ""
}

# Registry operations
get_workspace_port() {
  local workspace="$1"
  if [[ -f "$REGISTRY_FILE" ]]; then
    jq -r ".[\"$workspace\"].port // empty" "$REGISTRY_FILE"
  fi
}

get_workspace_pid() {
  local workspace="$1"
  if [[ -f "$REGISTRY_FILE" ]]; then
    jq -r ".[\"$workspace\"].pid // empty" "$REGISTRY_FILE"
  fi
}

is_workspace_running() {
  local workspace="$1"
  local port=$(get_workspace_port "$workspace")

  if [[ -z "$port" ]]; then
    return 1
  fi

  if is_port_in_use "$port"; then
    return 0
  fi

  return 1
}

save_registry_entry() {
  local workspace="$1"
  local port="$2"
  local pid="$3"

  local tmp_file=$(mktemp)
  jq --arg ws "$workspace" --arg port "$port" --arg pid "$pid" \
    '.[$ws] = {port: ($port | tonumber), pid: ($pid | tonumber)}' \
    "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE"
}

remove_registry_entry() {
  local workspace="$1"

  local tmp_file=$(mktemp)
  jq --arg ws "$workspace" 'del(.[$ws])' "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE"
}

cleanup_registry() {
  if [[ ! -f "$REGISTRY_FILE" ]]; then
    return
  fi

  local workspaces=$(jq -r 'keys[]' "$REGISTRY_FILE")
  for ws in $workspaces; do
    if ! is_workspace_running "$ws"; then
      remove_registry_entry "$ws"
    fi
  done
}

# Inngest operations
is_inngest_running() {
  is_port_in_use "$INNGEST_PORT"
}

get_inngest_lock_workspace() {
  if [[ -f "$INNGEST_LOCK_FILE" ]]; then
    cat "$INNGEST_LOCK_FILE"
  else
    echo ""
  fi
}

set_inngest_lock() {
  local workspace="$1"
  echo "$workspace" > "$INNGEST_LOCK_FILE"
}

clear_inngest_lock() {
  rm -f "$INNGEST_LOCK_FILE"
}

is_inngest_lock_valid() {
  if is_inngest_running && [[ -f "$INNGEST_LOCK_FILE" ]]; then
    return 0
  fi
  if [[ -f "$INNGEST_LOCK_FILE" ]] && ! is_inngest_running; then
    clear_inngest_lock
  fi
  return 1
}

stop_inngest_and_wait() {
  local inngest_pid=$(get_pid_on_port "$INNGEST_PORT")
  if [[ -n "$inngest_pid" ]]; then
    kill "$inngest_pid" 2>/dev/null || true
  fi

  local attempts=0
  local max_attempts=50
  while is_port_in_use "$INNGEST_PORT"; do
    sleep 0.1
    ((attempts++))
    if [[ $attempts -ge $max_attempts ]]; then
      echo "Warning: Inngest port $INNGEST_PORT still in use after 5s, forcing..." >&2
      lsof -ti ":$INNGEST_PORT" | xargs kill -9 2>/dev/null || true
      sleep 0.5
      break
    fi
  done

  clear_inngest_lock
}

# Start devserver for a workspace
start_devserver() {
  local workspace="$1"
  local no_browser="${2:-}"
  local app_path=$(get_app_dir "$workspace")
  local ws_root=$(get_workspace_root "$workspace")

  validate_workspace "$workspace" || exit 1

  local port=$(find_available_port)
  if [[ -z "$port" ]]; then
    echo "Error: No available ports between $MIN_PORT and $MAX_PORT" >&2
    exit 1
  fi

  local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$workspace")

  echo -ne "\033]0;▶ [$branch $port]\007"
  if is_inngest_running; then
    echo "🚀 App: $branch (port $port, inngest: $INNGEST_PORT)"
  else
    echo "🚀 App: $branch (port $port)"
    echo "   Note: Inngest not running. Start with: mydev inngest"
  fi

  cd "$app_path"

  NEXT_PUBLIC_APP_URL="http://localhost:$port" INNGEST_BASE_URL="http://localhost:$INNGEST_PORT" pnpm dev -p "$port" &
  local dev_pid=$!

  trap 'kill $dev_pid 2>/dev/null; exit' INT TERM
  trap 'kill $dev_pid 2>/dev/null' EXIT

  local attempts=0
  local max_attempts=150
  while ! lsof -i ":$port" >/dev/null 2>&1; do
    if ! kill -0 "$dev_pid" 2>/dev/null; then
      echo "Error: devserver failed to start" >&2
      wait "$dev_pid"
      exit 1
    fi
    sleep 0.2
    ((attempts++))
    if [[ $attempts -ge $max_attempts ]]; then
      echo "Warning: Server not detected on port $port yet" >&2
      break
    fi
  done

  if [[ "$no_browser" != "--no-browser" ]]; then
    open "http://localhost:$port"
  fi

  wait "$dev_pid"
}

# Stop devserver for a workspace
stop_devserver() {
  local workspace="$1"

  local port=$(get_workspace_port "$workspace")
  local pid=$(get_workspace_pid "$workspace")

  if [[ -z "$pid" ]]; then
    local existing=$(find_existing_devserver "$workspace")
    if [[ -n "$existing" ]]; then
      port="${existing%%:*}"
      pid="${existing##*:}"
    fi
  fi

  if [[ -z "$pid" ]]; then
    echo "No devserver found for workspace '$workspace'"
    return
  fi

  echo "Stopping devserver for '$workspace' (PID: $pid)..."

  pkill -P "$pid" 2>/dev/null || true
  kill "$pid" 2>/dev/null || true
  pkill -f "$workspace/$APP_SUBDIR.*next" 2>/dev/null || true

  remove_registry_entry "$workspace"
  echo "Stopped."
}

# Stop all devservers
stop_all_devservers() {
  cleanup_registry

  local stopped_any=false

  if [[ -f "$REGISTRY_FILE" ]] && [[ "$(jq 'length' "$REGISTRY_FILE")" != "0" ]]; then
    local workspaces=$(jq -r 'keys[]' "$REGISTRY_FILE")
    for ws in $workspaces; do
      stop_devserver "$ws"
      stopped_any=true
    done
  fi

  if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
    local existing=$(find_existing_devserver "$MAIN_WORKSPACE_NAME")
    if [[ -n "$existing" ]]; then
      stop_devserver "$MAIN_WORKSPACE_NAME"
      stopped_any=true
    fi
  fi

  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    local ws=$(basename "$workspace_dir")
    local existing=$(find_existing_devserver "$ws")
    if [[ -n "$existing" ]]; then
      stop_devserver "$ws"
      stopped_any=true
    fi
  done

  if [[ "$stopped_any" != "true" ]]; then
    echo "No devservers running."
  fi
}

# List all running devservers
list_devservers() {
  cleanup_registry

  local inngest_lock_ws=""
  if is_inngest_lock_valid; then
    inngest_lock_ws=$(get_inngest_lock_workspace)
  fi

  echo "Inngest:"
  if is_inngest_running; then
    if [[ -n "$inngest_lock_ws" ]]; then
      local lock_ws_root=$(get_workspace_root "$inngest_lock_ws")
      local lock_branch=$(git -C "$lock_ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$inngest_lock_ws")
      echo "  ✅ Running on port $INNGEST_PORT → $lock_branch ($inngest_lock_ws)"
    else
      echo "  ⚠️  Running on port $INNGEST_PORT (no lock - may route unpredictably)"
    fi
  else
    echo "  ❌ Not running. Start with: mydev inngest [workspace]"
  fi
  echo ""

  echo "Devservers:"
  echo ""
  printf "%-30s %-20s %-6s %-10s\n" "BRANCH" "(WORKSPACE)" "PORT" "INNGEST"
  printf "%-30s %-20s %-6s %-10s\n" "------" "-----------" "----" "-------"

  local found_any=false

  if [[ -f "$REGISTRY_FILE" ]] && [[ "$(jq 'length' "$REGISTRY_FILE")" != "0" ]]; then
    jq -r 'to_entries[] | "\(.key)\t\(.value.port)\t\(.value.pid)"' "$REGISTRY_FILE" | \
      while IFS=$'\t' read -r ws port pid; do
        local ws_root=$(get_workspace_root "$ws")
        local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
        branch="${branch:0:30}"
        local inngest_marker=""
        if [[ "$ws" == "$inngest_lock_ws" ]]; then
          inngest_marker="← connected"
        fi
        printf "%-30s %-20s %-6s %-10s\n" "$branch" "($ws)" "$port" "$inngest_marker"
      done
    found_any=true
  fi

  if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
    if [[ ! -f "$REGISTRY_FILE" ]] || ! jq -e ".[\"$MAIN_WORKSPACE_NAME\"]" "$REGISTRY_FILE" >/dev/null 2>&1; then
      local existing=$(find_existing_devserver "$MAIN_WORKSPACE_NAME")
      if [[ -n "$existing" ]]; then
        local port="${existing%%:*}"
        local branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
        branch="${branch:0:30}"
        local inngest_marker=""
        if [[ "$MAIN_WORKSPACE_NAME" == "$inngest_lock_ws" ]]; then
          inngest_marker="← connected"
        fi
        printf "%-30s %-20s %-6s %-10s\n" "$branch" "($MAIN_WORKSPACE_NAME)" "$port" "$inngest_marker"
        found_any=true
      fi
    fi
  fi

  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    local ws=$(basename "$workspace_dir")
    if [[ -f "$REGISTRY_FILE" ]] && jq -e ".[\"$ws\"]" "$REGISTRY_FILE" >/dev/null 2>&1; then
      continue
    fi
    local existing=$(find_existing_devserver "$ws")
    if [[ -n "$existing" ]]; then
      local port="${existing%%:*}"
      local branch=$(git -C "$workspace_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
      branch="${branch:0:30}"
      local inngest_marker=""
      if [[ "$ws" == "$inngest_lock_ws" ]]; then
        inngest_marker="← connected"
      fi
      printf "%-30s %-20s %-6s %-10s\n" "$branch" "($ws)" "$port" "$inngest_marker"
      found_any=true
    fi
  done

  if [[ "$found_any" != "true" ]]; then
    echo "(none)"
  fi
}

# Commands
cmd_start() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    if [[ -n "$input" ]]; then
      echo "Error: No workspace or branch found matching '$input'" >&2
    else
      echo "Error: No workspace specified and could not detect from current directory" >&2
    fi
    exit 1
  fi

  if is_workspace_running "$workspace"; then
    local port=$(get_workspace_port "$workspace")
    echo "Devserver for '$workspace' is already running on port $port" >&2
    echo "$port"
    exit 0
  fi

  local existing=$(find_existing_devserver "$workspace")
  if [[ -n "$existing" ]]; then
    local port="${existing%%:*}"
    local pid="${existing##*:}"
    echo "Found existing devserver for '$workspace' on port $port (PID: $pid)" >&2
    save_registry_entry "$workspace" "$port" "$pid"
    echo "$port"
    exit 0
  fi

  start_devserver "$workspace" --no-browser
}

cmd_open() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    cmd_start "$input"
    return
  fi

  if is_workspace_running "$workspace"; then
    local port=$(get_workspace_port "$workspace")
    echo "Devserver for '$workspace' is already running on port $port"
    open "http://localhost:$port"
    exit 0
  fi

  local existing=$(find_existing_devserver "$workspace")
  if [[ -n "$existing" ]]; then
    local port="${existing%%:*}"
    local pid="${existing##*:}"
    echo "Found existing devserver for '$workspace' on port $port (PID: $pid)"
    save_registry_entry "$workspace" "$port" "$pid"
    open "http://localhost:$port"
    exit 0
  fi

  start_devserver "$workspace"
}

cmd_dir() {
  local workspace=$(get_workspace "${1:-}")

  if [[ -z "$workspace" ]]; then
    echo "Error: No workspace specified and could not detect from current directory" >&2
    exit 1
  fi

  local ws_root=$(get_workspace_root "$workspace")

  if [[ ! -d "$ws_root" ]]; then
    echo "Error: Workspace root not found at $ws_root" >&2
    exit 1
  fi

  echo "$ws_root"
}

cmd_edit() {
  local workspace=$(get_workspace "${1:-}")

  if [[ -z "$workspace" ]]; then
    echo "Error: No workspace specified and could not detect from current directory" >&2
    exit 1
  fi

  local ws_root=$(get_workspace_root "$workspace")

  if [[ ! -d "$ws_root" ]]; then
    echo "Error: Workspace root not found at $ws_root" >&2
    exit 1
  fi

  local editor="${VISUAL:-$EDITOR}"

  if [[ -z "$editor" ]]; then
    echo "Error: No editor configured. Set \$VISUAL or \$EDITOR." >&2
    exit 1
  fi

  exec "$editor" "$ws_root"
}

cmd_inngest_open() {
  if ! is_inngest_running; then
    echo "Error: Inngest is not running" >&2
    echo "Start it with: mydev inngest" >&2
    exit 1
  fi

  local url="http://localhost:$INNGEST_PORT"
  echo "Opening Inngest dashboard: $url"
  open "$url"
}

cmd_inngest() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    if [[ -n "$input" ]]; then
      echo "Error: No workspace or branch found matching '$input'" >&2
    else
      echo "Error: No workspace specified and could not detect from current directory" >&2
    fi
    echo "" >&2
    echo "Usage: mydev inngest [workspace]" >&2
    exit 1
  fi

  validate_workspace "$workspace" || exit 1

  local ws_root=$(get_workspace_root "$workspace")
  local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$workspace")

  if is_inngest_lock_valid; then
    local current_lock=$(get_inngest_lock_workspace)
    if [[ "$current_lock" == "$workspace" ]]; then
      echo "Inngest is already running for '$workspace'" >&2
      echo "Dashboard: http://localhost:$INNGEST_PORT" >&2
      exit 0
    else
      local current_ws_root=$(get_workspace_root "$current_lock")
      local current_branch=$(git -C "$current_ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$current_lock")
      echo "Switching Inngest from '$current_branch' to '$branch'..."
      stop_inngest_and_wait
    fi
  elif is_inngest_running; then
    echo "Taking over orphaned Inngest server..."
    stop_inngest_and_wait
  fi

  local app_port=""

  if is_workspace_running "$workspace"; then
    app_port=$(get_workspace_port "$workspace")
  else
    local existing=$(find_existing_devserver "$workspace")
    if [[ -n "$existing" ]]; then
      app_port="${existing%%:*}"
    fi
  fi

  if [[ -z "$app_port" ]]; then
    echo "Error: Devserver for '$workspace' is not running" >&2
    echo "" >&2
    echo "Start it first with: mydev $workspace" >&2
    exit 1
  fi

  set_inngest_lock "$workspace"

  trap 'clear_inngest_lock' EXIT INT TERM

  local app_url="http://localhost:$app_port/api/inngest"

  echo "🚀 Inngest dev server on port $INNGEST_PORT"
  echo "   Dashboard: http://localhost:$INNGEST_PORT"
  echo "   Connected to: $branch (port $app_port)"
  echo ""

  npx inngest-cli@latest dev --port "$INNGEST_PORT" --no-discovery -u "$app_url"
}

show_help() {
  cat << 'EOF'
mydev - Manage devservers across workspaces

USAGE:
  mydev [command] [workspace|branch]

COMMANDS:
  open [workspace]       Start devserver and open browser (default if no command given)
  start [workspace]      Start devserver if not running (no browser, idempotent)
  cd [workspace]         cd to workspace/repo root
  edit [workspace]       Open workspace in editor ($VISUAL or $EDITOR)
  list                   List all running devservers and Inngest status
  inngest [workspace]    Start Inngest dev server for a specific workspace
  inngest-open           Open Inngest dashboard in browser
  stop [workspace]       Stop devserver for workspace
  stop-all               Stop all devservers
  help                   Show this help

WORKSPACE ARGUMENT:
  Can be specified as:
  - Workspace name: "feature-1", "bugfix-2", etc.
  - Branch name: "add-user-auth" - finds matching workspace
  - Omitted: detects from current directory or git branch

INNGEST:
  The Inngest dev server connects to ONE app instance at a time.
  This prevents confusion when multiple instances are running.

  Workflow:
    1. Start your app: mydev my-branch
    2. Connect Inngest: mydev inngest my-branch
    3. Switch Inngest:  mydev inngest other-branch

  Dashboard: http://localhost:8288

EXAMPLES:
  mydev feature-1              # Start/open devserver for feature-1 workspace
  mydev start feature-1        # Start devserver (no browser)
  mydev my-feature-branch      # Start/open by branch name
  mydev                        # Auto-detect from current dir/branch
  mydev list                   # Show all running servers and Inngest status
  mydev inngest feature-1      # Connect Inngest to feature-1's app
  mydev inngest-open           # Open Inngest dashboard
  mydev stop feature-1         # Stop feature-1's server
  mydev cd feature-1           # cd to feature-1 workspace root
  mydev edit feature-1         # Open feature-1 in $VISUAL or $EDITOR
EOF
}

main() {
  init_registry
  cleanup_registry

  local cmd="${1:-}"

  case "$cmd" in
    list|ls|-l|--list)
      list_devservers
      ;;
    stop)
      shift
      local workspace=$(get_workspace "${1:-}")
      if [[ -z "$workspace" ]]; then
        echo "Error: No workspace specified and could not detect from current directory"
        exit 1
      fi
      stop_devserver "$workspace"
      ;;
    stop-all|--stop-all)
      stop_all_devservers
      ;;
    cd|dir|path)
      shift
      cmd_dir "$@"
      ;;
    edit)
      shift
      cmd_edit "$@"
      ;;
    inngest)
      shift
      cmd_inngest "$@"
      ;;
    inngest-open)
      shift
      cmd_inngest_open "$@"
      ;;
    open)
      shift
      cmd_open "$@"
      ;;
    start)
      shift
      cmd_start "$@"
      ;;
    help|--help|-h)
      show_help
      ;;
    *)
      cmd_open "$cmd"
      ;;
  esac
}

main "$@"

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.