From 1f8757b2156794e305105f6956c159322b3bd95f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 6 Apr 2026 10:45:08 -0500 Subject: [PATCH] embed dashboard (#1169) * embed dashboard * docs * fmt --- .github/workflows/release.yml | 27 ++----- README.md | 3 - cli/Cargo.lock | 63 +++++++++++++++ cli/Cargo.toml | 1 + cli/build.rs | 17 ++++ cli/src/install.rs | 121 ----------------------------- cli/src/main.rs | 4 - cli/src/native/stream/dashboard.rs | 23 +----- cli/src/native/stream/http.rs | 64 +++++---------- cli/src/native/stream/mod.rs | 22 ------ cli/src/native/stream/websocket.rs | 16 +--- cli/src/output.rs | 7 +- docs/src/app/commands/page.mdx | 1 - docs/src/app/dashboard/page.mdx | 16 +--- skills/agent-browser/SKILL.md | 3 - 15 files changed, 116 insertions(+), 272 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c72fab..ed60f13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,6 +121,9 @@ jobs: - name: Sync version run: pnpm run version:sync + - name: Build dashboard + run: pnpm --filter dashboard build + - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable with: @@ -288,26 +291,6 @@ jobs: fi echo "Found $BINARY_COUNT binaries" - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build dashboard - run: pnpm --filter dashboard build - - - name: Create dashboard.zip - run: cd packages/dashboard/out && zip -r ../../../dashboard.zip . - - name: Extract changelog entry run: | VERSION="${{ needs.check-release.outputs.version }}" @@ -327,13 +310,13 @@ jobs: if gh release view "$TAG" &>/dev/null; then echo "Release $TAG already exists, uploading assets..." - gh release upload "$TAG" bin/agent-browser-* dashboard.zip --clobber + gh release upload "$TAG" bin/agent-browser-* --clobber else echo "Creating release $TAG..." gh release create "$TAG" \ --title "$TAG" \ --notes-file /tmp/release-notes.md \ - bin/agent-browser-* dashboard.zip + bin/agent-browser-* fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 9837d92..f1836fc 100644 --- a/README.md +++ b/README.md @@ -639,9 +639,6 @@ This is useful for multimodal AI models that can reason about visual layout, unl Monitor agent-browser sessions in real time with a local web dashboard showing a live viewport and command activity feed. ```bash -# Install the dashboard (one time) -agent-browser dashboard install - # Start the dashboard server (runs in background on port 4848) agent-browser dashboard start agent-browser dashboard start --port 8080 # Custom port diff --git a/cli/Cargo.lock b/cli/Cargo.lock index a6a334e..fe93e40 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -59,6 +59,7 @@ dependencies = [ "image", "libc", "reqwest", + "rust-embed", "serde", "serde_json", "sha2", @@ -1738,6 +1739,40 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -1791,6 +1826,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "semver" version = "1.0.27" @@ -2340,6 +2384,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2523,6 +2577,15 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-core" version = "0.62.2" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 94ad28d..5899c81 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -34,6 +34,7 @@ hmac = "0.12" hex = "0.4" chrono = "0.4" urlencoding = "2" +rust-embed = "8" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/cli/build.rs b/cli/build.rs index 6e234d9..7bc9cf1 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -3,7 +3,24 @@ use std::env; use std::fs; use std::path::Path; +/// Ensure `packages/dashboard/out/` exists so `rust-embed` doesn't fail during +/// Rust-only dev builds where the dashboard hasn't been built. The placeholder +/// `index.html` is only written when the directory is completely absent. +fn ensure_dashboard_dir() { + let dashboard_out = Path::new("../packages/dashboard/out"); + println!("cargo:rerun-if-changed=../packages/dashboard/out"); + if !dashboard_out.join("index.html").exists() { + let _ = fs::create_dir_all(dashboard_out); + let _ = fs::write( + dashboard_out.join("index.html"), + "

Dashboard not built. Run: cd packages/dashboard && pnpm build

\n", + ); + } +} + fn main() { + ensure_dashboard_dir(); + let protocol_dir = Path::new("cdp-protocol"); let out_dir = env::var("OUT_DIR").unwrap(); let out_path = Path::new(&out_dir).join("cdp_generated.rs"); diff --git a/cli/src/install.rs b/cli/src/install.rs index 1d5a1e3..467c9c4 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -772,83 +772,6 @@ fn package_exists_apt(pkg: &str) -> bool { .unwrap_or(false) } -// --------------------------------------------------------------------------- -// Dashboard install -// --------------------------------------------------------------------------- - -pub fn get_dashboard_dir() -> PathBuf { - dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".agent-browser") - .join("dashboard") -} - -const DASHBOARD_VERSION: &str = env!("CARGO_PKG_VERSION"); - -fn dashboard_download_url() -> String { - format!( - "https://github.com/vercel-labs/agent-browser/releases/download/v{}/dashboard.zip", - DASHBOARD_VERSION - ) -} - -pub fn run_dashboard_install() { - println!("{}", color::cyan("Installing dashboard...")); - - let dest = get_dashboard_dir(); - - if dest.join("index.html").exists() { - println!( - "{} Dashboard is already installed at {}", - color::success_indicator(), - dest.display() - ); - return; - } - - let url = dashboard_download_url(); - println!(" Downloading dashboard v{}", DASHBOARD_VERSION); - println!(" {}", url); - - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap_or_else(|e| { - eprintln!( - "{} Failed to create runtime: {}", - color::error_indicator(), - e - ); - exit(1); - }); - - let bytes = match rt.block_on(download_bytes(&url)) { - Ok(b) => b, - Err(e) => { - eprintln!("{} {}", color::error_indicator(), e); - eprintln!(" The dashboard may not be available for this version yet."); - eprintln!(" You can build it locally: cd packages/dashboard && pnpm build"); - exit(1); - } - }; - - match extract_dashboard_zip(bytes, &dest) { - Ok(()) => { - println!( - "{} Dashboard v{} installed successfully", - color::success_indicator(), - DASHBOARD_VERSION - ); - println!(" Location: {}", dest.display()); - } - Err(e) => { - let _ = fs::remove_dir_all(&dest); - eprintln!("{} {}", color::error_indicator(), e); - exit(1); - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -1037,47 +960,3 @@ mod tests { ); } } - -fn extract_dashboard_zip(bytes: Vec, dest: &Path) -> Result<(), String> { - fs::create_dir_all(dest).map_err(|e| format!("Failed to create directory: {}", e))?; - - let cursor = io::Cursor::new(bytes); - let mut archive = - zip::ZipArchive::new(cursor).map_err(|e| format!("Failed to read zip archive: {}", e))?; - - for i in 0..archive.len() { - let mut file = archive - .by_index(i) - .map_err(|e| format!("Failed to read zip entry: {}", e))?; - - let enclosed = match file.enclosed_name() { - Some(name) => name.to_owned(), - None => continue, - }; - let rel_path = enclosed.to_string_lossy().to_string(); - - if rel_path.is_empty() || file.is_dir() { - if file.is_dir() { - let out_dir = dest.join(&rel_path); - let _ = fs::create_dir_all(&out_dir); - } - continue; - } - - let out_path = dest.join(&rel_path); - if !out_path.starts_with(dest) { - continue; - } - - if let Some(parent) = out_path.parent() { - fs::create_dir_all(parent) - .map_err(|e| format!("Failed to create parent dir {}: {}", parent.display(), e))?; - } - let mut out_file = fs::File::create(&out_path) - .map_err(|e| format!("Failed to create file {}: {}", out_path.display(), e))?; - io::copy(&mut file, &mut out_file) - .map_err(|e| format!("Failed to write {}: {}", out_path.display(), e))?; - } - - Ok(()) -} diff --git a/cli/src/main.rs b/cli/src/main.rs index 31d4ab3..eb9811e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -654,10 +654,6 @@ fn main() { // Handle dashboard subcommand if clean.first().map(|s| s.as_str()) == Some("dashboard") { match clean.get(1).map(|s| s.as_str()) { - Some("install") => { - install::run_dashboard_install(); - return; - } Some("start") | None => { let port = clean .iter() diff --git a/cli/src/native/stream/dashboard.rs b/cli/src/native/stream/dashboard.rs index 0bde1e9..a19c459 100644 --- a/cli/src/native/stream/dashboard.rs +++ b/cli/src/native/stream/dashboard.rs @@ -1,16 +1,13 @@ use serde_json::{json, Value}; -use std::path::PathBuf; -use std::sync::Arc; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; use crate::connection::get_socket_dir; -use crate::install::get_dashboard_dir; use super::chat::{chat_status_json, handle_chat_request, handle_models_request}; use super::discovery::discover_sessions; -use super::http::{serve_static_file, CORS_HEADERS, DASHBOARD_NOT_INSTALLED_HTML}; +use super::http::{serve_embedded_file, CORS_HEADERS}; pub async fn run_dashboard_server(port: u16) { let addr = format!("127.0.0.1:{}", port); @@ -22,23 +19,17 @@ pub async fn run_dashboard_server(port: u16) { } }; - let dashboard_dir: Arc = Arc::from(get_dashboard_dir()); - loop { let Ok((stream, _addr)) = listener.accept().await else { break; }; - let dash_dir = dashboard_dir.clone(); tokio::spawn(async move { - handle_dashboard_connection(stream, dash_dir).await; + handle_dashboard_connection(stream).await; }); } } -async fn handle_dashboard_connection( - mut stream: tokio::net::TcpStream, - dashboard_dir: Arc, -) { +async fn handle_dashboard_connection(mut stream: tokio::net::TcpStream) { use tokio::io::AsyncReadExt; let mut buf = vec![0u8; 8192]; @@ -118,14 +109,8 @@ async fn handle_dashboard_connection( "application/json; charset=utf-8", chat_status_json().into_bytes(), ) - } else if dashboard_dir.join("index.html").exists() { - serve_static_file(&dashboard_dir, path) } else { - ( - "200 OK", - "text/html; charset=utf-8", - DASHBOARD_NOT_INSTALLED_HTML.as_bytes().to_vec(), - ) + serve_embedded_file(path) }; let response = format!( diff --git a/cli/src/native/stream/http.rs b/cli/src/native/stream/http.rs index 07e9947..584113d 100644 --- a/cli/src/native/stream/http.rs +++ b/cli/src/native/stream/http.rs @@ -1,5 +1,5 @@ +use rust_embed::Embed; use serde_json::{json, Value}; -use std::path::Path; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -13,6 +13,10 @@ use super::chat::{chat_status_json, handle_chat_request, handle_models_request}; use super::dashboard::spawn_session; use super::discovery::discover_sessions; +#[derive(Embed)] +#[folder = "../packages/dashboard/out/"] +struct DashboardAssets; + pub(super) const CORS_HEADERS: &str = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n"; /// Build CORS headers that reflect the request origin only when it passes @@ -42,7 +46,6 @@ fn parse_origin(peeked: &[u8]) -> Option { pub(super) async fn handle_http_request( mut stream: tokio::net::TcpStream, peeked: &[u8], - dashboard_dir: Option<&Path>, last_tabs: &Arc>>, last_engine: &Arc>, session_name: &str, @@ -163,14 +166,7 @@ pub(super) async fn handle_http_request( chat_status_json().into_bytes(), ) } else { - match dashboard_dir { - Some(dir) => serve_static_file(dir, path), - None => ( - "200 OK", - "text/html; charset=utf-8", - DASHBOARD_NOT_INSTALLED_HTML.as_bytes().to_vec(), - ), - } + serve_embedded_file(path) }; let response = format!( @@ -282,25 +278,19 @@ pub(super) async fn relay_command_to_daemon( Ok(response_line.trim().to_string()) } -pub(super) fn serve_static_file( - dir: &Path, - url_path: &str, -) -> (&'static str, &'static str, Vec) { +pub(super) fn serve_embedded_file(url_path: &str) -> (&'static str, &'static str, Vec) { let clean = url_path.trim_start_matches('/'); - let file_path = if clean.is_empty() { - dir.join("index.html") + let key = if clean.is_empty() { + "index.html" } else { - let joined = dir.join(clean); - if joined.is_file() { - joined - } else { - dir.join("index.html") - } + clean }; - match std::fs::read(&file_path) { - Ok(content) => { - let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or(""); + let file = DashboardAssets::get(key).or_else(|| DashboardAssets::get("index.html")); + + match file { + Some(content) => { + let ext = key.rsplit('.').next().unwrap_or(""); let ct = match ext { "html" => "text/html; charset=utf-8", "js" => "application/javascript; charset=utf-8", @@ -309,31 +299,17 @@ pub(super) fn serve_static_file( "svg" => "image/svg+xml", "png" => "image/png", "ico" => "image/x-icon", + "woff2" => "font/woff2", + "woff" => "font/woff", + "txt" => "text/plain; charset=utf-8", _ => "application/octet-stream", }; - ("200 OK", ct, content) + ("200 OK", ct, content.data.to_vec()) } - Err(_) => ( + None => ( "404 Not Found", "text/html; charset=utf-8", b"

404 Not Found

".to_vec(), ), } } - -pub(super) const DASHBOARD_NOT_INSTALLED_HTML: &str = r#" - -agent-browser - - - -
-

Dashboard not installed

-

Run agent-browser dashboard install to download the dashboard.

-
- -"#; diff --git a/cli/src/native/stream/mod.rs b/cli/src/native/stream/mod.rs index 81aaa5f..cd16c83 100644 --- a/cli/src/native/stream/mod.rs +++ b/cli/src/native/stream/mod.rs @@ -9,7 +9,6 @@ pub use cdp_loop::{ack_screencast_frame, start_screencast, stop_screencast}; pub use dashboard::run_dashboard_server; use serde_json::{json, Value}; -use std::path::PathBuf; use std::sync::Arc; use tokio::net::TcpListener; @@ -55,7 +54,6 @@ pub struct StreamServer { screencasting: Arc>, viewport_width: Arc>, viewport_height: Arc>, - dashboard_dir: Option, last_tabs: Arc>>, last_engine: Arc>, last_frame: Arc>>, @@ -91,16 +89,6 @@ impl StreamServer { Self::start_inner(preferred_port, client_slot, session_id, allow_port_fallback).await } - /// Resolve the dashboard directory if it exists. - fn resolve_dashboard_dir() -> Option { - let dir = dirs::home_dir()?.join(".agent-browser").join("dashboard"); - if dir.join("index.html").exists() { - Some(dir) - } else { - None - } - } - /// Notify the background CDP listener that the client has changed (browser launched/closed). pub fn notify_client_changed(&self) { self.client_notify.notify_one(); @@ -181,8 +169,6 @@ impl StreamServer { .map_err(|e| format!("Failed to get stream address: {}", e))?; let port = actual_addr.port(); - let dashboard_dir = Self::resolve_dashboard_dir(); - let (frame_tx, _) = broadcast::channel::(64); let client_count = Arc::new(Mutex::new(0usize)); let client_notify = Arc::new(Notify::new()); @@ -205,7 +191,6 @@ impl StreamServer { let vw_clone = viewport_width.clone(); let vh_clone = viewport_height.clone(); - let dashboard_dir_clone = dashboard_dir.clone(); let last_tabs_clone = last_tabs.clone(); let last_engine_clone = last_engine.clone(); let last_frame_clone = last_frame.clone(); @@ -223,7 +208,6 @@ impl StreamServer { cdp_session_clone, vw_clone, vh_clone, - dashboard_dir_clone, last_tabs_clone, last_engine_clone, last_frame_clone, @@ -277,7 +261,6 @@ impl StreamServer { screencasting, viewport_width, viewport_height, - dashboard_dir, last_tabs, last_engine, last_frame, @@ -435,11 +418,6 @@ impl StreamServer { }); let _ = self.frame_tx.send(msg.to_string()); } - - /// Whether the dashboard directory is available. - pub fn has_dashboard(&self) -> bool { - self.dashboard_dir.is_some() - } } pub(crate) fn timestamp_ms() -> u64 { diff --git a/cli/src/native/stream/websocket.rs b/cli/src/native/stream/websocket.rs index 920681e..7aecc34 100644 --- a/cli/src/native/stream/websocket.rs +++ b/cli/src/native/stream/websocket.rs @@ -1,6 +1,5 @@ use serde_json::{json, Value}; use std::net::SocketAddr; -use std::path::PathBuf; use std::sync::Arc; use futures_util::{SinkExt, StreamExt}; @@ -24,7 +23,6 @@ pub(super) async fn accept_loop( cdp_session_id: Arc>>, viewport_width: Arc>, viewport_height: Arc>, - dashboard_dir: Option, last_tabs: Arc>>, last_engine: Arc>, last_frame: Arc>>, @@ -32,7 +30,6 @@ pub(super) async fn accept_loop( mut shutdown_rx: watch::Receiver, session_name: String, ) { - let dashboard_dir = dashboard_dir.map(Arc::from); let session_name: Arc = Arc::from(session_name); loop { tokio::select! { @@ -53,7 +50,6 @@ pub(super) async fn accept_loop( let cdp_session_id = cdp_session_id.clone(); let vw = viewport_width.clone(); let vh = viewport_height.clone(); - let dd = dashboard_dir.clone(); let lt = last_tabs.clone(); let le = last_engine.clone(); let lf = last_frame.clone(); @@ -73,7 +69,6 @@ pub(super) async fn accept_loop( cdp_session_id, vw, vh, - dd, lt, le, lf, @@ -112,7 +107,6 @@ async fn handle_connection( cdp_session_id: Arc>>, viewport_width: Arc>, viewport_height: Arc>, - dashboard_dir: Option>, last_tabs: Arc>>, last_engine: Arc>, last_frame: Arc>>, @@ -148,15 +142,7 @@ async fn handle_connection( ) .await; } else { - handle_http_request( - stream, - &buf[..n], - dashboard_dir.as_deref().map(|p| p.as_path()), - &last_tabs, - &last_engine, - &session_name, - ) - .await; + handle_http_request(stream, &buf[..n], &last_tabs, &last_engine, &session_name).await; } } diff --git a/cli/src/output.rs b/cli/src/output.rs index c444f65..0595257 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -2399,15 +2399,15 @@ Examples: r##" agent-browser dashboard - Observability dashboard -Usage: agent-browser dashboard [start|stop|install] [options] +Usage: agent-browser dashboard [start|stop] [options] Manage the observability dashboard, a local web UI that shows live browser viewports and command activity feeds for all sessions. +The dashboard is bundled into the binary and requires no separate install. Subcommands: start [--port ] Start the dashboard server (default port: 4848) stop Stop the dashboard server - install Download and install the dashboard to ~/.agent-browser/dashboard/ Running 'agent-browser dashboard' with no subcommand is equivalent to 'dashboard start'. @@ -2421,7 +2421,6 @@ Global Options: --json Output as JSON Examples: - agent-browser dashboard install agent-browser dashboard start agent-browser dashboard start --port 8080 agent-browser dashboard stop @@ -2842,7 +2841,7 @@ Setup: install Install browser binaries install --with-deps Also install system dependencies (Linux) upgrade Upgrade to the latest version - dashboard install Install the observability dashboard + dashboard start Start the observability dashboard profiles List available Chrome profiles Snapshot Options: diff --git a/docs/src/app/commands/page.mdx b/docs/src/app/commands/page.mdx index de6dfce..41fd713 100644 --- a/docs/src/app/commands/page.mdx +++ b/docs/src/app/commands/page.mdx @@ -343,7 +343,6 @@ agent-browser --profile Default open https://gmail.com # Reuse a profile's logi agent-browser dashboard [start] # Start the dashboard server (default port: 4848) agent-browser dashboard start --port # Start on a specific port agent-browser dashboard stop # Stop the dashboard server -agent-browser dashboard install # Install the dashboard files ``` ## Chat diff --git a/docs/src/app/dashboard/page.mdx b/docs/src/app/dashboard/page.mdx index 0026383..9846696 100644 --- a/docs/src/app/dashboard/page.mdx +++ b/docs/src/app/dashboard/page.mdx @@ -2,19 +2,9 @@ Monitor agent-browser sessions in real time with a local web dashboard showing a live browser viewport and command activity feed. -## Install - -Download the dashboard once: - -```bash -agent-browser dashboard install -``` - -This downloads the dashboard to `~/.agent-browser/dashboard/` and is served directly by the daemon when streaming is enabled. - ## Usage -Start the dashboard server and open any session -- it appears automatically: +The dashboard is bundled into the binary and requires no separate install. Start the server and open any session: ```bash agent-browser dashboard start @@ -132,9 +122,7 @@ The dashboard is a Next.js static export (`output: 'export'`) that produces plai pnpm build:dashboard ``` -The built files are served by the daemon's stream server on the same port used for WebSocket connections. Plain HTTP requests serve the dashboard, while WebSocket upgrade requests are handled as before. - -When the dashboard is not installed, visiting the HTTP endpoint shows instructions to run `agent-browser dashboard install`. +The dashboard is embedded into the CLI binary at compile time using `rust-embed`. Plain HTTP requests serve the embedded dashboard assets, while WebSocket upgrade requests are handled as before. ## AI Chat diff --git a/skills/agent-browser/SKILL.md b/skills/agent-browser/SKILL.md index 19176ba..8989e61 100644 --- a/skills/agent-browser/SKILL.md +++ b/skills/agent-browser/SKILL.md @@ -789,9 +789,6 @@ Lightpanda does not support `--extension`, `--profile`, `--state`, or `--allow-f The dashboard is a standalone background server that shows live browser viewports, command activity, and console output for all sessions. ```bash -# Install the dashboard once -agent-browser dashboard install - # Start the dashboard server (background, port 4848) agent-browser dashboard start