use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use serde_json::json;
use crate::cli::format::TableOrJson;
use crate::daemon::client::DaemonClient;
use crate::daemon::protocol::StatusReport;
use crate::daemon::server;
#[derive(Parser)]
pub struct StatusCommand {
#[arg(long, value_name = "PATH")]
pub socket: Option<PathBuf>,
#[arg(short = 'o', long, value_enum, default_value_t = TableOrJson::Table)]
pub output: TableOrJson,
#[arg(long, hide = true)]
pub json: bool,
}
impl StatusCommand {
pub async fn execute(mut self) -> Result<()> {
if self.json {
eprintln!("warning: --json is deprecated; use -o/--output json instead");
self.output = TableOrJson::Json;
}
let json_out = self.output == TableOrJson::Json;
let socket_path = server::resolve_socket(self.socket)?;
match DaemonClient::new(&socket_path).status().await {
Ok(report) => print_running(json_out, &report),
Err(_) => print_not_running(json_out),
}
}
}
fn print_running(json_out: bool, report: &StatusReport) -> Result<()> {
if json_out {
println!("{}", serde_json::to_string_pretty(report)?);
return Ok(());
}
println!("daemon: running ({})", describe_status(report));
if let Some(rate_limit) = &report.github_rate_limit {
let line = rate_limit.summary_line();
if !line.is_empty() {
println!(" github rate limit: {line}");
}
}
if report.services.is_empty() {
println!(" (no services registered)");
}
for svc in &report.services {
let health = if svc.healthy { "ok" } else { "unhealthy" };
println!(" {:<16} {:<10} {}", svc.name, health, svc.summary);
}
super::warn_version_mismatch(report);
Ok(())
}
fn describe_status(report: &StatusReport) -> String {
let mut parts = match &report.version {
Some(version) => vec![format!("v{version}")],
None => vec!["version unknown".to_string()],
};
if let Some(commit) = &report.provenance.commit {
parts.push(commit.clone());
}
if let Some(built) = &report.provenance.build_timestamp {
parts.push(format!("built {built}"));
}
if report.provenance.dirty == Some(true) {
parts.push("dirty".to_string());
}
parts.join(", ")
}
fn print_not_running(json_out: bool) -> Result<()> {
if json_out {
println!(
"{}",
serde_json::to_string_pretty(&json!({ "running": false }))?
);
} else {
println!("daemon: not running");
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::daemon::service::ServiceStatus;
fn report(version: Option<&str>) -> StatusReport {
StatusReport {
services: vec![ServiceStatus {
name: "browser-bridge".to_string(),
healthy: true,
summary: "no tab connected".to_string(),
detail: serde_json::Value::Null,
}],
version: version.map(str::to_string),
..StatusReport::default()
}
}
#[test]
fn print_running_renders_table_and_json_with_or_without_a_version() {
print_running(false, &report(Some("1.2.3"))).unwrap();
print_running(false, &report(None)).unwrap();
print_running(true, &report(Some("1.2.3"))).unwrap();
print_running(
false,
&StatusReport {
services: vec![],
version: Some("1.2.3".to_string()),
..StatusReport::default()
},
)
.unwrap();
}
#[test]
fn print_running_renders_the_github_rate_limit_line() {
use crate::github_rate_limit::{RateLimitResource, RateLimitSnapshot};
let res = |used: u64| RateLimitResource {
used,
limit: 5000,
remaining: 5000 - used,
percent: used as f64 / 5000.0 * 100.0,
reset: 1_700_000_000,
};
let mut rep = report(Some("1.2.3"));
rep.github_rate_limit = Some(RateLimitSnapshot {
graphql: Some(res(100)),
core: Some(res(27)),
search: None,
});
print_running(false, &rep).unwrap();
print_running(true, &rep).unwrap();
rep.github_rate_limit = Some(RateLimitSnapshot {
graphql: Some(res(4500)), core: Some(res(27)),
search: None,
});
print_running(false, &rep).unwrap();
}
#[test]
fn describe_status_appends_provenance_pieces_when_present() {
use crate::build_info::Provenance;
assert_eq!(describe_status(&report(Some("0.36.0"))), "v0.36.0");
assert_eq!(describe_status(&report(None)), "version unknown");
let dirty = StatusReport {
version: Some("0.36.0".to_string()),
provenance: Provenance {
commit: Some("a6d304fd".to_string()),
build_timestamp: Some("2026-07-20T05:33:17+00:00".to_string()),
dirty: Some(true),
..Provenance::default()
},
..StatusReport::default()
};
assert_eq!(
describe_status(&dirty),
"v0.36.0, a6d304fd, built 2026-07-20T05:33:17+00:00, dirty"
);
let clean = StatusReport {
version: Some("0.36.0".to_string()),
provenance: Provenance {
commit: Some("a6d304fd".to_string()),
dirty: Some(false),
..Provenance::default()
},
..StatusReport::default()
};
assert_eq!(describe_status(&clean), "v0.36.0, a6d304fd");
}
#[test]
fn print_not_running_renders_table_and_json() {
print_not_running(false).unwrap();
print_not_running(true).unwrap();
}
}