From 44c0fe68f7fafd5400ed8d6615c7baa6d231c505 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Mon, 7 Apr 2025 19:41:33 -0600 Subject: [PATCH] Simplified folder structure to "Server" and "Agent", and merged the launch scripts for each into a single multi-menu launch script. --- .gitignore | 4 +- Data/Agent/api-collector-agent.py | 320 ------------------------------ Launch-Agent.ps1 | 85 -------- Launch-Borealis.ps1 | 256 +++++++++++++++--------- 4 files changed, 169 insertions(+), 496 deletions(-) delete mode 100644 Data/Agent/api-collector-agent.py delete mode 100644 Launch-Agent.ps1 diff --git a/.gitignore b/.gitignore index 6f5d4e3..0430de9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vs/ -Borealis-Workflow-Automation-Tool/ -Borealis-API-Collector-Agent/ \ No newline at end of file +Server/ +Agent/ \ No newline at end of file diff --git a/Data/Agent/api-collector-agent.py b/Data/Agent/api-collector-agent.py deleted file mode 100644 index 427e154..0000000 --- a/Data/Agent/api-collector-agent.py +++ /dev/null @@ -1,320 +0,0 @@ -import sys -import uuid -import time -import json -import base64 -import threading -import requests -from io import BytesIO -import socket - -from PyQt5 import QtCore, QtGui, QtWidgets -from PIL import ImageGrab - -# ---------------- Configuration ---------------- -# SERVER_URL = "https://borealis.bunny-lab.io" # Production URL Example -SERVER_URL = "http://localhost:5000" -CHECKIN_ENDPOINT = f"{SERVER_URL}/api/agent/checkin" -CONFIG_ENDPOINT = f"{SERVER_URL}/api/agent/config" -DATA_POST_ENDPOINT = f"{SERVER_URL}/api/agent/data" -HEARTBEAT_ENDPOINT = f"{SERVER_URL}/api/agent/heartbeat" - -HOSTNAME = socket.gethostname().lower() -RANDOM_SUFFIX = uuid.uuid4().hex[:8] -AGENT_ID = f"{HOSTNAME}-agent-{RANDOM_SUFFIX}" - -# Default poll interval for config. Adjust as needed. -CONFIG_POLL_INTERVAL = 5 - -# ---------------- State ---------------- -app_instance = None -region_widget = None -capture_thread_started = False -current_interval = 1000 -config_ready = threading.Event() -overlay_visible = True -heartbeat_thread_started = False - -# Track if we have a valid connection to Borealis -IS_CONNECTED = False -CONNECTION_LOST_REPORTED = False - -# Keep a copy of the last config to avoid repeated provisioning -LAST_CONFIG = {} - -# ---------------- Signal Bridge ---------------- -class RegionLauncher(QtCore.QObject): - trigger = QtCore.pyqtSignal(int, int, int, int) - - def __init__(self): - super().__init__() - self.trigger.connect(self.handle) - - def handle(self, x, y, w, h): - launch_region(x, y, w, h) - -region_launcher = None - -# ---------------- Helper: Reconnect ---------------- -def reconnect(): - """ - Attempt to connect to Borealis until successful. - Sets IS_CONNECTED = True upon success. - """ - global IS_CONNECTED, CONNECTION_LOST_REPORTED - while not IS_CONNECTED: - try: - requests.post(CHECKIN_ENDPOINT, json={"agent_id": AGENT_ID, "hostname": HOSTNAME}, timeout=5) - IS_CONNECTED = True - CONNECTION_LOST_REPORTED = False - print(f"[INFO] Agent ID: {AGENT_ID} connected to Borealis.") - except Exception: - if not CONNECTION_LOST_REPORTED: - print(f"[CONNECTION LOST] Attempting to Reconnect to Borealis Server at {SERVER_URL}") - CONNECTION_LOST_REPORTED = True - time.sleep(10) - -# ---------------- Networking ---------------- -def poll_for_config(): - """ - Polls for agent configuration from Borealis. - Returns a config dict or None on failure. - """ - try: - res = requests.get(CONFIG_ENDPOINT, params={"agent_id": AGENT_ID}, timeout=5) - if res.status_code == 200: - return res.json() - else: - print(f"[ERROR] Config polling returned status: {res.status_code}") - except Exception: - # We'll let the config_loop handle setting IS_CONNECTED = False - pass - return None - -def send_image_data(image): - """ - Attempts to POST screenshot data to Borealis if IS_CONNECTED is True. - """ - global IS_CONNECTED, CONNECTION_LOST_REPORTED - if not IS_CONNECTED: - return # Skip sending if not connected - - try: - buffer = BytesIO() - image.save(buffer, format="PNG") - encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") - - response = requests.post(DATA_POST_ENDPOINT, json={ - "agent_id": AGENT_ID, - "type": "screenshot", - "image_base64": encoded - }, timeout=5) - - if response.status_code != 200: - print(f"[ERROR] Screenshot POST failed: {response.status_code} - {response.text}") - except Exception as e: - if IS_CONNECTED and not CONNECTION_LOST_REPORTED: - # Only report once - print(f"[CONNECTION LOST] Attempting to Reconnect to Borealis Server at {SERVER_URL}") - CONNECTION_LOST_REPORTED = True - IS_CONNECTED = False - -def send_heartbeat(): - """ - Attempts to send heartbeat if IS_CONNECTED is True. - """ - global IS_CONNECTED, CONNECTION_LOST_REPORTED - if not IS_CONNECTED: - return - - try: - response = requests.get(HEARTBEAT_ENDPOINT, params={"agent_id": AGENT_ID}, timeout=5) - if response.status_code != 200: - print(f"[ERROR] Heartbeat returned status: {response.status_code}") - raise ValueError("Heartbeat not 200") - except Exception: - if IS_CONNECTED and not CONNECTION_LOST_REPORTED: - print(f"[CONNECTION LOST] Attempting to Reconnect to Borealis Server at {SERVER_URL}") - CONNECTION_LOST_REPORTED = True - IS_CONNECTED = False - -# ---------------- Region Overlay ---------------- -class ScreenshotRegion(QtWidgets.QWidget): - def __init__(self, x=100, y=100, w=300, h=200): - super().__init__() - self.setGeometry(x, y, w, h) - self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.drag_offset = None - self.resizing = False - self.resize_handle_size = 12 - self.setVisible(True) - - self.label = QtWidgets.QLabel(self) - self.label.setText(AGENT_ID) - self.label.setStyleSheet("color: lime; background: transparent; font-size: 10px;") - self.label.move(8, 4) - - self.setMouseTracking(True) - - def paintEvent(self, event): - painter = QtGui.QPainter(self) - painter.setRenderHint(QtGui.QPainter.Antialiasing) - - # Transparent fill - painter.setBrush(QtCore.Qt.transparent) - painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0), 2)) - painter.drawRect(self.rect()) - - # Resize Handle Visual (Bottom-Right) - handle_rect = QtCore.QRect( - self.width() - self.resize_handle_size, - self.height() - self.resize_handle_size, - self.resize_handle_size, - self.resize_handle_size - ) - painter.fillRect(handle_rect, QtGui.QColor(0, 255, 0)) - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - if (event.pos().x() > self.width() - self.resize_handle_size and - event.pos().y() > self.height() - self.resize_handle_size): - self.resizing = True - else: - self.drag_offset = event.globalPos() - self.frameGeometry().topLeft() - - def mouseMoveEvent(self, event): - if self.resizing: - new_width = max(event.pos().x(), 100) - new_height = max(event.pos().y(), 80) - self.resize(new_width, new_height) - elif event.buttons() & QtCore.Qt.LeftButton and self.drag_offset: - self.move(event.globalPos() - self.drag_offset) - - def mouseReleaseEvent(self, event): - self.resizing = False - self.drag_offset = None - - def get_geometry(self): - geo = self.geometry() - return geo.x(), geo.y(), geo.width(), geo.height() - -# ---------------- Threads ---------------- -def capture_loop(): - """ - Continuously captures the user-defined region every current_interval ms if connected. - """ - global current_interval - print("[INFO] Screenshot capture loop started") - config_ready.wait() - - while region_widget is None: - print("[WAIT] Waiting for region widget to initialize...") - time.sleep(0.2) - - print(f"[INFO] Agent Capturing Region: x:{region_widget.x()} y:{region_widget.y()} w:{region_widget.width()} h:{region_widget.height()}") - - while True: - if overlay_visible and IS_CONNECTED: - x, y, w, h = region_widget.get_geometry() - try: - img = ImageGrab.grab(bbox=(x, y, x + w, y + h)) - send_image_data(img) - except Exception as e: - print(f"[ERROR] Screenshot error: {e}") - time.sleep(current_interval / 1000) - -def heartbeat_loop(): - """ - Heartbeat every 10 seconds if connected. - If it fails, we set IS_CONNECTED=False, and rely on config_loop to reconnect. - """ - while True: - send_heartbeat() - time.sleep(10) - -def config_loop(): - """ - 1) Reconnect (if needed) until the agent can contact Borealis - 2) Poll for config. If new config is different from LAST_CONFIG, re-provision - 3) If poll_for_config fails or we see connection issues, set IS_CONNECTED=False - and loop back to reconnect() on next iteration - """ - global capture_thread_started, heartbeat_thread_started - global current_interval, overlay_visible, LAST_CONFIG, IS_CONNECTED - - while True: - # If we aren't connected, reconnect - if not IS_CONNECTED: - reconnect() - - # Attempt to get config - config = poll_for_config() - if config is None: - # This means we had a poll failure, so mark disconnected and retry. - IS_CONNECTED = False - continue - - # If it has a "task" : "screenshot" - if config.get("task") == "screenshot": - # Compare to last known config - if config != LAST_CONFIG: - # Something changed, so provision - print("[PROVISIONING] Agent Provisioning Command Issued by Borealis") - - x = config.get("x", 100) - y = config.get("y", 100) - w = config.get("w", 300) - h = config.get("h", 200) - current_interval = config.get("interval", 1000) - overlay_visible = config.get("visible", True) - - print('[PROVISIONING] Agent Configured as "Screenshot" Collector') - print(f'[PROVISIONING] Polling Rate: {current_interval / 1000:.1f}s') - - # Show or move region widget - if not region_widget: - region_launcher.trigger.emit(x, y, w, h) - else: - region_widget.setGeometry(x, y, w, h) - region_widget.setVisible(overlay_visible) - - LAST_CONFIG = config - - # Make sure capture thread is started - if not capture_thread_started: - threading.Thread(target=capture_loop, daemon=True).start() - capture_thread_started = True - - # Make sure heartbeat thread is started - if not heartbeat_thread_started: - threading.Thread(target=heartbeat_loop, daemon=True).start() - heartbeat_thread_started = True - - # Signal that provisioning is done so capture thread can run - config_ready.set() - - # Sleep before next poll - time.sleep(CONFIG_POLL_INTERVAL) - -def launch_region(x, y, w, h): - """ - Initializes the screenshot region overlay widget exactly once. - """ - global region_widget - if region_widget: - return - print("[INFO] Agent Starting...") - region_widget = ScreenshotRegion(x, y, w, h) - region_widget.show() - -# ---------------- Main ---------------- -if __name__ == "__main__": - app_instance = QtWidgets.QApplication(sys.argv) - region_launcher = RegionLauncher() - - # Start the config loop in a background thread - threading.Thread(target=config_loop, daemon=True).start() - - # Enter Qt main event loop - sys.exit(app_instance.exec_()) diff --git a/Launch-Agent.ps1 b/Launch-Agent.ps1 deleted file mode 100644 index 6a3f876..0000000 --- a/Launch-Agent.ps1 +++ /dev/null @@ -1,85 +0,0 @@ -# Launch-API-Collector-Agent.ps1 -# Run this script with: -# Set-ExecutionPolicy Unrestricted -Scope Process; .\Launch-API-Collector-Agent.ps1 - -# ---------------------- Initialization & Visuals ---------------------- -$symbols = @{ - Success = [char]0x2705 - Running = [char]0x23F3 - Fail = [char]0x274C - Info = [char]0x2139 -} - -function Write-ProgressStep { - param ( - [string]$Message, - [string]$Status = $symbols["Info"] - ) - Write-Host "`r$Status $Message... " -NoNewline -} - -function Run-Step { - param ( - [string]$Message, - [scriptblock]$Script - ) - Write-ProgressStep -Message $Message -Status "$($symbols.Running)" - try { - & $Script - if ($LASTEXITCODE -eq 0 -or $?) { - Write-Host "`r$($symbols.Success) $Message " - } else { - throw "Non-zero exit code" - } - } catch { - Write-Host "`r$($symbols.Fail) $Message - Failed: $_ " -ForegroundColor Red - exit 1 - } -} - -Clear-Host -Write-Host "Deploying Borealis API Collector Agent..." -ForegroundColor Green -Write-Host "====================================================================================" - -# ---------------------- Path Definitions ---------------------- -$venvFolder = "Borealis-API-Collector-Agent" -$agentSourcePath = "Data\Agent\api-collector-agent.py" -$agentRequirements = "Data\Agent\requirements.txt" -$agentDestinationFolder = "$venvFolder\Agent" -$agentDestinationFile = "$agentDestinationFolder\api-collector-agent.py" - -# ---------------------- Create Python Virtual Environment & Copy Agent ---------------------- -Run-Step "Create Virtual Python Environment for Collector Agent" { - if (!(Test-Path "$venvFolder\Scripts\Activate")) { - python -m venv $venvFolder | Out-Null - } - - # Copy Agent Script - if (Test-Path $agentSourcePath) { - if (Test-Path $agentDestinationFolder) { - Remove-Item -Recurse -Force $agentDestinationFolder | Out-Null - } - New-Item -Path $agentDestinationFolder -ItemType Directory -Force | Out-Null - Copy-Item -Path $agentSourcePath -Destination $agentDestinationFile -Force - } else { - Write-Host "`r$($symbols.Info) Warning: Agent script not found at '$agentSourcePath', skipping copy." -ForegroundColor Yellow - } - - . "$venvFolder\Scripts\Activate" -} - -# ---------------------- Install Python Dependencies ---------------------- -Run-Step "Install Python Dependencies for Collector Agent" { - if (Test-Path $agentRequirements) { - pip install -q -r $agentRequirements 2>&1 | Out-Null - } else { - Write-Host "`r$($symbols.Info) Agent-specific requirements.txt not found at '$agentRequirements', skipping Python packages." -ForegroundColor Yellow - } -} - -# ---------------------- Launch Agent ---------------------- -Push-Location $venvFolder -Write-Host "`nLaunching Borealis API Collector Agent..." -ForegroundColor Green -Write-Host "====================================================================================" -python "Agent\api-collector-agent.py" -Pop-Location diff --git a/Launch-Borealis.ps1 b/Launch-Borealis.ps1 index e4186a2..28987c7 100644 --- a/Launch-Borealis.ps1 +++ b/Launch-Borealis.ps1 @@ -1,8 +1,20 @@ -# Start_Windows - WebServer.ps1 -# Run this script with: -# Set-ExecutionPolicy Unrestricted -Scope Process; .\Start_Windows -WebServer.ps1 +<# + Deploy-Borealis.ps1 + ---------------------- + This script deploys the Borealis Workflow Automation Tool with two modules: + - Server (Web Dashboard) + - Agent (Client / Data Collector) -# ---------------------- Initialization & Visuals ---------------------- + It begins by presenting a menu to the user. Based on the selection (1 or 2), the corresponding module is launched. + + Usage: + Set-ExecutionPolicy Unrestricted -Scope Process; .\Deploy-Borealis.ps1 +#> + +# ---------------------- Common Initialization & Visuals ---------------------- +Clear-Host + +# Define common symbols for displaying progress and status $symbols = @{ Success = [char]0x2705 Running = [char]0x23F3 @@ -10,14 +22,16 @@ $symbols = @{ Info = [char]0x2139 } +# Function to write progress steps with a given status symbol function Write-ProgressStep { param ( [string]$Message, - [string]$Status = $symbols["Info"] # Ensure proper lookup + [string]$Status = $symbols["Info"] ) Write-Host "`r$Status $Message... " -NoNewline } +# Function to run a step and check for errors function Run-Step { param ( [string]$Message, @@ -27,7 +41,7 @@ function Run-Step { try { & $Script if ($LASTEXITCODE -eq 0 -or $?) { - Write-Host "`r$($symbols.Success) $Message " # Fix symbol lookup + Write-Host "`r$($symbols.Success) $Message " } else { throw "Non-zero exit code" } @@ -37,102 +51,166 @@ function Run-Step { } } -Clear-Host -Write-Host "Deploying Borealis - Workflow Automation Tool..." -ForegroundColor Green +# ---------------------- Menu Prompt & User Input ---------------------- +Write-Host "Deploying Borealis - Workflow Automation Tool..." -ForegroundColor Blue Write-Host "====================================================================================" +Write-Host "Please choose which module you want to launch / (re)deploy:" +Write-Host "- Server (Web Dashboard) [1]" +Write-Host "- Agent (Local/Remote Client) [2]" -# ---------------------- Node.js Check ---------------------- -if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Write-Host "`r$($symbols.Fail) Node.js is not installed. Please install Node.js and try again." -ForegroundColor Red - exit 1 -} +$choice = Read-Host "Enter 1 or 2" -# ---------------------- Path Definitions ---------------------- -$venvFolder = "Borealis-Workflow-Automation-Tool" -$dataSource = "Data" -$dataDestination = "$venvFolder\Borealis" -$customUIPath = "$dataSource\WebUI" -$webUIDestination = "$venvFolder\web-interface" +switch ($choice) { -# ---------------------- Create Python Virtual Environment & Prepare Borealis Files ---------------------- -Run-Step "Create Borealis Virtual Python Environment" { - if (!(Test-Path "$venvFolder\Scripts\Activate")) { - python -m venv $venvFolder | Out-Null - } - # ---------------------- Copy Server Data ---------------------- - if (Test-Path $dataSource) { - if (Test-Path $dataDestination) { - Remove-Item -Recurse -Force $dataDestination | Out-Null + # ---------------------- Server Module ---------------------- + "1" { + Clear-Host + Write-Host "Deploying Borealis - Workflow Automation Tool..." -ForegroundColor Green + Write-Host "====================================================================================" + + # ---------------------- Server: Environment & Dependency Checks ---------------------- + # Check if Node.js is installed + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Write-Host "`r$($symbols.Fail) Node.js is not installed. Please install Node.js and try again." -ForegroundColor Red + exit 1 } - New-Item -Path $dataDestination -ItemType Directory -Force | Out-Null - Copy-Item -Path "$dataSource\*" -Destination $dataDestination -Recurse - } else { - Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow - } + + # ---------------------- Server: Path Definitions ---------------------- + $venvFolder = "Server" + $dataSource = "Data" + $dataDestination = "$venvFolder\Borealis" + $customUIPath = "$dataSource\WebUI" + $webUIDestination = "$venvFolder\web-interface" - # ---------------------- React UI Deployment ---------------------- - if (-not (Test-Path $webUIDestination)) { - npx create-react-app $webUIDestination | Out-Null - } + # ---------------------- Server: Create Python Virtual Environment & Prepare Files ---------------------- + Run-Step "Create Borealis Virtual Python Environment" { + # Create virtual environment if it does not exist + if (!(Test-Path "$venvFolder\Scripts\Activate")) { + python -m venv $venvFolder | Out-Null + } + # Copy server data if the Data folder exists + if (Test-Path $dataSource) { + if (Test-Path $dataDestination) { + Remove-Item -Recurse -Force $dataDestination | Out-Null + } + New-Item -Path $dataDestination -ItemType Directory -Force | Out-Null + Copy-Item -Path "$dataSource\*" -Destination $dataDestination -Recurse + } else { + Write-Host "`r$($symbols.Info) Warning: Data folder not found, skipping copy." -ForegroundColor Yellow + } + # React UI Deployment: Create default React app if no deployment folder exists + if (-not (Test-Path $webUIDestination)) { + npx create-react-app $webUIDestination | Out-Null + } + # Copy custom UI if it exists + if (Test-Path $customUIPath) { + Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force + } else { + Write-Host "`r$($symbols.Info) No custom UI found, using default React app." -ForegroundColor Yellow + } + # Remove any existing React build folder + if (Test-Path "$webUIDestination\build") { + Remove-Item -Path "$webUIDestination\build" -Recurse -Force + } + # Activate the Python virtual environment + . "$venvFolder\Scripts\Activate" + } - if (Test-Path $customUIPath) { - Copy-Item -Path "$customUIPath\*" -Destination $webUIDestination -Recurse -Force - } else { - Write-Host "`r$($symbols.Info) No custom UI found, using default React app." -ForegroundColor Yellow - } + # ---------------------- Server: Install Python Dependencies ---------------------- + Run-Step "Install Python Dependencies into Virtual Python Environment" { + if (Test-Path "requirements.txt") { + pip install -q -r requirements.txt 2>&1 | Out-Null + } else { + Write-Host "`r$($symbols.Info) No requirements.txt found, skipping Python packages." -ForegroundColor Yellow + } + } - # Remove Pre-Existing ReactJS Build Folder (If one Exists) - if (Test-Path "$webUIDestination\build") { - Remove-Item -Path "$webUIDestination\build" -Recurse -Force - } + # ---------------------- Server: Build React App ---------------------- + Run-Step "ReactJS Web Frontend: Install Necessary NPM Packages" { + $packageJsonPath = Join-Path $webUIDestination "package.json" + if (Test-Path $packageJsonPath) { + Push-Location $webUIDestination + $env:npm_config_loglevel = "silent" - # ---------------------- Activate Python Virtual Environment ---------------------- - . "$venvFolder\Scripts\Activate" -} + # Install required NPM packages + npm install --silent --no-fund --audit=false 2>&1 | Out-Null + npm install --silent react-resizable --no-fund --audit=false | Out-Null + npm install --silent reactflow --no-fund --audit=false | Out-Null + npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null -# ---------------------- Install Python Dependencies ---------------------- -Run-Step "Install Python Dependencies into Virtual Python Environment" { - if (Test-Path "requirements.txt") { - pip install -q -r requirements.txt 2>&1 | Out-Null - } else { - Write-Host "`r$($symbols.Info) No requirements.txt found, skipping Python packages." -ForegroundColor Yellow - } -} + Pop-Location + } + } -# ---------------------- Build React App ---------------------- -Run-Step "ReactJS Web Frontend: Install Necessary NPM Packages" { - $packageJsonPath = Join-Path $webUIDestination "package.json" - if (Test-Path $packageJsonPath) { - Push-Location $webUIDestination - $env:npm_config_loglevel = "silent" - - # Install NPM - npm install --silent --no-fund --audit=false 2>&1 | Out-Null - - # Install React Resizable - npm install --silent react-resizable --no-fund --audit=false | Out-Null - - # Install React Flow - npm install --silent reactflow --no-fund --audit=false | Out-Null - - # Install Material UI Libraries - npm install --silent @mui/material @mui/icons-material @emotion/react @emotion/styled --no-fund --audit=false 2>&1 | Out-Null + Run-Step "ReactJS Web Frontend: Build App" { + Push-Location $webUIDestination + # Build the React app (output is visible for development) + npm run build + Pop-Location + } + # ---------------------- Server: Launch Flask Server ---------------------- + Push-Location $venvFolder + Write-Host "`nLaunching Borealis..." -ForegroundColor Green + Write-Host "====================================================================================" + Write-Host "$($symbols.Running) Starting Python Flask Server..." -NoNewline + python "Borealis\server.py" Pop-Location } -} -Run-Step "ReactJS Web Frontend: Build App" { - Push-Location $webUIDestination - #npm run build | Out-Null # Suppress Compilation Output - npm run build # Enabled during Development - Pop-Location -} + # ---------------------- Agent Module ---------------------- + "2" { + Clear-Host + Write-Host "Deploying Borealis Agent..." -ForegroundColor Green + Write-Host "====================================================================================" -# ---------------------- Launch Flask Server ---------------------- -Push-Location $venvFolder -Write-Host "`nLaunching Borealis..." -ForegroundColor Green -Write-Host "====================================================================================" -Write-Host "$($symbols.Running) Starting Python Flask Server..." -NoNewline -python "Borealis\server.py" -Pop-Location + # ---------------------- Agent: Path Definitions ---------------------- + $venvFolder = "Agent" + $agentSourcePath = "Data\Agent\borealis-agent.py" + $agentRequirements = "Data\Agent\requirements.txt" + $agentDestinationFolder = "$venvFolder\Agent" + $agentDestinationFile = "$agentDestinationFolder\borealis-agent.py" + + # ---------------------- Agent: Create Python Virtual Environment & Copy Agent Script ---------------------- + Run-Step "Create Virtual Python Environment for Agent" { + # Create virtual environment for the agent + if (!(Test-Path "$venvFolder\Scripts\Activate")) { + python -m venv $venvFolder | Out-Null + } + # Copy the agent script if it exists + if (Test-Path $agentSourcePath) { + if (Test-Path $agentDestinationFolder) { + Remove-Item -Recurse -Force $agentDestinationFolder | Out-Null + } + New-Item -Path $agentDestinationFolder -ItemType Directory -Force | Out-Null + Copy-Item -Path $agentSourcePath -Destination $agentDestinationFile -Force + } else { + Write-Host "`r$($symbols.Info) Warning: Agent script not found at '$agentSourcePath', skipping copy." -ForegroundColor Yellow + } + # Activate the virtual environment + . "$venvFolder\Scripts\Activate" + } + + # ---------------------- Agent: Install Python Dependencies ---------------------- + Run-Step "Install Python Dependencies for Agent" { + if (Test-Path $agentRequirements) { + pip install -q -r $agentRequirements 2>&1 | Out-Null + } else { + Write-Host "`r$($symbols.Info) Agent-specific requirements.txt not found at '$agentRequirements', skipping Python packages." -ForegroundColor Yellow + } + } + + # ---------------------- Agent: Launch Agent Script ---------------------- + Push-Location $venvFolder + Write-Host "`nLaunching Borealis Agent..." -ForegroundColor Green + Write-Host "====================================================================================" + python "Agent\borealis-agent.py" + Pop-Location + } + + # ---------------------- Default (Invalid Selection) ---------------------- + default { + Write-Host "Invalid selection. Exiting..." -ForegroundColor Yellow + exit 1 + } +}