browser_control/cli/
start.rs1use anyhow::{anyhow, Context, Result};
4use serde::Serialize;
5use std::path::PathBuf;
6
7use crate::cli::output::print_json;
8use crate::detect::{self, Engine, Installed, Kind};
9use crate::launch::{self, LaunchOpts};
10use crate::paths;
11use crate::registry::{self, BrowserRow, Registry};
12
13#[derive(Debug, Serialize)]
14pub struct StartResult {
15 pub name: String,
16 pub kind: Kind,
17 pub pid: u32,
18 pub engine: Engine,
19 pub endpoint: String,
20 pub profile: PathBuf,
21 pub headless: bool,
22 pub started_at: String,
23 pub reused: bool,
24}
25
26pub async fn run(browser: Option<String>, headless: bool, json: bool) -> Result<()> {
27 let installed = detect::list_installed();
28 if installed.is_empty() {
29 anyhow::bail!("no supported browsers installed; run `browser-control list-installed`");
30 }
31
32 let resolved_kind: Kind = match browser.as_deref() {
33 None => first_chromium_or_first(&installed)
34 .ok_or_else(|| anyhow!("no chromium-based browser installed"))?,
35 Some(s) => Kind::parse(s).ok_or_else(|| {
36 anyhow!("unknown browser kind `{s}`; valid: chrome, edge, chromium, brave, firefox")
37 })?,
38 };
39
40 let installed_match = installed
41 .iter()
42 .find(|i| i.kind == resolved_kind)
43 .cloned()
44 .ok_or_else(|| {
45 anyhow!(
46 "browser `{}` is not installed on this machine",
47 resolved_kind.as_str()
48 )
49 })?;
50
51 let registry = Registry::open()?;
52 if let Some(row) = registry.first_alive_by_kind(resolved_kind)? {
53 let res = to_result(&row, true);
54 emit(&res, json)?;
55 return Ok(());
56 }
57
58 let name = registry::naming::generate_default(resolved_kind, ®istry)?;
59 let profile_dir = paths::default_profile_dir(resolved_kind)?;
60 std::fs::create_dir_all(&profile_dir).context("creating profile directory")?;
61 let opts = LaunchOpts {
62 headless,
63 profile_dir: profile_dir.clone(),
64 };
65 let handle = launch::launch(&installed_match, opts)
66 .await
67 .with_context(|| format!("launching {}", installed_match.executable.display()))?;
68
69 let row = BrowserRow {
70 name: name.clone(),
71 kind: resolved_kind,
72 engine: handle.engine,
73 pid: handle.pid,
74 endpoint: handle.endpoint.clone(),
75 port: handle.port,
76 profile_dir: handle.profile_dir.clone(),
77 executable: installed_match.executable.clone(),
78 headless,
79 started_at: registry::now_iso8601(),
80 };
81 registry.insert(&row)?;
82 let _pid = handle.forget();
83
84 let res = to_result(&row, false);
85 emit(&res, json)?;
86 Ok(())
87}
88
89fn first_chromium_or_first(installed: &[Installed]) -> Option<Kind> {
90 installed
91 .iter()
92 .find(|i| i.kind.is_chromium())
93 .map(|i| i.kind)
94 .or_else(|| installed.first().map(|i| i.kind))
95}
96
97fn to_result(row: &BrowserRow, reused: bool) -> StartResult {
98 StartResult {
99 name: row.name.clone(),
100 kind: row.kind,
101 pid: row.pid,
102 engine: row.engine,
103 endpoint: row.endpoint.clone(),
104 profile: row.profile_dir.clone(),
105 headless: row.headless,
106 started_at: row.started_at.clone(),
107 reused,
108 }
109}
110
111fn emit(res: &StartResult, json: bool) -> Result<()> {
112 if json {
113 print_json(&mut std::io::stdout(), res)?;
114 } else {
115 let reused = if res.reused { " (reused)" } else { "" };
116 println!("Started {}{}", res.name, reused);
117 println!(" kind: {}", res.kind.as_str());
118 println!(" pid: {}", res.pid);
119 println!(" engine: {:?}", res.engine);
120 println!(" endpoint: {}", res.endpoint);
121 println!(" profile: {}", res.profile.display());
122 }
123 Ok(())
124}