Skip to content

Building from Source

Ghost is built from four separate codebases that are combined into a single desktop application. Think of it like building a car — the engine (Go backend), the dashboard (React frontend), the body (Tauri desktop shell), and the accessories (Chrome extension) are each built separately, then assembled into the final product. This page explains how to build each piece and how they fit together.


ToolVersionWhy it’s neededInstall
Go1.25+Compiles the backend — the proxy engine, REST API, WebSocket hub, AI agent, SQLite database, and all business logicgo.dev/dl
Node.js20+Runs the frontend build tools (Vite, TypeScript compiler, Tailwind CSS)nodejs.org
npm10+Installs frontend and extension JavaScript dependenciesIncluded with Node.js
RustLatest stableCompiles the Tauri desktop shell — the native window, system tray, menus, and OS integration layerrustup.rs
Xcode CLI ToolsLatestRequired on macOS for simctl (iOS simulator control), codesign (app signing), and system headersxcode-select --install

Optional tools for security features:

ToolPurpose
Python 3Required for sqlmap and semgrep security scanners
FridaDynamic instrumentation for mobile apps (installed via Ghost’s UI)
ADBAndroid device control (install via Android SDK)

The fastest way to start developing is to run the Go backend and React frontend separately. This gives you hot module replacement (instant UI updates without rebuilding) for the frontend and race detection for the backend.

Terminal 1 — Go backend:

Terminal window
make dev

This runs go run -race -tags dev ./cmd/ghost, which:

  • Starts the proxy on port 4545 and the API on port 5565
  • Enables Go’s race detector (catches concurrent access bugs)
  • Uses -tags dev which replaces the go:embed directive with an empty filesystem — the Go binary doesn’t try to embed or serve the frontend

Terminal 2 — React frontend:

Terminal window
cd frontend && npm run dev

This starts Vite’s development server on port 5173 with:

  • Hot Module Replacement (HMR) — edit a React component and see the change instantly without a page reload
  • TypeScript type checking on save
  • Proxy rules that forward API and WebSocket requests to the Go backend

To run the full desktop app with the Tauri shell:

Terminal window
make desktop-dev

This starts both the Vite dev server and the Tauri Rust shell simultaneously. The Rust shell launches the Go sidecar and navigates the WebView to the Vite dev server, so you get HMR inside the native desktop window.


When developing without Tauri (the two-terminal approach), the Vite dev server proxies API requests to the Go backend so the frontend can communicate with it:

PathTargetProtocol
/api/*http://localhost:9090HTTP
/ws/*ws://localhost:9090WebSocket
/healthhttp://localhost:9090HTTP

Important: The proxy target is hardcoded to port 9090 in frontend/vite.config.ts. The Go backend defaults to port 5565. For the proxy to work, either:

  • Run Go on port 9090: set [api] port = 9090 in ~/.ghost/config.toml
  • Or change the proxy target in vite.config.ts to 5565

When running through Tauri (desktop mode), this proxy is not used — the Tauri shell navigates directly to the Go sidecar’s API port.


The Go backend compiles into a single binary that contains everything: the HTTP proxy, REST API, WebSocket hub, SQLite database, AI agent, addon engine, and (in production) the entire React frontend embedded as static files.

Terminal window
# Development — hot-reload friendly, no frontend embedding
make dev # go run -race -tags dev ./cmd/ghost
# Production — standalone binary with embedded frontend
make build # Output: dist/ghost
# Sidecar binaries — for embedding inside the Tauri desktop app
make sidecar-darwin-arm64 # Output: frontend/src-tauri/binaries/ghost-engine-aarch64-apple-darwin
make sidecar-darwin-amd64 # Output: frontend/src-tauri/binaries/ghost-engine-x86_64-apple-darwin
make sidecar-windows # Output: frontend/src-tauri/binaries/ghost-engine-x86_64-pc-windows-msvc.exe
# Dev sidecars — same filenames but with -tags dev (no frontend embedding)
make sidecar-dev-darwin-arm64
make sidecar-dev-darwin-amd64
make sidecar-dev-windows

Production vs. dev builds: The key difference is the -tags dev flag. In production, the Go binary uses go:embed to bundle the entire frontend/dist/ directory inside itself — the binary becomes a self-contained web server that serves the React frontend from memory. In dev mode, this embedding is replaced with an empty filesystem, so the Go binary doesn’t need the frontend to be built at all. The frontend is served separately by Vite’s dev server.

Sidecar naming: The filenames follow Tauri’s naming convention (ghost-engine-{rust-target-triple}). Tauri looks for these specific filenames in frontend/src-tauri/binaries/ when building the desktop app.

Terminal window
cd frontend
npm install # Install all dependencies from package.json
npm run dev # Start Vite dev server on port 5173 with HMR
npm run build # TypeScript type-check (tsc -b) then Vite production build
npm run lint # ESLint across all source files
npm run preview # Serve the production build locally for testing

Build output: frontend/dist/ — contains minified HTML, CSS, JavaScript, and font files. Source maps are generated as “hidden” (exist for Sentry stack traces but are not served to the browser).

Key dependencies:

  • React 19 with TypeScript ~5.9
  • Vite 7 for building and dev server
  • Tailwind CSS v4 (via @tailwindcss/vite plugin)
  • Zustand 5 for state management
  • Monaco Editor for code editing (addons, Frida scripts)
  • @tanstack/react-virtual for virtualized lists (flow list, element trees)
  • react-resizable-panels for the split-pane layout
Terminal window
cd frontend
npx tauri dev # Development mode (Vite dev server + Tauri shell)
npx tauri build # Production build (DMG installer + updater bundle)

The Tauri configuration lives in frontend/src-tauri/tauri.conf.json and controls window settings, bundle targets, security policies, and the updater configuration.

What tauri build produces:

  • macOS: A .dmg installer and a .app.tar.gz + .sig (compressed app bundle with EdDSA signature for the auto-updater)
  • Windows: An NSIS installer that installs per-user (no admin required)
Terminal window
cd extension
npm install
npm run build # Build service worker + content script
npm run dev # Watch mode for development
npm run typecheck # TypeScript check without building

Build process: The extension uses two separate Vite configurations:

  1. Main build (vite.config.ts): Compiles the service worker and popup as ES modules
  2. Content script build (vite.content.config.ts): Compiles the content script as an IIFE (Immediately Invoked Function Expression) because Chrome content scripts cannot use ES module import statements

The content script build uses emptyOutDir: false to avoid erasing the main build output. Both builds target ES2022 and produce minified output in the extension/dist/ directory.

Loading in Chrome for development:

  1. Build the extension: cd extension && npm run build
  2. Open Chrome → chrome://extensions
  3. Enable “Developer mode”
  4. Click “Load unpacked” and select the extension/dist/ folder

Go makes cross-compilation straightforward. Since Ghost’s Go code is pure Go (no CGo dependencies), you can build for any supported platform from any machine:

Terminal window
# macOS Apple Silicon (from any OS)
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine ./cmd/ghost
# macOS Intel (from any OS)
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine ./cmd/ghost
# Windows x64 (from any OS)
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o ghost-engine.exe ./cmd/ghost

CGO_ENABLED=0 ensures no C compiler is needed and the resulting binary has zero external dependencies. The CI pipeline sets this explicitly, though the Makefile relies on Go’s default behavior (which also works since there are no CGo imports).

Terminal window
make desktop-macos-universal

This builds both ARM64 and x64 sidecar binaries, then merges them with Apple’s lipo tool into a single “universal binary” that runs natively on both Apple Silicon and Intel Macs. The resulting binary is roughly double the size but eliminates the need for users to pick the right download.


FlagUsed inPurpose
-tags devmake dev, dev sidecarsReplaces go:embed with an empty filesystem, so the Go binary doesn’t need the frontend built. Makes Go compilation faster during development.
-racemake dev, make testEnables Go’s race detector, which catches concurrent access bugs at runtime. Adds ~10x overhead, so only used in development and testing.
-ldflags="-s -w"All production builds-s strips the symbol table, -w strips DWARF debug information. Together they reduce binary size by approximately 30%.
CGO_ENABLED=0CI pipeline, cross-compilationForces pure Go compilation with no C dependencies. Required for cross-compilation (can’t use a macOS C compiler to build for Windows).

Terminal window
# Run all Go tests with race detection and coverage
make test # go test -race -cover -count=1 ./...
# Verbose test output (shows each test name)
make test-v # go test -race -cover -count=1 -v ./...
# Skip long-running tests (integration tests)
make test-short # go test -race -short -count=1 ./...
# Generate HTML coverage report
make cover # Runs tests, then opens coverage.html
# Static analysis
make vet # go vet ./...
make lint # golangci-lint run ./...
# Code formatting
make fmt # gofmt -s -w + goimports -w

The -count=1 flag disables test caching, ensuring tests always run fresh. This is important because Ghost’s tests interact with SQLite databases and network ports that may have different state between runs.


Terminal window
# Remove all build artifacts (dist/, frontend/dist/, coverage files, sidecar binaries)
make clean
# Emergency cleanup — kill everything Ghost-related
make nuke

make nuke is the nuclear option for when things are stuck. It:

  1. Kills all ghost-engine, ghost-desktop, and Cargo/Tauri build processes
  2. Kills any WebDriverAgent (xcodebuild.*wda) builds
  3. Disables the system proxy on macOS (via networksetup)
  4. Removes the SQLite database (~/.ghost/ghost.db)
  5. Removes all build artifacts
  6. Checks that ports 4545, 5565, and 1420 are free

Use this when Ghost didn’t shut down cleanly and leftover processes are holding ports or the system proxy is stuck pointing at a dead Ghost instance.


When you run make desktop (or make desktop-macos-arm64), the build process is:

  1. Go sidecar is compiled and placed in frontend/src-tauri/binaries/ with the correct target-triple filename
  2. Frontend is built by Vite into frontend/dist/ — the Go sidecar’s production embed picks this up
  3. Tauri compiles the Rust shell, bundles the Go sidecar as an external binary, and packages everything into a DMG/NSIS installer

At runtime:

  1. The user launches Ghost (double-click the app)
  2. The Tauri Rust shell starts and spawns the Go sidecar with --sidecar flag
  3. The Go sidecar picks ephemeral ports, starts the proxy and API, and prints {"api_port": N, "proxy_port": N, "token": "..."} to stdout
  4. The Tauri shell reads this JSON and navigates the WebView to http://localhost:{api_port}
  5. The React frontend loads from the Go binary’s embedded filesystem and connects to the API and WebSocket