BBasilic
Development

Dev Environments

Portless named .localhost URLs, local vs remote VPC development, and Expo.

Strategy for supporting all IDEs, local PC development, and remote VPC development across the basilic monorepo.

Local HTTP apps use Vercel Labs Portless: stable https://*.localhost names, HTTPS with a local CA, ephemeral bind ports. Humans and agents should use the names, not ports.

Quick Reference

ContextWeb + APIMobile (Expo)
Localpnpm setup then pnpm devpnpm --filter @repo/mobile start
Remote + CursorForward 443; pnpm devpnpm --filter @repo/mobile start:localhost
Remote + other IDEForward 443; run pnpm devForward 8081 + start:localhost, or use start:tunnel
Physical device (remote Metro)—Use start:tunnel (port forwarding does not help devices)
Bypass Portlesspnpm --filter <pkg> dev:app or PORTLESS=0Metro is not wrapped

Overview

The monorepo must work consistently whether you develop locally (macOS, Linux, Windows) or on a remote VPC. This document defines port requirements, app-specific behavior, and environment-agnostic patterns so any IDE (Cursor, VS Code, Neovim, JetBrains, etc.) can be used.

Core Principles

  1. IDE-agnostic – No assumptions about Cursor, VS Code Remote, or other tools. Docs and scripts work for any setup.
  2. Named local URLs – Application traffic uses Portless .localhost hosts. Bind ports are assigned in 4000–4999 and are not part of the public contract.
  3. Environment detection – Commands and env vars support both local and remote contexts; docs explain both paths.

Canonical local URLs

URLAppNotes
https://basilic.localhostNext.js webLogin test@test.ai
https://api.basilic.localhostFastify APIGET /health, /reference
https://docu.basilic.localhostFumadocsThis repo only
https://email.basilic.localhostReact Email preview
https://agents.basilic.localhosteve workspace/eve/command and /eve/chat (GET …/v1/health)

Linked git worktrees prefix the branch: https://fix-ui.api.basilic.localhost. scripts/dev.mjs exports sibling URLs from portless get so worktrees do not call the main checkout.

pnpm dev starts the Portless HTTPS proxy on 443 before Turbo when it is down, so sudo is prompted once in your terminal. It does not touch the Keychain (--skip-trust). Parallel app tasks no longer each try to bind 443 inside the TUI. pnpm setup / pnpm setup:portless trusts the local CA and starts that proxy (may prompt for sudo and a Keychain confirmation; re-run is safe; CI skips it). After reboot, the next pnpm dev, pnpm exec portless proxy start, or pnpm exec portless service install brings :443 back. portless get may print :1355 if the HTTPS proxy was never started; Basilic env still uses https://<name>.localhost (port 443).

Safari: .localhost may need pnpm exec portless hosts sync. Diagnose with pnpm exec portless doctor and pnpm exec portless list.

Still numeric (not Portless)

PortService
54322Local Supabase Postgres
8081 / 19000 / 19001Expo Metro / DevTools
3000 / 3001Playwright and CI spawned web/API (no Portless, no eve)
3104 / 3105Local eve command/chat loopback behind the agents gateway
11434Optional Ollama

Direct-run debugging: pnpm --filter @repo/web dev:app (and sibling dev:app scripts). PORTLESS=0 pnpm --filter @repo/web dev bypasses the proxy. Node processes outside Portless need NODE_EXTRA_CA_CERTS=~/.portless/ca.pem to fetch the named HTTPS URLs.

Google/Apple OAuth may reject .localhost redirect URIs. Local callbacks are still documented as https://basilic.localhost/auth/callback/oauth/.... Use the provider's localhost exception or a custom TLD if they refuse.

Local PC Development

Standard workflow: run everything on the same machine where the IDE and simulators live.

Web + API + agents

pnpm dev

Ensures local Postgres is up, then starts Turborepo's TUI (same left-pane logs as pnpm lint:eslint / pnpm test): Fastify, Next.js, eve workspace (/eve/command and /eve/chat) in this repo, and dependent package watchers. Prints the named .localhost URLs, then the TUI. Mobile stays a separate command. Generated projects omit agents.

Mobile (Expo)

pnpm --filter @repo/mobile start
# or: cd apps/mobile && pnpm start
  • Default – Expo uses LAN; simulator/device connects via your machine's IP.
  • iOS Simulator – Requires macOS + Xcode; runs locally.
  • Android Emulator – Runs locally (Linux/macOS/Windows).

Remote VPC Development

When the repo and dev servers run on a remote VPC, the simulator and browser run on your local machine and must reach Metro/API/Next over the network. Note: If the VPC runs Linux, you cannot run the iOS Simulator there (Xcode is macOS-only). Use a local Mac for iOS or rely on Expo Go on a physical device + tunnel.

Port Forwarding (Cursor, VS Code Remote-SSH)

Cursor automatically tunnels ports from remote to local when you connect to a VPC. No setup required—run Expo and the simulator will reach Metro via localhost once forwarded.

If your IDE forwards ports from remote to local:

  1. Forward 443 so https://*.localhost on your laptop hits the remote Portless proxy. Also forward 8081 (and optionally 19000, 19001) for Expo. Trust the remote Portless CA on the client (NODE_EXTRA_CA_CERTS / portless trust), or use PORTLESS=0 / dev:app with raw loopback ports.

  2. Run Expo with --localhost so it advertises localhost:8081 instead of the VPC's LAN IP:

    pnpm --filter @repo/mobile start:localhost
  3. The simulator on your local machine connects to localhost:8081, which is tunneled to the remote Metro server.

Tunnel Mode (No Port Forwarding)

When port forwarding is unavailable or unreliable:

pnpm --filter @repo/mobile start:tunnel

Expo creates a public URL (via ngrok). Simulators and physical devices can connect regardless of network. Expect higher latency. Physical devices (real phones/tablets) cannot use localhost—they run on a different machine. Port forwarding only helps simulators on your dev machine. For physical devices with remote Metro, always use --tunnel.

Network / VPN

If the VPC is reachable from your machine (VPN or shared network):

  • Use --lan (or default). Expo will advertise the VPC's IP.
  • Ensure your local machine can reach the VPC Portless proxy (443) and Metro (8081). Portless is loopback-only unless LAN mode is enabled.

App-Specific Notes

Expo (apps/mobile)

ScenarioCommandSimulator connects via
Local devpnpm --filter @repo/mobile startLAN IP (default)
Remote + port forwardingpnpm --filter @repo/mobile start:localhostlocalhost:8081 (tunneled)
Remote + no forwardingpnpm --filter @repo/mobile start:tunnelPublic ngrok URL
Remote + VPN / same LANpnpm --filter @repo/mobile startVPC IP:8081

Scripts start:localhost and start:tunnel are available in apps/mobile for convenience.

Expo Setup Details

Prerequisites

  • iOS Simulator: macOS with Xcode. Run xcode-select --install if needed. Simulator is in Xcode → Open Developer Tool → Simulator.
  • Android Emulator: Android Studio with an AVD. Create a virtual device in AVD Manager.
  • Expo Go (physical device): Install from App Store / Play Store. Connects via QR code or LAN/tunnel URL.

Expo Go vs development builds

  • Expo Go – Default. No native build needed. pnpm start → scan QR or press i / a to open simulator. Use when the app has no custom native code.
  • Development builds – Required for custom native modules (e.g. expo-dev-client). Build locally with eas build or npx expo run:ios / run:android. Simulator builds run on the machine where the simulator lives (e.g. your Mac for iOS).

Workflow: remote VPC + Cursor

  1. Connect Cursor to the remote VPC (SSH).
  2. Open terminal in Cursor, run: pnpm --filter @repo/mobile start:localhost.
  3. Cursor auto-tunnels port 8081. Press i (iOS) or a (Android) in the Expo CLI.
  4. Simulator launches locally and loads the bundle from localhost:8081 (forwarded to remote Metro).

Hot reload / Fast Refresh works over tunnels and port forwarding, but may feel slightly slower than local due to network latency.

API / env for mobile

  • The app runs in the simulator on your local machine. API requests must reach wherever your backend is.
  • Simulator on the same machine as Portless: EXPO_PUBLIC_API_URL=https://api.basilic.localhost.
  • Physical devices do not resolve .localhost to your computer; use Expo LAN/tunnel and a reachable API URL.
  • If using tunnel for Expo, the API must be reachable from the device—use a public URL or ensure the device is on the same network.

Troubleshooting Expo

IssueFix
"Unable to resolve module" / red boxRun pnpm install at repo root; restart Metro with pnpm start (clear cache: expo start -c).
Simulator can't connect to Metro (remote)Use --localhost when Cursor forwards ports; or --tunnel if not. Ensure 8081 is in the Ports panel (Cursor/VS Code).
"Network response timed out"API unreachable. Check EXPO_PUBLIC_API_URL and that the API host is reachable from the simulator.
iOS Simulator not foundOpen Xcode Simulator first, or run open -a Simulator before pressing i.
Android emulator not foundStart an AVD from Android Studio; ensure adb devices lists it.

Next.js / Fastify

These run where you execute pnpm dev. Browsers use the named .localhost URLs (forward 443 on remote). Local Postgres is Docker via pnpm db:start (Supabase CLI in packages/db).

Scripts (apps/mobile)

ScriptCommandUse case
startexpo startLocal dev, default LAN
start:localhostexpo start --localhostRemote + port forwarding (Cursor, VS Code)
start:tunnelexpo start --tunnelRemote without forwarding, or physical device

IDE Support Matrix

IDEPort forwardingRemote (VPC) workflow
CursorAutomatic – tunnels portsUse --localhost; 8081 auto-fwd
VS CodeRemote-SSH (Ports view)Same as Cursor
NeovimManual / ssh -LUse --tunnel or SSH port fwd
JetBrainsRemote DevForward ports or use --tunnel
OtherVariesPrefer --tunnel for mobile

Common Gotchas

  • Remote + simulator won't connect – Forgot --localhost? Without it, Expo advertises the VPC IP, which your laptop may not reach. Use start:localhost when using port forwarding.
  • Physical device + remote Metro – Port forwarding does not help. The device runs the app; its localhost is the device itself. Use start:tunnel.
  • iOS Simulator on Linux VPC – Impossible. Xcode is macOS-only. Use a local Mac or a physical iPhone + tunnel.
  • API unreachable from app – Simulator: https://api.basilic.localhost. Device: LAN/tunnel URL, not .localhost.

Environment Variables

No special env vars are required to distinguish local vs remote. The same .env files work in both contexts. Ensure:

  • NEXT_PUBLIC_API_URL / NEXT_PUBLIC_APP_URL / EXPO_PUBLIC_API_URL default to https://api.basilic.localhost and https://basilic.localhost. Worktree pnpm dev overrides via portless get.

Checklist: New Developer Setup

  1. Clone repo, run pnpm setup.
  2. Decide: local only, or remote VPC?
  3. If remote with Cursor: ports are auto-tunneled; use pnpm --filter @repo/mobile start:localhost for Expo. With other IDEs: configure port forwarding or use start:tunnel for mobile.
  4. Run pnpm dev (web, API, agents in this repo) and pnpm --filter @repo/mobile start (or start:localhost / start:tunnel for remote) from repo root.
  5. Verify simulator/device can load the app and reach the API.
  • Dev Tooling – pnpm, Biome, ESLint, Turborepo
  • Onboard new developer — playbook /w-onboard when installed under .agents/skills/workflow/w-onboard/ (see Skills)

On this page