1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! `life relay` — manage the relay daemon for remote agent sessions.
//!
//! Wraps life-relayd as a library so users run `life relay auth|start|stop|status`
//! instead of a separate binary. Shares credentials with the broomva CLI
//! (`~/.broomva/config.json`) so `broomva auth login` tokens work here too.
use anyhow::Result;
use clap::Subcommand;
use tracing::info;
#[derive(Subcommand)]
pub enum RelayCommand {
/// Authenticate with broomva.tech via device authorization.
/// If already logged in via `broomva auth login`, those credentials are reused.
Auth {
/// Server URL.
#[arg(long, default_value = "https://broomva.tech")]
url: String,
},
/// Start the relay daemon (connects to broomva.tech, polls for commands).
Start {
/// Local API bind address.
#[arg(long, default_value = "127.0.0.1:3004")]
bind: String,
/// Server URL to connect to.
#[arg(long, default_value = "https://broomva.tech")]
server: String,
},
/// Stop the relay daemon.
Stop,
/// Show relay daemon and connection status.
Status,
}
pub async fn run(command: RelayCommand) -> Result<()> {
match command {
RelayCommand::Auth { url } => {
let cfg = life_relayd::config::load_config()?;
// Check if broomva CLI token already exists
match life_relayd::config::read_token(&cfg) {
Ok(_) => {
println!(" Already authenticated (token found).");
println!(" Source: broomva CLI or relay credentials.");
println!();
println!(" To re-authenticate, run `life relay auth --url {url}`");
println!(" with a fresh device code flow.");
}
Err(_) => {
info!(url = %url, "starting device authorization");
life_relayd::auth::run(&url, &cfg.credentials_path()).await?;
}
}
}
RelayCommand::Start { bind, server } => {
info!(bind = %bind, server = %server, "starting relay daemon");
life_relayd::daemon::run(&bind, &server).await?;
}
RelayCommand::Stop => {
// Send stop signal to running daemon via local API
let client = reqwest::Client::new();
match client
.get("http://127.0.0.1:3004/health")
.timeout(std::time::Duration::from_secs(2))
.send()
.await
{
Ok(_) => {
println!(" Relay daemon is running but graceful stop is not yet implemented.");
println!(" Use `pkill -f life-relayd` or `kill $(lsof -ti :3004)` to stop.");
}
Err(_) => {
println!(" No relay daemon running on port 3004.");
}
}
}
RelayCommand::Status => {
let cfg = life_relayd::config::load_config()?;
let has_token = life_relayd::config::read_token(&cfg).is_ok();
println!(" Relay Configuration");
println!(" ───────────────────");
println!(" Config dir: {}", cfg.config_dir.display());
println!(" Authenticated: {}", if has_token { "yes" } else { "no" });
// Check if daemon is running
let client = reqwest::Client::new();
match client
.get("http://127.0.0.1:3004/health")
.timeout(std::time::Duration::from_secs(2))
.send()
.await
{
Ok(resp) if resp.status().is_success() => {
println!(" Daemon: running (port 3004)");
if let Ok(body) = resp.json::<serde_json::Value>().await {
if let Some(v) = body.get("version").and_then(|v| v.as_str()) {
println!(" Version: {v}");
}
}
}
_ => {
println!(" Daemon: not running");
}
}
if !has_token {
println!();
println!(" Run `life relay auth` or `broomva auth login` to authenticate.");
}
}
}
Ok(())
}