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