//! `hivectl open ` — resolve a hive surface URL from the //! daemon, print it, and best-effort `xdg-open` it. use std::path::Path; use anyhow::{Context as _, Result}; use crate::cli::OpenTarget; use crate::util::query_hive_urls; /// `open ` — resolve the surface URL from the daemon, /// print it, then best-effort `xdg-open` it. Printing is the reliable /// core (headless / SSH hosts where no browser opener exists); the open /// is convenience on top, so a missing/failed `xdg-open` is not an error. pub(crate) async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> { // The connect error is already actionable (`client::request` classifies // it), so propagate it rather than restating a guess about the cause. let urls = query_hive_urls(socket) .await .context("could not read this hive's URLs from the daemon")? .context("the daemon reported no URLs — `services.hyperhive.domain` is unset")?; let (url, hint) = match target { OpenTarget::Home => ( urls.home, "the dashboard URL needs `services.hyperhive.domain` to be set", ), OpenTarget::Forge => ( urls.forge, "the public forge URL needs `services.hyperhive.forge.behindGateway = true`", ), OpenTarget::Matrix => ( urls.matrix, "the matrix GUI URL needs `services.hyperhive.swarm.matrix.gui.enable = true`", ), }; let url = url.with_context(|| format!("no URL available for this surface — {hint}"))?; println!("{url}"); // Best-effort: many hosts are headless, so a missing opener or a // non-zero exit is fine — the URL is already printed. match std::process::Command::new("xdg-open").arg(&url).status() { Ok(status) if status.success() => {} Ok(status) => eprintln!("note: xdg-open exited with {status} (URL printed above)"), Err(e) => eprintln!("note: could not run xdg-open ({e}) (URL printed above)"), } Ok(()) }