| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use super::identity::{owner_home, recorded_owner}; |
| 6 |
use super::{ui, Ctx}; |
| 7 |
use anyhow::{bail, Context, Result}; |
| 8 |
use spectra_core::ledger::Ledger; |
| 9 |
use spectra_mcp::client::Client; |
| 10 |
use std::path::PathBuf; |
| 11 |
|
| 12 |
pub async fn open(ctx: &Ctx, owner_home_flag: Option<PathBuf>, launch: bool) -> Result<i32> { |
| 13 |
let socket = ctx.socket(); |
| 14 |
if !Client::alive(&socket).await { |
| 15 |
bail!("Spectra is not serving {} (spectra serve)", ctx.data.display()); |
| 16 |
} |
| 17 |
let home = owner_home(ctx, owner_home_flag); |
| 18 |
let ledger = Ledger::open_read_only(&ctx.db()?).await?; |
| 19 |
let owner = recorded_owner(&ledger, &home).await?; |
| 20 |
let r = Client::new(&socket, Some(owner)).send("POST", "/radar/link", &[], Vec::new()).await?; |
| 21 |
if r.status != 200 { |
| 22 |
bail!("the daemon refused the sign-in: {}", String::from_utf8_lossy(&r.body)); |
| 23 |
} |
| 24 |
let url = r.json()?.get("url").and_then(|u| u.as_str()).map(String::from).context("no link in the answer")?; |
| 25 |
println!("{} {}", ui::done("Signed"), ui::subject("a one-time link to Radar, good for a minute")); |
| 26 |
show(&url, launch); |
| 27 |
Ok(0) |
| 28 |
} |
| 29 |
|
| 30 |
fn show(url: &str, launch: bool) { |
| 31 |
println!(" {}", ui::id(url)); |
| 32 |
if launch |
| 33 |
&& std::process::Command::new("xdg-open") |
| 34 |
.arg(url) |
| 35 |
.stdout(std::process::Stdio::null()) |
| 36 |
.stderr(std::process::Stdio::null()) |
| 37 |
.spawn() |
| 38 |
.is_ok() |
| 39 |
{ |
| 40 |
println!(" {} {}", ui::note(), ui::dim("opened in your browser")); |
| 41 |
} |
| 42 |
} |
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
pub async fn open_remote(ctx: &Ctx, owner_home_flag: Option<PathBuf>, launch: bool, port: u16) -> Result<i32> { |
| 47 |
let cfg = ctx.remote()?.context("no server configured")?; |
| 48 |
let home = owner_home(ctx, owner_home_flag); |
| 49 |
let listener = tokio::net::TcpListener::bind(("127.0.0.1", port)) |
| 50 |
.await |
| 51 |
.with_context(|| format!("binding 127.0.0.1:{port} for Radar (another `spectra open` running? --port picks another)"))?; |
| 52 |
let port = listener.local_addr()?.port(); |
| 53 |
let client = ctx.owner_client(&home).await?.context("no server configured")?; |
| 54 |
let r = client.send("POST", "/radar/link", &[], Vec::new()).await?; |
| 55 |
if r.status != 200 { |
| 56 |
bail!("the server refused the sign-in: {}", String::from_utf8_lossy(&r.body)); |
| 57 |
} |
| 58 |
let url = r.json()?.get("url").and_then(|u| u.as_str()).map(String::from).context("no link in the answer")?; |
| 59 |
let local = spectra_mcp::proxy::local_link(&url, port); |
| 60 |
println!("{} {}{}{}", ui::done("Serving"), ui::subject("Radar"), ui::sep(), ui::dim(format!("from {} over pinned TLS", cfg.server))); |
| 61 |
println!("{}", ui::kv("radar", format!("http://127.0.0.1:{port}"))); |
| 62 |
println!("{} {}", ui::done("Signed"), ui::subject("a one-time link, good for a minute")); |
| 63 |
show(&local, launch); |
| 64 |
println!(" {} {}", ui::note(), ui::dim("Ctrl-C stops the proxy")); |
| 65 |
use std::io::Write; |
| 66 |
std::io::stdout().flush().ok(); |
| 67 |
let app = spectra_mcp::proxy::router(cfg.endpoint()?, port); |
| 68 |
axum::serve(listener, app) |
| 69 |
.with_graceful_shutdown(async { |
| 70 |
let _ = tokio::signal::ctrl_c().await; |
| 71 |
}) |
| 72 |
.await?; |
| 73 |
Ok(0) |
| 74 |
} |