Simplified folder structure to "Server" and "Agent", and merged the launch scripts for each into a single multi-menu launch script.

This commit is contained in:
Nicole Rappe 2025-04-07 19:41:33 -06:00
parent 4c4623fa71
commit 44c0fe68f7
4 changed files with 169 additions and 496 deletions

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
.vs/
Borealis-Workflow-Automation-Tool/
Borealis-API-Collector-Agent/
Server/
Agent/

View File

@ -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_())

View File

@ -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

View File

@ -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
}
}