Skip to content

Start Web Server Command

Overview

The symphony web command starts the Symphony web server in isolation—either in development mode (Nuxt dev server) or production mode (built Nitro server). This is a granular command complementing the comprehensive symphony start command, useful for development workflows where you want to start only the web UI without the orchestrator.

Key Purpose: Provide fast web server startup for development iteration and production deployment scenarios.

Entry Point: cli/commands/web.ts

Shared Launch Helper: cli/utils/webServer.ts

CLI Usage:

bash
symphony web [--port <number>]

Modes:

  • Development: npx nuxt dev --port <port> (hot reload, HMR)
  • Production: node .output/server/index.mjs with PORT/HOST env vars (prebuilt, optimized)

How It Works

1. Initialization Phase

Port Resolution:

  • Default: 3000
  • Override via --port <number> flag
  • Passed to process spawn as command argument or env var

NODE_ENV Determination:

  • Dev mode: process.env.NODE_ENV !== 'production'
  • Prod mode: NODE_ENV === 'production'

PID Management:

  • Writes CLI process PID to .symphony/symphony.pid
  • Removed on graceful shutdown
  • Note: Unlike start.ts, does NOT check if Symphony already running (no double-start prevention)

2. Process Spawning Phase (Dev Mode)

bash
npx nuxt dev --port 3000

Command Structure:

  • command: 'npx'
  • args: ['nuxt', 'dev', '--port', '3000']
  • cwd: Symphony project root
  • readyPattern: /Local:\s+http/ (Nuxt dev server indicates ready)

Nuxt Dev Server Output:

[web] ➜  Local:   http://localhost:3000/

ProcessManager watches stdout, matches pattern, marks process as ready: true.

3. Process Spawning Phase (Production Mode)

bash
node .output/server/index.mjs

Command Structure:

  • command: 'node'
  • args: ['/absolute/path/to/.output/server/index.mjs']
  • cwd: Symphony project root
  • env: { PORT: '3000', HOST: '0.0.0.0' }
  • readyPattern: /Listening/ (custom Nitro startup message)

Nitro Production Server:

  • Built by nuxt build.output/ directory
  • Listens on PORT (via env var) and HOST (0.0.0.0 = all interfaces)
  • Emits startup message including "Listening"

4. Output & Logging Phase

ProcessManager pipes both stdout and stderr with [web] prefix:

[web] ➜  Local:   http://localhost:3000/
[web] [nitro] Built in 1234ms
[web] [error] Something failed

Key Components

Files & Responsibilities

FilePurpose
cli/commands/web.tsEntry point; mode detection, shutdown coordination, delegates web spawn contract to shared helper
cli/utils/webServer.tsShared web launch helper used by web.ts and start.ts; owns dev/prod command, args, env merge, and ready patterns
cli/utils/processManager.tsProcess lifecycle (spawn, stdout/stderr piping, readiness detection, graceful shutdown)
cli/utils/pid.tsPID file read/write/cleanup and liveness checking
cli/utils/output.tsTerminal formatting (heading, success, error, dim helpers)
cli/index.tsCLI router; dispatches 'web' case to startWeb()
nuxt.config.tsNuxt configuration (SSR disabled, Nitro settings, file watch ignores)

Process Lifecycle


Design Decisions

1. Granular vs Comprehensive Command (HIGH IMPACT)

Decision: Offer both symphony web (granular) and symphony start (comprehensive).

Rationale:

  • Granular: Fast iteration in development (no DB init, project detection overhead)
  • Comprehensive: Full initialization for production or clean startup

Trade-off: Requires two similar implementations, but enables flexible workflows.

2. Dev vs Production Mode Separation

Decision: Different commands (nuxt dev vs node .output/server/index.mjs) based on NODE_ENV.

Rationale:

  • Dev mode supports hot reload, faster iteration
  • Prod mode is prebuilt, zero startup latency

Trade-off: Requires Nuxt build as prerequisite for production. Could auto-build if missing, but keeps it explicit (fail-fast).

3. Readiness Detection via Regex Patterns

Decision: Each mode emits a distinct readiness signal; ProcessManager watches stdout for pattern match.

Rationale:

  • Non-blocking: waits for server to be truly ready before returning control
  • Avoids port-probing heuristics (fragile with proxies)
  • Clear separation of concerns (ProcessManager doesn't know server internals)

Trade-off: Regex patterns can drift if Nuxt/Nitro output changes. Requires maintenance on version upgrades.

4. Single-Process Model (vs start dual-process)

Decision: Spawn only the web server, not the orchestrator.

Rationale:

  • Simpler mental model for development workflows
  • Lower resource overhead
  • Allows independent orchestrator startup (useful for debugging)

Trade-off: Orchestrator must be started separately; users need to understand two-process model.

5. No Double-Start Check (unlike start.ts)

Decision: startWeb() does NOT check if Symphony already running (no PID liveness check).

Rationale:

  • Allows starting web server alongside separate orchestrator process
  • Simpler code (no process detection)
  • Port collision detected naturally by OS (EADDRINUSE)

Trade-off: User error (running two web servers) results in port error instead of user-friendly message.

6. Graceful Shutdown: SIGTERM → SIGKILL

Decision: Send SIGTERM, wait 10 seconds, then force SIGKILL if needed.

Rationale:

  • Allows process to clean up resources (flush logs, close connections)
  • Prevents orphaned child processes
  • SIGKILL as fallback guarantees cleanup

Trade-off: 10s delay on shutdown. Could be user-configurable if shutdown speed becomes critical.

7. PID File Even for Granular Command

Decision: startWeb() writes PID file like startAll() does.

Rationale:

  • Consistency: both CLI entry points write the same PID file
  • Allows symphony status to detect active web process

Trade-off: PID file represents web process, not orchestrator (confusing if both running). Could use separate pid files per process.

8. Environment Variable Pass-Through in Production

Decision: Production mode spawns Nitro with { PORT, HOST } env vars from options.

Rationale:

  • Allows port/host override without rebuilding
  • Production deployments often require env-var configuration

Trade-off: Dev mode (nuxt dev) hardcodes port in args (no env var fallback). Asymmetry.


Workflow Examples

Development Iteration

bash
# Terminal 1: Start web server (hot reload enabled)
$ symphony web --port 3000

# Terminal 2: Start orchestrator separately for testing
$ symphony orchestrator

# Modify app code → browser auto-refreshes

Production Deployment

bash
# Build production bundle
$ npm run build

# Start production server (prebuilt, zero startup latency)
$ NODE_ENV=production symphony web --port 8080

Debugging

bash
# Start just web server, keep orchestrator stopped
$ symphony web

# Make requests, test API endpoints, verify Nitro behavior

Known Gaps & Future Work

1. Port Collision Detection

Currently: OS-level EADDRINUSE error. Better: Check port availability before spawning, provide user-friendly message.

2. Readiness Pattern Drift

Risk: Nuxt/Nitro versions change output format, readyPattern no longer matches. Mitigation: Version-specific pattern override in config, or timeout-based fallback.

3. PID File Conflict with startAll()

If user runs:

bash
symphony web &
symphony start  # Checks PID, finds web process, thinks Symphony running

Result: Confusing error message.

4. Production Build Prerequisite

symphony web in production mode requires .output/server/index.mjs to exist. If missing: Cryptic "ENOENT" error. Better: Check for .output/, suggest npm run build with clear message.

5. No Process Validation on Startup

Dev mode assumes Nuxt is installed (npx nuxt). Prod mode assumes build output exists. No pre-checks; failures occur during spawn.

6. Asymmetric Port Handling

  • Dev: --port passed as arg (['nuxt', 'dev', '--port', '3000'])
  • Prod: --port passed as env var ({ PORT: '3000' })

These should be consistent.

7. No Reverse Proxy / Routing Wrapper

Currently: Direct Nitro server on port. Future: Optional reverse proxy (nginx/Caddy) for:

  • SSL termination
  • Request logging
  • Route rewriting (useful in mono-repo setups)

8. Lack of Health Check Endpoint

ProcessManager detects readiness via regex; no structured health checks. Better: Call GET /api/health{ status: 'ok' } instead of pattern matching.


  • Start Command (docs/cli/commands/start-command.md) — Comprehensive startup with DB/project init
  • Process Manager — Used by both start and web commands
  • Orchestrator Command (docs/cli/commands/orchestrator-command.md) — Separate process for agent dispatch
  • Nuxt Configuration — SPA mode, Nitro settings, file watch ignores
  • Development Setup (docs/development-setup.md) — Project structure, local development workflows