@blackbelt-technology/pi-agent-dashboard
Web dashboard for monitoring and interacting with pi agent sessions
Package details
Install @blackbelt-technology/pi-agent-dashboard from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@blackbelt-technology/pi-agent-dashboard- Package
@blackbelt-technology/pi-agent-dashboard- Version
0.4.6- Published
- May 2, 2026
- Downloads
- 2,238/mo · 832/wk
- Author
- mbotond
- License
- MIT
- Types
- extension, skill
- Size
- 3 MB
- Dependencies
- 3 dependencies · 7 peers
Pi manifest JSON
{
"extensions": [
"packages/extension/src/bridge.ts"
],
"skills": [
"packages/extension/.pi/skills/pi-dashboard"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
PI Dashboard
A web-based dashboard for monitoring and interacting with pi agent sessions from any browser, including mobile.
🌐 Website & demo: blackbelttechnology.github.io/pi-agent-dashboard — animated tour, screenshots, and install guide.
📝 Changelog: CHANGELOG.md
Note: This dashboard only works with pi. Oh My Pi is not supported.
Table of contents
- Quickstart
- Features
- Prerequisites
- Configuration
- Usage
- Recommended extensions
- Troubleshooting
- Architecture
- Monitoring
- Development
- Building the Electron app
- CI/CD & releasing
- License
Quickstart
Three install paths, pick one:
A — Electron desktop app (no prerequisites)
Download a pre-built installer from GitHub Releases:
| Platform | Download |
|---|---|
| macOS (Apple Silicon / Intel) | .dmg (arm64 / x64) |
| Linux (x64 / ARM64) | .deb or .AppImage |
| Windows (x64 / ARM64) | .exe (NSIS), .zip, or portable .exe |
On first launch a setup wizard walks you through mode selection (standalone vs. power-user), API key / OAuth sign-in, and recommended extensions. The standalone mode bundles Node.js and auto-installs pi + dashboard + openspec into ~/.pi-dashboard/ — no terminal, npm, or Node.js required.
Picking the right macOS DMG: run uname -m in Terminal — arm64 means Apple Silicon (M1/M2/M3/M4), x86_64 means Intel. Or open Apple menu → About This Mac and read the chip name. Download the matching DMG; if you grab the wrong one macOS will refuse to launch the app with a "cannot be opened" error.
Note: A future release will rename the macOS DMGs to
PI-Dashboard-darwin-arm64-<ver>.dmgandPI-Dashboard-darwin-x64-<ver>.dmg(previously a singlePI Dashboard.dmgwas produced and silently overwrote one arch on each release). Direct download links pointing at the unsuffixed filename will 404 from that release onward; please link to the Releases page instead. See OpenSpec changefix-darwin-dmg-arch-collision.
B — pi package (recommended for CLI users)
pi install npm:@blackbelt-technology/pi-agent-dashboard
pi
The bridge extension auto-starts the dashboard server on first launch:
🌐 Dashboard started at http://localhost:8000
Open http://localhost:8000 in any browser. All active pi sessions appear automatically. See Prerequisites for Node.js / build-tool requirements.
C — From source (contributors)
git clone https://github.com/BlackBeltTechnology/pi-agent-dashboard.git
cd pi-agent-dashboard
npm install
pi install /path/to/pi-agent-dashboard # global
# or: pi install -l /path/to/pi-agent-dashboard # project-local only
To try the extension in a single pi session without registering it:
pi -e /path/to/pi-agent-dashboard/packages/extension/src/bridge.ts
Remove with pi remove /path/to/pi-agent-dashboard. Alternatively, add the package path directly to ~/.pi/agent/settings.json (global) or .pi/settings.json (project) under "packages": [...].
Features
Sessions & chat
- Real-time session mirroring — all active pi sessions with live streaming messages
- Bidirectional interaction — send prompts and commands from the browser
- Session statistics — token counts, costs, model info, thinking level, context usage bar
- Elapsed time tracking — live ticking counters on running operations, final duration on completed tool calls and reasoning blocks
- Session spawning — launch new pi sessions from the dashboard (headless by default, or via tmux)
- On-demand session loading — browse historical sessions with lazy-loaded content from pi session files
- Force kill escalation — two-click Stop button; first click sends soft abort, second force-kills (SIGTERM → SIGKILL). Session preserved as "ended" for resume/fork.
Workspace & UI
- Workspace management — organize sessions by project folder with pinned directories and drag-to-reorder
- Command autocomplete —
/prefix triggers a filtering dropdown - Mobile-friendly — responsive layout with swipe drawer, touch targets, and mobile action menus
- Markdown preview — rendered markdown views with search, mermaid diagrams, syntax highlighting, and safe handling for raw HTML
refattributes - Searchable select dialogs — keyboard-navigable picker with real-time filtering (OpenSpec changes, flow commands)
Integrations
- PromptBus architecture — unified prompt routing with adapters (TUI, dashboard, custom). Interactive dialogs (confirm/select/input/editor/multiselect) survive page refresh and server restart. Multiselect uses the bus-routed browser path exclusively (the dashboard
MultiselectRendererdialog) since pi 0.70's RPC mode has no working terminal-overlay primitive. First-response-wins semantics with cross-adapter dismissal. - Extension UI System (Phase 1) — extensions can declare slash-command-triggered modal UIs as data, without authoring React or importing an SDK. Listen on
pi.events.on("ui:list-modules", probe)and push descriptors intoprobe.modules; the dashboard renderstable/grid/formviews with row actions, optional confirm-dialog gates, and MDI icons. Modules survive reconnect via the server-side cache; in pure-pi mode descriptors stay dormant. Seeopenspec/specs/extension-ui-system/spec.mdfor the protocol contract. - pi-flows integration — live flow execution dashboard with agent cards, detail views, flow graph, summary, abort/auto controls. Launch flows and design new ones with Flow Architect, all from the browser. Fork decisions and subagent dialogs forwarded via PromptBus.
- OpenSpec integration — browse specs, view archive history, manage changes, create new changes from the sidebar
- Browser-based provider auth — sign in to Anthropic, OpenAI Codex, GitHub Copilot, Gemini CLI, and Antigravity from Settings. Enter API keys for other providers. Credentials saved to
~/.pi/agent/auth.jsonand live-synced to running sessions. - Custom LLM providers — add OpenAI-compatible, Anthropic-compatible, or Google Generative AI endpoints (Settings → Providers → LLM Providers). Test button verifies the base URL + API key before saving. Adding / editing / removing takes effect live in every running session — no restart.
- Package management — browse, install, update, remove, and move pi packages between global and project scopes from a single rich-row UI used in both Settings and Pi Resources. Install dialog exposes a Local/Global radio when launched from a per-folder context. Search the npm registry for pi-package extensions/skills/themes; install from npm or git URL. Active sessions auto-reload after changes.
Dev tools
- Integrated terminal — full browser-based terminal emulator (xterm.js + node-pty) with ANSI colors, scrollback, and keep-alive
- Diff viewer — side-by-side and unified diff views with file tree navigation. In Jujutsu workspaces the diff is regime-aware: shows the cumulative changes since the workspace's branch point, not just the working-copy delta.
- Editor integration — open files in VS Code, Cursor, etc. directly from tool call cards
- Jujutsu workspaces (optional) — when
jjis on PATH and the session is inside a.jj/repo, the dashboard surfaces a workspace badge, a+ Workspaceaction that createsjj workspace add+ spawns a fresh agent in it, and aFold backaction that drives thejj-workspace-fold-backskill (jj-native rebase + push, nevergit commit/git merge). Activates silently — zero UI whenjjis not installed. See docs/architecture.md for the data flow.
Networking & distribution
- Network discovery — mDNS-based auto-discovery of other dashboard servers on the local network
- Zrok tunnel — optional persistent public URL via reserved shares (see Configuration → Tunnel)
Prerequisites
Only needed for Quickstart paths B and C. The Electron app (path A) bundles everything in standalone mode.
| Requirement | Why | Install |
|---|---|---|
| pi | The AI coding agent the dashboard monitors | npm i -g @mariozechner/pi-coding-agent |
| Node.js ≥ 22.18.0 | Server runtime. Older 22.x / 24.x < 24.3.0 are affected by nodejs/node#58515 which crashes Fastify at startup. | nodejs.org |
| C++ build tools | Required by node-pty native addon for the integrated terminal |
Xcode CLI Tools (macOS) / build-essential (Linux) |
Optional:
| Tool | Purpose | When needed |
|---|---|---|
| tmux | Spawn new pi sessions in a tmux window | When spawnStrategy is "tmux" |
| zrok | Public tunnel with persistent URLs | When tunnel.enabled is true (default) |
Configuration
- Config file:
~/.pi/dashboard/config.json(auto-created with defaults on first run) - Tool overrides (machine-local):
~/.pi/dashboard/tool-overrides.json— see Tool overrides - Settings UI: click the ⚙ gear icon in the sidebar header to edit all fields from the browser
Precedence & keys
CLI flags → environment variables → config file → built-in defaults.
| CLI flag | Env var | Config key | Default | Description |
|---|---|---|---|---|
--port |
PI_DASHBOARD_PORT |
port |
8000 |
HTTP + browser WebSocket port |
--pi-port |
PI_DASHBOARD_PI_PORT |
piPort |
9999 |
Pi extension WebSocket port |
--dev |
— | — | false |
Development mode (proxy to Vite) |
--no-tunnel |
— | tunnel.enabled |
true |
Disable zrok tunnel |
| — | — | autoStart |
true |
Bridge auto-starts server if not running |
| — | — | autoShutdown |
false |
Server shuts down when idle |
| — | — | shutdownIdleSeconds |
300 |
Seconds idle before auto-shutdown |
| — | — | spawnStrategy |
"headless" |
Session spawn mode: "headless" or "tmux" |
| — | — | reattachPlacement |
"always" |
After a dashboard restart, where re-registering bridges land in folder lists. "always" (top), "streaming-only" (only mid-completion), "preserve" (legacy: keep prior drag order) |
| — | — | devBuildOnReload |
false |
Rebuild client + restart server on /reload |
| — | — | askUserPromptTimeoutSeconds |
300 |
ask_user prompt timeout in seconds. ≤ 0 (e.g. -1) = wait indefinitely |
The bridge also honours PI_DASHBOARD_URL=ws://host:port to point at a remote server instead of localhost.
Minimal config.json
{
"port": 8000,
"piPort": 9999,
"autoStart": true,
"autoShutdown": false,
"shutdownIdleSeconds": 300,
"spawnStrategy": "headless",
"tunnel": { "enabled": true, "reservedToken": "auto-created-on-first-run" },
"devBuildOnReload": false,
"askUserPromptTimeoutSeconds": 300,
"openspec": {
"pollIntervalSeconds": 30,
"maxConcurrentSpawns": 3,
"changeDetection": "mtime",
"jitterSeconds": 5
}
}
Authentication (optional)
OAuth2 authentication guards external (tunnel) access. Localhost is always unguarded.
{
"auth": {
"secret": "auto-generated-if-omitted",
"providers": {
"github": { "clientId": "...", "clientSecret": "..." },
"google": { "clientId": "...", "clientSecret": "..." },
"keycloak": { "clientId": "...", "clientSecret": "...", "issuerUrl": "https://keycloak.example.com/realms/myrealm" }
},
"allowedUsers": ["octocat", "user@example.com", "*@company.com"]
}
}
| Key | Required | Description |
|---|---|---|
auth.secret |
No | JWT signing secret (auto-generated if omitted) |
auth.providers |
Yes | Map of provider → { clientId, clientSecret, issuerUrl? } |
auth.allowedUsers |
No | Allowlist: usernames, emails, or *@domain wildcards. Empty = allow all |
Supported providers: github, google, keycloak, oidc (generic OIDC with issuerUrl).
Callback URL: register https://<tunnel-url>/auth/callback/<provider> in your OAuth provider settings. The tunnel URL is stable across restarts (reserved shares are auto-created).
Tunnel (zrok)
The dashboard auto-connects a zrok tunnel on start when tunnel.enabled is true. Install with brew install zrok (macOS) and run zrok enable <token> to enrol — the dashboard reads zrok's own config (~/.zrok2/environment.json), no keys are stored in the dashboard. Reserved shares provide persistent URLs across restarts.
OpenSpec background polling
Tune how often the server polls known directories for OpenSpec updates (openspec block):
| Key | Default | Range | Description |
|---|---|---|---|
pollIntervalSeconds |
30 |
5–3600 |
How often each known directory is polled |
maxConcurrentSpawns |
3 |
1–16 |
Cap on concurrent openspec CLI invocations |
changeDetection |
"mtime" |
"mtime" | "always" |
mtime skips unchanged proposals; always polls unconditionally |
jitterSeconds |
5 |
0–60 |
Per-directory phase offset so polls don't align on the same tick |
Live-reconfigurable via Settings → Advanced → "Background polling (OpenSpec)" or PUT /api/config — no server restart needed. See docs/architecture.md for the cost model.
Tool overrides
The dashboard resolves every external tool it calls (pi, pi-coding-agent, openspec, npm, node, tsx, git, zrok, pi-dashboard) through a single ToolRegistry. Each tool has an ordered strategy chain (override → managed install → bare-import / npm-global → PATH search), and every resolution records a diagnostic trail.
Inspecting and overriding — Settings → General → Tools shows every resolved tool, its source, and the trail. You can set a per-tool override path, rescan individually or all at once, and export the full diagnostic report.
Overrides file — ~/.pi/dashboard/tool-overrides.json:
{
"version": 1,
"overrides": {
"pi": { "path": "C:\\custom\\pi.cmd" },
"pi-coding-agent": { "path": "D:\\dev\\pi-coding-agent\\dist\\index.js" }
}
}
The file is deliberately separate from config.json so machine-specific paths don't follow a dotfiles sync. Invalid overrides (path doesn't exist) are recorded in the trail and the registry falls through to the next strategy automatically.
Usage
Auto-start (default)
The bridge extension automatically starts the dashboard server when pi launches if it's not already running. Disable with "autoStart": false in ~/.pi/dashboard/config.json.
Daemon mode
pi-dashboard start # background daemon (production)
pi-dashboard start --dev # dev mode (proxy to Vite, fallback to production build)
pi-dashboard stop # stop daemon (also kills stale port holders)
pi-dashboard restart # restart (production)
pi-dashboard restart --dev # restart in dev mode
pi-dashboard status # daemon status
Daemon stdout/stderr is logged to ~/.pi/dashboard/server.log (append mode with timestamped headers per start).
Manual server start
npx tsx packages/server/src/cli.ts
npx tsx packages/server/src/cli.ts --port 8000 --pi-port 9999
npx tsx packages/server/src/cli.ts --dev # proxy to Vite dev server
Graceful restart via API
# Restart in the same mode
curl -X POST http://localhost:8000/api/restart
# Switch to dev / production
curl -X POST http://localhost:8000/api/restart -H 'Content-Type: application/json' -d '{"dev":true}'
curl -X POST http://localhost:8000/api/restart -H 'Content-Type: application/json' -d '{"dev":false}'
# Check current mode
curl -s http://localhost:8000/api/health | jq .mode
The restart endpoint waits for the old server to exit, starts the new one, and verifies health. It works identically on Windows, macOS, and Linux (no sh / lsof / curl dependency).
Dev mode with production fallback
When started with --dev, the server proxies client requests to the Vite dev server for HMR. If Vite is not running, it automatically falls back to serving the production build from dist/client/:
pi-dashboard start --devalways works — no 502 errors- Start/stop Vite independently without restarting the dashboard
- Start Vite later and refresh the browser to get HMR
Dev build on reload
Set "devBuildOnReload": true in config.json for a one-command full-stack refresh:
/reload → build client → stop server → reload extension → auto-start fresh server
Blocks pi for ~2–5s during the build. The server shutdown affects all connected sessions — they auto-reconnect when one restarts the server.
Session spawning
Headless (default) — runs pi as a background process with no terminal attached. Interaction through the web UI.
tmux — runs pi inside a tmux session named pi-dashboard, each spawned session as a new window:
tmux attach -t pi-dashboard # attach
tmux list-windows -t pi-dashboard # list windows
# inside tmux: Ctrl-b n / p / w # next / prev / picker
Switch with "spawnStrategy": "tmux" in ~/.pi/dashboard/config.json.
Keyboard shortcuts in chat input
Bash-style history recall and per-session draft persistence:
| Key | Action |
|---|---|
Enter |
Send the prompt |
Shift+Enter |
Insert a newline |
ArrowUp |
Recall previous user prompt (caret on first line, no dropdown open). Repeat to walk back |
ArrowDown |
Walk forward through history (caret on last line). Past the newest entry, restores the in-progress draft |
Escape |
Restore in-progress draft and exit history mode; also cancels pending prompt / dismisses dropdown |
Tab / Enter in dropdown |
Accept the highlighted /command or @file suggestion |
Drafts (typed-but-unsent text) are persisted per session in localStorage under chat-draft:<sessionId> and survive navigation (Settings, OpenSpec preview, diffs, …) and full page reloads. Drafts never leak between sessions.
Recommended extensions
The dashboard integrates tightly with a small, curated set of pi extensions — for custom tool rendering, the Flow dashboard, and anthropic-messages protocol compatibility. The Electron wizard installs them in one go; the Packages tab and a top-of-page banner keep them discoverable afterwards.
| Extension | Source | Status | Unlocks |
|---|---|---|---|
pi-anthropic-messages |
git@github.com:BlackBeltTechnology/pi-anthropic-messages.git |
required | Tool calls on Claude-model Anthropic OAuth / 9Router cc/* / pi-model-proxy providers. Without it, tool calls fall back to Claude Code's built-in bash_ide sandbox and fail. |
@tintinweb/pi-subagents |
npm:@tintinweb/pi-subagents |
strongly suggested | Agent tool card UI, subagent activity badge, get_subagent_result / steer_subagent renderers |
pi-flows |
git@github.com:BlackBeltTechnology/pi-flows.git |
strongly suggested | Flow dashboard, role aliases (@planning, @coding, …), subagent / flow_write / flow_results / agent_write / ask_user / skill_read / finish tools |
pi-web-access |
npm:pi-web-access |
strongly suggested | web_search, code_search, fetch_content, get_search_content |
pi-agent-browser |
npm:pi-agent-browser |
optional | browser tool (open, snapshot, click, screenshot) |
Authoritative source: packages/shared/src/recommended-extensions.ts. Descriptions, versions, and installed-state are enriched live via GET /api/packages/recommended (offline fallback).
Troubleshooting
Dashboard server doesn't start
If pi launches but the dashboard never becomes reachable, inspect the launch log:
cat ~/.pi/dashboard/server.log # Linux / macOS
type %USERPROFILE%\.pi\dashboard\server.log # Windows
The log is append-mode with timestamped headers per start attempt, so previous crashes are preserved. Common issues:
ERR_UNSUPPORTED_ESM_URL_SCHEMEon Windows — fully fixed in 0.4.0+. The 0.2.10 release wrapped the--importloader position as afile://URL, but the entry-script position stayed a raw Windows path — which crashed Node on non-C:drives (A:\,B:\, …) because the drive-letter heuristic has gaps there. 0.4.0 routes all four server-spawn call sites through a singlespawnNodeScript/toFileUrlhelper that wraps both positions unconditionally, and a repo-level lint test prevents regression. Upgrade the package.Cannot find pi's TypeScript loader— pi is not installed globally. Runnpm install -g @mariozechner/pi-coding-agent.- Fastify crash at startup — you're on Node 22.0.0–22.17.x or 24.1.0–24.2.x which are affected by nodejs/node#58515. Upgrade to 22.18+ or 24.3+.
Port already in use
- Windows:
netstat -ano | findstr :8000thentaskkill /F /PID <pid> - Unix:
lsof -t -i :8000 | xargs kill - Or change
portin~/.pi/dashboard/config.json.
UI is empty or stuck after switching servers
Since the safe-server-switch release, switching servers via the header dropdown is transactional: the UI verifies the target through a short-lived staging WebSocket (5 s timeout) before clearing state or writing localStorage. If the target is unreachable, nothing changes — a toast appears and you stay on the previous server.
If the currently-active server drops for more than 3 s, a yellow “Disconnected from <host>. Retrying…” banner appears at the top with a Switch server button — use it to pick a reachable server.
You should no longer need to manually localStorage.removeItem("pi-dashboard-last-server") to recover from a bad switch. If you still get stuck, please file an issue.
Windows: sessions die when the dashboard restarts
Since the consolidate-windows-spawn-and-platform-handlers release, pi sessions on Windows survive dashboard restart, matching macOS/Linux behaviour. Previously, killing the dashboard process (Task Manager, Ctrl+C, /api/restart, crash) terminated every running pi session because the children were in the server's libuv kill-on-close Job Object. The fix uses detached: true so children are excluded from the parent's job.
If you previously relied on "closing the dashboard cleans everything up," use the per-session Force Kill action instead (or POST /api/session/:id/force-kill).
Windows Terminal tab doesn't appear
Install Windows Terminal (wt.exe) for tabbed interactive sessions — the dashboard prefers it over WSL tmux / headless when available. Windows 11 ships with it; on Windows 10 install from the Microsoft Store.
If wt.exe is on PATH but launching does nothing, check Settings → Apps → Advanced app settings → App execution aliases. If the "wt" alias is disabled, wt.exe is found but can't be executed. Enable the alias or uninstall/reinstall Windows Terminal.
Sessions don't group under my pinned folder
Since v0.3+, session grouping uses OS-aware path equality (platform/paths.ts). Sessions group correctly under a pinned folder even across trailing-separator, separator-style, or case differences (on Windows and macOS).
If you still see two entries for what should be one folder, the paths are likely on different Windows drives (A:\Foo and B:\Foo are different filesystems and never merge) — that's correct behaviour. If the paths really are the same filesystem, file an issue with both the pinned path (Settings → Tools → Export diagnostics) and the session cwd from /api/sessions.
Tool not found (pi / openspec / npm / …)
Open Settings → General → Tools, click the chevron next to the failing tool to see the full tried[] trail, then either (a) install the missing tool on PATH / in the managed location shown in the trail, or (b) set an explicit override via the row's path input. Hit Rescan to pick up the change without a server restart.
Recommended extensions: "Permission denied (publickey)"
pi-flows and pi-anthropic-messages install via SSH (git@github.com:…). If your system has no GitHub SSH key, set one up following GitHub's SSH docs, or substitute the equivalent HTTPS URL in the manifest if your fork is public.
Architecture
graph LR
subgraph "Per pi session"
B[Bridge Extension]
end
subgraph "Dashboard Server (Node.js)"
PG[Pi Gateway :9999]
BG[Browser Gateway :8000]
HTTP[HTTP / Static Files]
MEM[(In-Memory Store)]
JSON[(JSON Files)]
end
subgraph "Browser"
UI[React Web Client]
end
B <-->|WebSocket| PG
UI <-->|WebSocket| BG
UI -->|HTTP| HTTP
PG --- MEM
PG --- JSON
BG --- MEM
| Component | Location | Role |
|---|---|---|
| Bridge Extension | packages/extension/ |
Runs in every pi session. Forwards events, relays commands, auto-starts server, hosts PromptBus. |
| Dashboard Server | packages/server/ |
Aggregates events in-memory, persists metadata to JSON, serves the web client, manages terminals. |
| Web Client | packages/client/ |
React + Tailwind UI with real-time WebSocket updates. |
| Shared | packages/shared/ |
TypeScript types, protocols, and utilities shared across all packages. |
| Plugin Runtime | packages/dashboard-plugin-runtime/ |
Plugin loader, slot registry, slot consumers, plugin context API, and Vite plugin. |
First-party features can live as separate monorepo packages by declaring a pi-dashboard-plugin field in their package.json. The Vite plugin auto-discovers these manifests at build time and generates a static registry (packages/client/src/generated/plugin-registry.tsx) using named imports for tree-shaking. During server startup, loadServerEntries() dynamic-imports each plugin's server entry and invokes registerPlugin(ctx: ServerPluginContext). See docs/architecture.md for the full plugin data flow.
See docs/architecture.md for detailed data flows, reconnection logic, and persistence model.
Auto-start flow
flowchart TD
A[pi session starts] --> B[ensureConfig]
B --> C[loadConfig]
C --> D{TCP probe :piPort}
D -->|Port open| E[Connect to server]
D -->|Port closed| F{autoStart?}
F -->|false| G[Skip]
F -->|true| H[Spawn server detached]
H --> I["Notify: 🌐 Dashboard started"]
I --> E
The server is spawned detached (child_process.spawn with detached: true, unref()), so it outlives the pi session. Duplicate spawn attempts from concurrent pi sessions fail harmlessly with EADDRINUSE.
Monitoring
The health endpoint provides server and agent process metrics:
curl -s http://localhost:8000/api/health | jq
Returns:
mode—"dev"or"production"server.rss,server.heapUsed,server.heapTotal— server memoryserver.activeSessions,server.totalSessions— session countsagents[]— per-agent metrics (CPU%, RSS, heap, event loop max delay, system load)
Agent metrics are collected every 15s via heartbeats and include eventLoopMaxMs — useful for diagnosing connection drops during long-running operations.
Development
Commands
npm install # Install dependencies
npm test # Run all tests (vitest)
npm run test:watch # Watch mode
npm run build # Build web client (Vite)
npm run dev # Start Vite dev server (HMR)
npm run lint # Type-check (tsc --noEmit)
npm run reload # Reload all connected pi sessions
npm run reload:check # Type-check + reload all pi sessions
Typical local dev workflow
# Terminal 1: Dashboard server in dev mode
npx tsx packages/server/src/cli.ts --dev
# Terminal 2: Vite dev server (HMR for the web client)
npm run dev
# Terminal 3: pi with the bridge extension
pi -e packages/extension/src/bridge.ts # or just `pi` if installed
# Open http://localhost:8000 (server proxies to Vite for SPA routes + assets)
# Or http://localhost:3000 (Vite directly, proxies API/WS to :8000)
Deploy after changes
# After client changes (production mode)
npm run build
curl -X POST http://localhost:8000/api/restart
# After server changes (runs TypeScript directly, no build needed)
curl -X POST http://localhost:8000/api/restart
# After bridge extension changes
npm run reload
# Full rebuild (e.g., after pulling updates)
npm run build
curl -X POST http://localhost:8000/api/restart
npm run reload
Extension UI events
Your own extensions can broadcast UI events to the dashboard:
pi.events.emit("dashboard:ui", {
method: "notify",
message: "Deployment complete!",
level: "success",
});
Supported methods: confirm, select, input, notify.
Project structure
Monorepo with npm workspaces — top-level layout only. See AGENTS.md for the full file-by-file index.
packages/
├── shared/ # Shared TypeScript types, protocols, config, session-meta helpers
├── extension/ # Bridge extension (runs inside pi) — WS client, PromptBus, event forwarding, auto-start
├── server/ # Dashboard server — HTTP + dual WebSocket gateways, in-memory store, terminals, auth, tunnel
├── client/ # React + Tailwind web client — 80+ components, hooks, event reducer
└── electron/ # Electron desktop wrapper — wizard, system tray, auto-update, bundled Node.js
Building the Electron app
Prerequisites: Node.js 22.12+; platform-specific tools handled by Electron Forge automatically.
Native build (current platform)
npm run electron:build # Build for current platform & arch
npm run electron:build -- --arch x64 # Override architecture
npm run electron:build -- --skip-client # Skip client rebuild
Or step by step:
npm run build # Build web client
cd packages/electron
bash scripts/download-node.sh # Download Node.js for bundling
npm run make # Build installer
Output by platform:
| Platform | Output | Location |
|---|---|---|
| macOS | .dmg |
packages/electron/out/make/ |
| Linux | .deb + .AppImage |
packages/electron/out/make/ |
| Windows | .exe (NSIS) + .zip + portable .exe |
packages/electron/out/make/ |
Cross-platform builds (Docker)
From macOS or Linux, build installers for all platforms:
npm run electron:build -- --all # macOS (native) + Linux + Windows (Docker)
npm run electron:build -- --linux # Linux .deb + .AppImage only
npm run electron:build -- --windows # Windows .exe (NSIS) only
npm run electron:build -- --linux --windows # Both, skip native
Building both macOS DMGs locally (--mac-both)
On an Apple Silicon mac, produce both the arm64 and Intel x64 DMGs in one invocation:
npm run electron:build -- --mac-both
Requires Rosetta 2 (softwareupdate --install-rosetta --agree-to-license) so node-pty's x64 prebuilt binary can be unpacked during the cross-arch run. The script wipes per-arch caches between the two builds (resources/.last-arch sentinel) so back-to-back runs don't accidentally ship arm64 binaries inside an x64 DMG. Intel macs cannot cross-build arm64 locally (Rosetta is one-way) — use CI for arm64 validation.
Docker builds use a Node 22 Debian container with NSIS installed for Windows cross-compilation. Output goes to packages/electron/out/make/.
Electron dev mode
# Start the dashboard server and Vite dev server first
pi-dashboard start --dev
npm run dev
# Then launch Electron pointing at the dev server
cd packages/electron
npm run start:dev
Regenerating icons
All platform icon variants are generated from the master icon at packages/electron/resources/icon.png:
cd packages/electron
npm run icons # Generates .icns (macOS), .ico (Windows), and resized PNGs
CI/CD & releasing
See docs/release-process.md for the full cut-a-release workflow.
Continuous integration
Every push to develop and every pull request against develop triggers ci.yml:
npm ci— install dependenciesnpm run lint— type checknpm test— run testsnpm run build— build web client
Releasing
The publish workflow (publish.yml) triggers on v* tags:
npm version patch # or minor / major
git push --follow-tags
This runs CI, publishes to npm with --provenance for supply-chain transparency, and builds Electron installers for all platforms on native runners:
| Runner | Platform | Outputs |
|---|---|---|
macos-14 |
macOS arm64 | .dmg (Apple Silicon) |
macos-15-intel |
macOS x64 | .dmg (Intel; last GitHub-hosted x86_64 image, EOL 2027-08) |
ubuntu-latest |
Linux x64 | .deb + .AppImage |
ubuntu-24.04-arm |
Linux arm64 | .deb |
windows-latest |
Windows x64 | .exe (NSIS) + .zip + portable |
windows-latest |
Windows arm64 | .zip + portable (x64 Node.js via WoW64) |
All artifacts are uploaded to a draft GitHub Release. Release notes are extracted automatically from the matching ## [<version>] section of CHANGELOG.md.
Trusted Publisher (OIDC) setup
The publish workflow uses npm Trusted Publishers over OIDC — no NPM_TOKEN secret required. Short-lived, workflow-scoped credentials are exchanged between GitHub and npm at publish time, and every release carries automatic npm provenance tying the published artifact to the exact workflow run.
Requirements (already configured in publish.yml):
permissions:
contents: write # draft GitHub Release + tag push
id-token: write # OIDC token exchange with the npm registry
environment: npm-publish
Trusted Publishing requires npm CLI ≥ 11.5.1. The workflow upgrades npm automatically (npm install -g npm@latest) before publishing.
One-time npm-side setup — repeat once per published package (five scoped workspaces; @blackbelt-technology/pi-dashboard-electron is private and skipped):
| Package |
|---|
@blackbelt-technology/pi-agent-dashboard (root) |
@blackbelt-technology/pi-dashboard-shared |
@blackbelt-technology/pi-dashboard-extension |
@blackbelt-technology/pi-dashboard-server |
@blackbelt-technology/pi-dashboard-web |
For each:
- Go to npmjs.com → the package → Settings → Trusted Publisher → GitHub Actions
- Fill in:
- Organization or user:
BlackBeltTechnology - Repository:
pi-agent-dashboard - Workflow filename:
publish.yml(filename only, not the full path) - Environment name:
npm-publish(must match theenvironment:field in the workflow)
- Organization or user:
- Save
GitHub Environment (recommended) — configures an optional human-approval gate on every release:
- GitHub repo → Settings → Environments → New environment → name
npm-publish - Optionally add required reviewers and/or deployment branch/tag protection rules (e.g. restrict to
v*tags)
No secrets to rotate, no tokens to leak.
License
MIT