diff --git a/dashboard/View-SwarmDashboard.ps1 b/dashboard/View-SwarmDashboard.ps1
new file mode 100644
index 0000000..d03173d
--- /dev/null
+++ b/dashboard/View-SwarmDashboard.ps1
@@ -0,0 +1,51 @@
+$traceFile = "C:\\PersonalRepo\\.planning\\traces.jsonl"
+$clearString = [char]27 + "[2J" + [char]27 + "[H"
+
+while ($true) {
+ Write-Host -NoNewline $clearString
+ Write-Host "================================================================================" -ForegroundColor Cyan
+ Write-Host " LIVE SWARM DASHBOARD (2026 OTel Architecture) " -ForegroundColor White -BackgroundColor DarkBlue
+ Write-Host "================================================================================`n" -ForegroundColor Cyan
+
+ if (Test-Path $traceFile) {
+ # Read the last 15 lines so the dashboard doesn't overflow
+ $rawLines = Get-Content -Path $traceFile -Tail 15 -ErrorAction SilentlyContinue
+
+ $logs = @()
+ foreach ($line in $rawLines) {
+ try {
+ if (-not [string]::IsNullOrWhiteSpace($line)) {
+ $logs += $line | ConvertFrom-Json -ErrorAction Stop
+ }
+ } catch {
+ # Skip invalid JSON lines
+ }
+ }
+
+ if ($logs.Count -gt 0) {
+ $formattedLogs = foreach ($log in $logs) {
+ $reason = $log.reasoning_path
+ if ($null -ne $reason -and $reason.Length -gt 60) {
+ $reason = $reason.Substring(0,57) + "..."
+ }
+
+ [PSCustomObject]@{
+ Time = [datetime]::Parse($log.timestamp).ToString("HH:mm:ss")
+ Span = if ($log.span_id) { $log.span_id } else { "N/A" }
+ Persona = if ($log.persona) { $log.persona } else { "Unknown" }
+ Action = if ($log.action_type) { $log.action_type } else { "Unknown" }
+ Tokens = if ($log.metrics.total_tokens) { $log.metrics.total_tokens } else { 0 }
+ Reasoning = $reason
+ }
+ }
+ $formattedLogs | Format-Table -AutoSize
+ } else {
+ Write-Host "No valid traces found yet." -ForegroundColor Yellow
+ }
+ } else {
+ Write-Host "Waiting for traces... ($traceFile not found)" -ForegroundColor Yellow
+ }
+
+ Write-Host "`nPress Ctrl+C to exit. Refreshing every 2 seconds..." -ForegroundColor DarkGray
+ Start-Sleep -Seconds 2
+}
diff --git a/dashboard/index.html b/dashboard/index.html
new file mode 100644
index 0000000..f08ec95
--- /dev/null
+++ b/dashboard/index.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+ Promptimprover Swarm Dashboard
+
+
+
+
+ Promptimprover Swarm Dashboard
+
+
+
+
+
+ | Time |
+ Span |
+ Persona |
+ Action |
+ Tokens |
+ Reasoning |
+
+
+
+
+
+
+
+
diff --git a/dashboard/server.js b/dashboard/server.js
new file mode 100644
index 0000000..4e6d8c5
--- /dev/null
+++ b/dashboard/server.js
@@ -0,0 +1,62 @@
+// C:\PersonalRepo\portfolio\Promptimprover\dashboard\server.js
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const url = require('url');
+const { once } = require('events');
+
+const TRACE_FILE = path.resolve('C:/PersonalRepo/.planning/traces.jsonl');
+const STATIC_DIR = path.join(__dirname);
+const PORT = 3000;
+
+// Helper: read last N lines from trace file
+async function readLastLines(filePath, maxLines = 20) {
+ if (!fs.existsSync(filePath)) return [];
+ const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
+ let data = '';
+ stream.on('data', chunk => { data += chunk; });
+ await once(stream, 'end');
+ const lines = data.trim().split(/\r?\n/);
+ const recent = lines.slice(-maxLines);
+ // parse each JSON line safely
+ return recent.map(l => {
+ try { return JSON.parse(l); } catch (_) { return null; }
+ }).filter(Boolean);
+}
+
+function serveStatic(res, filePath, contentType) {
+ fs.readFile(filePath, (err, content) => {
+ if (err) {
+ res.writeHead(404);
+ res.end('Not found');
+ } else {
+ res.writeHead(200, { 'Content-Type': contentType });
+ res.end(content);
+ }
+ });
+}
+
+const server = http.createServer(async (req, res) => {
+ const parsed = url.parse(req.url, true);
+ if (parsed.pathname === '/' || parsed.pathname === '/index.html') {
+ serveStatic(res, path.join(STATIC_DIR, 'index.html'), 'text/html');
+ } else if (parsed.pathname === '/style.css') {
+ serveStatic(res, path.join(STATIC_DIR, 'style.css'), 'text/css');
+ } else if (parsed.pathname === '/data') {
+ try {
+ const logs = await readLastLines(TRACE_FILE, 20);
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify(logs));
+ } catch (e) {
+ res.writeHead(500);
+ res.end('Error reading trace file');
+ }
+ } else {
+ res.writeHead(404);
+ res.end('Not found');
+ }
+});
+
+server.listen(PORT, '127.0.0.1', () => {
+ console.log(`Promptimprover dashboard listening on http://127.0.0.1:${PORT}`);
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..662fd35
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "promptimprover",
+ "version": "0.1.0",
+ "scripts": {
+ "dashboard": "powershell -NoProfile -ExecutionPolicy Bypass -File ./run-dashboard.ps1",
+ "dashboard-web": "node dashboard/server.js"
+ }
+}
diff --git a/run-dashboard.ps1 b/run-dashboard.ps1
new file mode 100644
index 0000000..e9501e8
--- /dev/null
+++ b/run-dashboard.ps1
@@ -0,0 +1,6 @@
+$script = Join-Path $PSScriptRoot "dashboard\View-SwarmDashboard.ps1"
+if (Test-Path $script) {
+ powershell -NoProfile -ExecutionPolicy Bypass -File $script
+} else {
+ Write-Host "Dashboard script not found at $script" -ForegroundColor Red
+}