diff --git a/AGENTS.md b/AGENTS.md
index ac941b6..5236f89 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,6 +35,12 @@ dograh/
./scripts/stop_services.sh
```
+On Windows (PowerShell):
+```powershell
+.\scripts\start_services_dev.ps1
+.\scripts\stop_services.ps1
+```
+
## Environment Configuration
- `api/.env` - Backend environment variables
diff --git a/docs/contribution/setup.mdx b/docs/contribution/setup.mdx
index 1182ddc..7054883 100644
--- a/docs/contribution/setup.mdx
+++ b/docs/contribution/setup.mdx
@@ -8,10 +8,14 @@ If the below steps do not work out for you, it would be great if you can open an
### System Requirements
- git to clone the forked repository
-- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) to manage your node versions locally)
+- Node.js 24 to run the UI (we recommend using [NVM](https://github.com/nvm-sh/nvm) on macOS/Linux or [NVM for Windows](https://github.com/coreybutler/nvm-windows) on Windows to manage your node versions locally)
- Python 3.13 to run the backend
- Docker to run the database and redis cache locally
+
+All commands below are shown for **macOS / Linux**. Expand the **Windows** tab for the PowerShell equivalent where it differs.
+
+
### Steps
1. Fork the Dograh repository by going to https://github.com/dograh-hq/dograh
2. Clone the forked repository on your machine
@@ -20,10 +24,16 @@ git clone https://github.com//dograh
cd dograh
```
3. Create a python virtual environment
-```
+
+```bash macOS/Linux
python3 -m venv venv
source venv/bin/activate
```
+```powershell Windows
+python -m venv venv
+.\venv\Scripts\Activate.ps1
+```
+
4. Install the requirements
```
pip install -r api/requirements.txt
@@ -50,25 +60,51 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS
a57e3e92b02c minio/minio "/usr/bin/docker-ent…" 18 seconds ago Up 18 seconds (healthy) 127.0.0.1:9000-9001->9000-9001/tcp dograh-minio-1
```
8. Setup environment variables
-``
+
+```bash macOS/Linux
cp api/.env.example api/.env && cp ui/.env.example ui/.env
-``
-9. Setup pipecat git submodule
```
+```powershell Windows
+Copy-Item api/.env.example api/.env
+Copy-Item ui/.env.example ui/.env
+```
+
+9. Setup pipecat git submodule
+
+```bash macOS/Linux
bash scripts/setup_pipecat.sh
```
-10. Start backend services
+```powershell Windows
+.\scripts\setup_pipecat.ps1
```
+
+10. Start backend services
+
+```bash macOS/Linux
bash scripts/start_services_dev.sh
```
+```powershell Windows
+.\scripts\start_services_dev.ps1
+```
+
Verify that your backend server is running
+
+```bash macOS/Linux
+curl -X GET localhost:8000/api/v1/health
```
-curl -X GET localhost:8000/api/v1/health
+```powershell Windows
+curl.exe http://localhost:8000/api/v1/health
```
+
You would be able to see the logs in logs/ directory.
-```
+
+```bash macOS/Linux
tail -f logs/latest/*.log
```
+```powershell Windows
+Get-Content logs/latest/*.log -Wait
+```
+
11. Start the UI
```
cd ui && npm run dev
diff --git a/docs/getting-started/troubleshooting.mdx b/docs/getting-started/troubleshooting.mdx
index 7c0fcf1..13d064d 100644
--- a/docs/getting-started/troubleshooting.mdx
+++ b/docs/getting-started/troubleshooting.mdx
@@ -9,11 +9,17 @@ description: "Common issues and solutions for running Dograh AI"
### When a port is already in use:
##### Check what's using the port first and then kill the process (may require sudo on Linux)
-```bash
+
+```bash macOS/Linux
lsof -i :3010
-
kill -9 $(lsof -t -i :3010)
```
+```powershell Windows
+netstat -ano | findstr :3010
+# Find the PID in the last column, then:
+Stop-Process -Id -Force
+```
+
### When Docker containers are using the ports (with auto-restart enabled):
diff --git a/scripts/makemigrate.ps1 b/scripts/makemigrate.ps1
new file mode 100644
index 0000000..35685d5
--- /dev/null
+++ b/scripts/makemigrate.ps1
@@ -0,0 +1,58 @@
+#!/usr/bin/env pwsh
+# Create a new Alembic migration with autogenerate (Windows)
+
+Param(
+ [string]$MigrationName,
+ [switch]$DryRun
+)
+
+$ErrorActionPreference = 'Stop'
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BaseDir = Split-Path -Parent $ScriptDir
+Set-Location $BaseDir
+
+# Ensure repository root is importable for Alembic env/module resolution.
+if ($env:PYTHONPATH) {
+ $env:PYTHONPATH = "$BaseDir;$($env:PYTHONPATH)"
+} else {
+ $env:PYTHONPATH = $BaseDir
+}
+
+$EnvFile = Join-Path $BaseDir 'api/.env'
+
+# Load environment variables
+if (Test-Path $EnvFile) {
+ Get-Content $EnvFile | ForEach-Object {
+ $line = $_.Trim()
+ if ($line -and -not $line.StartsWith('#')) {
+ $parts = $line -split '=', 2
+ if ($parts.Count -eq 2) {
+ [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process')
+ }
+ }
+ }
+} else {
+ Write-Host "Error: Environment file $EnvFile not found." -ForegroundColor Red
+ exit 1
+}
+
+# Prompt for migration name when not provided via parameter
+if (-not $MigrationName) {
+ $MigrationName = Read-Host "Enter the migration name (minimum 5 characters)"
+}
+
+if (-not $MigrationName -or $MigrationName.Length -lt 5) {
+ Write-Host "Error: Migration name must be at least 5 characters long." -ForegroundColor Red
+ exit 1
+}
+
+# Generate the Alembic revision
+$cmd = "alembic -c api/alembic.ini revision --autogenerate -m `"$MigrationName`""
+
+if ($DryRun) {
+ Write-Host "Dry run: $cmd"
+ exit 0
+}
+
+alembic -c api/alembic.ini revision --autogenerate -m $MigrationName
diff --git a/scripts/migrate.ps1 b/scripts/migrate.ps1
new file mode 100644
index 0000000..9147147
--- /dev/null
+++ b/scripts/migrate.ps1
@@ -0,0 +1,36 @@
+#!/usr/bin/env pwsh
+# Run Alembic database migrations (Windows)
+
+$ErrorActionPreference = 'Stop'
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BaseDir = Split-Path -Parent $ScriptDir
+Set-Location $BaseDir
+
+# Ensure repository root is importable for Alembic env/module resolution.
+if ($env:PYTHONPATH) {
+ $env:PYTHONPATH = "$BaseDir;$($env:PYTHONPATH)"
+} else {
+ $env:PYTHONPATH = $BaseDir
+}
+
+$EnvFile = Join-Path $BaseDir 'api/.env'
+
+# Load environment variables
+if (Test-Path $EnvFile) {
+ Get-Content $EnvFile | ForEach-Object {
+ $line = $_.Trim()
+ if ($line -and -not $line.StartsWith('#')) {
+ $parts = $line -split '=', 2
+ if ($parts.Count -eq 2) {
+ [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process')
+ }
+ }
+ }
+} else {
+ Write-Host "Error: Environment file $EnvFile not found." -ForegroundColor Red
+ exit 1
+}
+
+# Run migrations
+alembic -c api/alembic.ini upgrade head
diff --git a/scripts/setup_pipecat.ps1 b/scripts/setup_pipecat.ps1
new file mode 100644
index 0000000..e682413
--- /dev/null
+++ b/scripts/setup_pipecat.ps1
@@ -0,0 +1,24 @@
+#!/usr/bin/env pwsh
+# Setup script for using pipecat as a git submodule (Windows)
+
+$ErrorActionPreference = 'Stop'
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BaseDir = Split-Path -Parent $ScriptDir
+Set-Location $BaseDir
+
+Write-Host "Setting up pipecat as a git submodule..."
+
+# Initialize and update submodules
+Write-Host "Initializing git submodules..."
+git submodule update --init --recursive
+
+# Install pipecat in editable mode with all extras
+Write-Host "Installing pipecat dependencies..."
+pip install -e './pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,speechmatics,openrouter,camb]'
+
+# Install other requirements
+Write-Host "Installing dograh API requirements..."
+pip install -r api/requirements.txt
+
+Write-Host "Setup complete! Pipecat is now available as a git submodule."
diff --git a/scripts/start_services_dev.ps1 b/scripts/start_services_dev.ps1
new file mode 100644
index 0000000..a9470c4
--- /dev/null
+++ b/scripts/start_services_dev.ps1
@@ -0,0 +1,151 @@
+#!/usr/bin/env pwsh
+# Start Dograh services in development mode (Windows)
+# Usage: .\scripts\start_services_dev.ps1 [-ArqWorkers 2] [-NoMigrations] [-IncludeTelephonyWorkers]
+#
+# Note: Telephony workers (ari_manager, campaign_orchestrator) are disabled by
+# default on Windows because they use Unix signal handlers not supported by the
+# Windows asyncio event loop.
+
+Param(
+ [int]$ArqWorkers = 1,
+ [switch]$NoMigrations,
+ [switch]$IncludeTelephonyWorkers
+)
+
+$ErrorActionPreference = 'Stop'
+
+###############################################################################
+### CONFIGURATION
+###############################################################################
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BaseDir = Split-Path -Parent $ScriptDir
+Set-Location $BaseDir
+
+$EnvFile = Join-Path $BaseDir 'api/.env'
+$RunDir = Join-Path $BaseDir 'run'
+$LogsRoot = Join-Path $BaseDir 'logs'
+$LatestDir = Join-Path $LogsRoot 'latest'
+$VenvPath = Join-Path $BaseDir 'venv'
+
+Write-Host "Starting Dograh Services (DEV MODE) in BASE_DIR: $BaseDir"
+Write-Host "Auto-reload enabled for api/ directory changes"
+
+###############################################################################
+### 1) Load environment variables
+###############################################################################
+
+if (Test-Path $EnvFile) {
+ Get-Content $EnvFile | ForEach-Object {
+ $line = $_.Trim()
+ if ($line -and -not $line.StartsWith('#')) {
+ $parts = $line -split '=', 2
+ if ($parts.Count -eq 2) {
+ [Environment]::SetEnvironmentVariable($parts[0].Trim(), $parts[1].Trim().Trim('"'), 'Process')
+ }
+ }
+ }
+}
+
+if (-not $env:UVICORN_BASE_PORT) { $env:UVICORN_BASE_PORT = '8000' }
+
+###############################################################################
+### 2) Define services
+###############################################################################
+
+$serviceSpecs = @()
+
+if ($IncludeTelephonyWorkers) {
+ $serviceSpecs += @{ Name = 'ari_manager'; Cmd = "python -m api.services.telephony.ari_manager" }
+ $serviceSpecs += @{ Name = 'campaign_orchestrator'; Cmd = "python -m api.services.campaign.campaign_orchestrator" }
+}
+
+$serviceSpecs += @{ Name = 'uvicorn'; Cmd = "uvicorn api.app:app --host 0.0.0.0 --port $($env:UVICORN_BASE_PORT) --reload --reload-dir api" }
+
+for ($i = 1; $i -le $ArqWorkers; $i++) {
+ $serviceSpecs += @{ Name = "arq$i"; Cmd = "python -m arq api.tasks.arq.WorkerSettings --custom-log-dict api.tasks.arq.LOG_CONFIG" }
+}
+
+###############################################################################
+### 3) Activate virtual environment
+###############################################################################
+
+$VenvActivateScript = Join-Path $VenvPath 'Scripts/Activate.ps1'
+
+if (Test-Path $VenvActivateScript) {
+ . $VenvActivateScript
+ Write-Host "Virtual environment activated: $VenvPath"
+} else {
+ Write-Host "Warning: Virtual environment not found at $VenvPath"
+ Write-Host "Continuing without virtual environment activation..."
+}
+
+###############################################################################
+### 4) Stop old services
+###############################################################################
+
+New-Item -ItemType Directory -Path $RunDir -Force | Out-Null
+
+foreach ($spec in $serviceSpecs) {
+ $pidFile = Join-Path $RunDir "$($spec.Name).pid"
+ if (Test-Path $pidFile) {
+ $oldPid = (Get-Content $pidFile -Raw).Trim()
+ if ($oldPid) {
+ $prev = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue'
+ & taskkill /PID $oldPid /T /F 2>&1 | Out-Null
+ $ErrorActionPreference = $prev
+ }
+ Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
+ }
+}
+
+###############################################################################
+### 5) Run migrations
+###############################################################################
+
+if (-not $NoMigrations) {
+ alembic -c (Join-Path $BaseDir 'api/alembic.ini') upgrade head
+}
+
+###############################################################################
+### 6) Prepare logs
+###############################################################################
+
+New-Item -ItemType Directory -Path $LatestDir -Force | Out-Null
+Get-ChildItem $LatestDir -Filter '*.log' -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue
+
+###############################################################################
+### 7) Start services
+###############################################################################
+
+foreach ($spec in $serviceSpecs) {
+ $name = $spec.Name
+ $logPath = Join-Path $LatestDir "$name.log"
+ $pidFile = Join-Path $RunDir "$name.pid"
+
+ Write-Host "-> Starting $name"
+
+ $wrapped = "cd /d `"$BaseDir`" && $($spec.Cmd) >> `"$logPath`" 2>&1"
+ $proc = Start-Process cmd.exe -ArgumentList '/c', $wrapped -PassThru -WindowStyle Hidden
+
+ Set-Content -Path $pidFile -Value $proc.Id
+ Write-Host " PID $($proc.Id) -> $logPath"
+}
+
+###############################################################################
+### 8) Summary
+###############################################################################
+
+Write-Host ""
+Write-Host "------------------------------------------------------"
+Write-Host "Mode: DEVELOPMENT (auto-reload enabled)"
+Write-Host ""
+foreach ($spec in $serviceSpecs) {
+ $procId = (Get-Content (Join-Path $RunDir "$($spec.Name).pid") -Raw).Trim()
+ Write-Host " $($spec.Name) (PID $procId) -> logs/latest/$($spec.Name).log"
+}
+Write-Host ""
+Write-Host "Health: curl.exe http://localhost:$($env:UVICORN_BASE_PORT)/api/v1/health"
+Write-Host "Logs: Get-Content logs/latest/*.log -Wait"
+Write-Host "Stop: .\scripts\stop_services.ps1"
+Write-Host "------------------------------------------------------"
diff --git a/scripts/stop_services.ps1 b/scripts/stop_services.ps1
new file mode 100644
index 0000000..d5006a4
--- /dev/null
+++ b/scripts/stop_services.ps1
@@ -0,0 +1,92 @@
+#!/usr/bin/env pwsh
+# Stop Dograh services started by start_services_dev.ps1 (Windows)
+
+$ErrorActionPreference = 'Stop'
+
+###############################################################################
+### CONFIGURATION
+###############################################################################
+
+$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$BaseDir = Split-Path -Parent $ScriptDir
+$RunDir = Join-Path $BaseDir 'run'
+
+Set-Location $BaseDir
+Write-Host "Stopping Dograh Services in BASE_DIR: $BaseDir"
+
+###############################################################################
+### HELPER
+###############################################################################
+
+function Stop-ProcessTree([int]$ProcessId) {
+ # taskkill /T kills the entire process tree. Temporarily relax error
+ # preference so that a "process not found" message on stderr does not
+ # terminate the script.
+ $prev = $ErrorActionPreference
+ try {
+ $ErrorActionPreference = 'SilentlyContinue'
+ & taskkill /PID $ProcessId /T /F 2>&1 | Out-Null
+ } finally {
+ $ErrorActionPreference = $prev
+ }
+}
+
+###############################################################################
+### STOP SERVICES
+###############################################################################
+
+if (-not (Test-Path $RunDir)) {
+ Write-Host "No run directory found at $RunDir"
+ Write-Host "No services appear to be running."
+ exit 0
+}
+
+$pidFiles = Get-ChildItem $RunDir -Filter '*.pid' -ErrorAction SilentlyContinue
+if (-not $pidFiles) {
+ Write-Host "No PID files found in $RunDir"
+ Write-Host "No services appear to be running."
+ exit 0
+}
+
+$stoppedCount = 0
+$failedCount = 0
+
+foreach ($pidFile in $pidFiles) {
+ $name = $pidFile.BaseName
+ $oldPid = (Get-Content $pidFile.FullName -Raw).Trim()
+
+ $proc = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue
+ if ($proc) {
+ Write-Host "Stopping $name (PID $oldPid)..."
+ Stop-ProcessTree ([int]$oldPid)
+ Start-Sleep -Seconds 2
+
+ $still = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue
+ if ($still) {
+ Write-Host " Warning: $name did not exit cleanly"
+ $failedCount++
+ } else {
+ Write-Host " Stopped $name"
+ $stoppedCount++
+ }
+ } else {
+ # The tracked cmd.exe may have exited but child processes may still run.
+ # Best-effort cleanup via taskkill tree kill.
+ Stop-ProcessTree ([int]$oldPid)
+ Write-Host "Service $name (PID $oldPid) is not running"
+ }
+
+ Remove-Item $pidFile.FullName -Force -ErrorAction SilentlyContinue
+}
+
+###############################################################################
+### SUMMARY
+###############################################################################
+
+Write-Host ""
+Write-Host "------------------------------------------------------"
+Write-Host "Stopped $stoppedCount service(s)"
+if ($failedCount -gt 0) {
+ Write-Host "Failed to stop $failedCount service(s)"
+}
+Write-Host "------------------------------------------------------"
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 379da05..32e0c67 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ui",
- "version": "1.16.0",
+ "version": "1.19.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ui",
- "version": "1.16.0",
+ "version": "1.19.2",
"dependencies": {
"@dagrejs/dagre": "^1.1.4",
"@hey-api/client-fetch": "^0.10.0",
@@ -62,6 +62,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/source-map-support": "^0.5.10",
+ "cross-env": "^7.0.3",
"eslint": "^9",
"eslint-config-next": "^15.3.3",
"eslint-plugin-simple-import-sort": "^12.1.1",
@@ -12900,6 +12901,25 @@
}
}
},
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
diff --git a/ui/package.json b/ui/package.json
index 04e7ce8..fd4d2d7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -3,7 +3,7 @@
"version": "1.20.0",
"private": true,
"scripts": {
- "dev": "NODE_OPTIONS='--enable-source-maps' next dev --turbopack",
+ "dev": "cross-env NODE_OPTIONS=--enable-source-maps next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint",
@@ -60,6 +60,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@hey-api/openapi-ts": "^0.66.2",
+ "cross-env": "^7.0.3",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",