BREP_app 0.4.0

The BREP CAD application: an eframe (egui + wgpu) host that draws the brep-render 3D engine into an egui frame — native + wasm from one codebase.
Documentation
//! Native entry: `brep-app [--mcp [--mcp-port PORT] [--session-root DIR]]`.
//!
//! Opens a real OS window (winit, via eframe) whose wgpu device drives the
//! shared `brep-render` engine. Needs a display + GPU to actually run; on a
//! headless box this still `cargo build`s (the CI/gate signal), and the web
//! target (`lib.rs` + `WebRunner`) is the interactive verification path.
//!
//! `--mcp` embeds the MCP server (src/mcp.rs): the window becomes a session an
//! AI agent drives over `http://127.0.0.1:PORT/mcp`, and the terminal prints
//! how to configure Claude Code or Codex for it.

#[cfg(not(target_arch = "wasm32"))]
const USAGE: &str = "usage: brep-app [--mcp] [--mcp-port PORT] [--session-root DIR]

  --mcp               embed the MCP server: agents drive this window over http://127.0.0.1:PORT/mcp
  --mcp-port PORT     the port to listen on (default 8765)
  --session-root DIR  where MCP sessions keep screenshots and call logs (default: the OS temp dir)
  -h, --help          this text";

/// What the command line asked for.
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, PartialEq, Default)]
struct Launch {
    mcp: bool,
    mcp_port: Option<u16>,
    session_root: Option<std::path::PathBuf>,
    help: bool,
}

#[cfg(not(target_arch = "wasm32"))]
fn parse_args(args: impl IntoIterator<Item = String>) -> Result<Launch, String> {
    let mut launch = Launch::default();
    let mut args = args.into_iter();
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--mcp" => launch.mcp = true,
            "--mcp-port" => {
                let value = args.next().ok_or("--mcp-port needs a port number")?;
                launch.mcp_port = Some(value.parse().map_err(|_| format!("--mcp-port: `{value}` is not a port number"))?);
            }
            "--session-root" => {
                launch.session_root = Some(args.next().ok_or("--session-root needs a directory")?.into());
            }
            "-h" | "--help" => launch.help = true,
            other => return Err(format!("unknown argument `{other}`")),
        }
    }
    if (launch.mcp_port.is_some() || launch.session_root.is_some()) && !launch.mcp {
        return Err("--mcp-port and --session-root need --mcp".into());
    }
    Ok(launch)
}

#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
    let _ = brep_app::logger::try_init();
    let launch = match parse_args(std::env::args().skip(1)) {
        Ok(l) => l,
        Err(e) => {
            eprintln!("brep-app: {e}\n{USAGE}");
            std::process::exit(2);
        }
    };
    if launch.help {
        println!("{USAGE}");
        return Ok(());
    }

    #[cfg(feature = "mcp")]
    let mcp = if launch.mcp {
        // Bind first: a port in use is a launch error, not a window without
        // its server. The instructions print from the address actually bound.
        let port = launch.mcp_port.unwrap_or(brep_app::mcp::DEFAULT_PORT);
        let listener = match brep_app::mcp::bind(port) {
            Ok(l) => l,
            Err(e) => {
                eprintln!("brep-app: {e}");
                std::process::exit(1);
            }
        };
        let session_root = launch.session_root.clone().unwrap_or_else(brep_app::mcp::default_session_root);
        println!("{}", brep_app::mcp::agent_instructions(&brep_app::mcp::url(&listener), &session_root));
        // The state registry publishes only for a host; this window has one.
        brep_app::automation::registry::set_enabled(true);
        Some(brep_app::mcp::Launch { listener, session_root })
    } else {
        None
    };
    #[cfg(not(feature = "mcp"))]
    if launch.mcp {
        eprintln!("brep-app: this build has no MCP server (built without the `mcp` feature)");
        std::process::exit(1);
    }

    let native_options = eframe::NativeOptions {
        // egui's frame render pass stays single-sample so the viewport blit
        // pipeline (also single-sample) matches; the engine does its own 4x MSAA
        // inside the offscreen render.
        multisampling: 0,
        ..Default::default()
    };
    eframe::run_native(
        "brep-app",
        native_options,
        Box::new(move |cc| {
            brep_app::fonts::install(&cc.egui_ctx);
            let app = brep_app::app::BrepApp::new(cc)?;
            #[cfg(feature = "mcp")]
            if let Some(launch) = mcp {
                // The app's own diagnostics, not a second reading of the same
                // adapter: the banner a host sees and the Info window a user
                // sees must name the same hardware.
                let adapter = app.diagnostics().adapter_line();
                brep_app::mcp::start(app.automation().clone(), adapter, launch)?;
            }
            Ok(Box::new(app) as Box<dyn eframe::App>)
        }),
    )
}

#[cfg(target_arch = "wasm32")]
fn main() {}

// BREP private tests: 267be7e4e75be195