#![recursion_limit = "256"]
mod agent_coordination;
mod analysis_handles;
mod analysis_queue;
mod artifact_store;
mod authority;
mod build_info;
mod cli;
mod client_profile;
mod dispatch;
mod env_compat;
mod error;
mod job_store;
mod mutation_audit;
mod mutation_gate;
mod preflight_store;
mod prompts;
mod protocol;
mod recent_buffer;
mod resource_analysis;
mod resource_catalog;
mod resource_context;
mod resource_profiles;
mod resources;
mod runtime_types;
mod server;
mod session_context;
mod session_metrics_payload;
mod state;
mod surface_manifest;
mod telemetry;
mod test_helpers;
mod tool_defs;
mod tool_runtime;
mod tools;
pub(crate) use state::AppState;
use anyhow::Result;
#[cfg(feature = "http")]
use cli::format_http_startup_banner;
use cli::{cli_option_value, resolve_startup_project, select_startup_project_source};
use server::oneshot::run_oneshot;
use server::transport_stdio::run_stdio;
use state::RuntimeDaemonMode;
use std::sync::Arc;
use tool_defs::{
ToolPreset, ToolProfile, ToolSurface, default_budget_for_preset, default_budget_for_profile,
};
fn init_tracing_fmt_only() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_env("CODELENS_LOG")
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.with_writer(std::io::stderr)
.with_target(false)
.init();
}
#[cfg(feature = "otel")]
fn init_tracing() {
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::trace::SdkTracerProvider;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
let endpoint = std::env::var("CODELENS_OTEL_ENDPOINT").unwrap_or_default();
if endpoint.is_empty() {
init_tracing_fmt_only();
return;
}
let exporter = match opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(&endpoint)
.build()
{
Ok(exp) => exp,
Err(e) => {
eprintln!(
"codelens: failed to create OTLP exporter ({e}), falling back to stderr-only tracing"
);
init_tracing_fmt_only();
return;
}
};
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(
opentelemetry_sdk::Resource::builder()
.with_service_name("codelens-mcp")
.build(),
)
.build();
let tracer = provider.tracer("codelens-mcp");
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
let env_filter = tracing_subscriber::EnvFilter::try_from_env("CODELENS_LOG")
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
let fmt_layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_target(false);
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.with(otel_layer)
.init();
eprintln!("codelens: OpenTelemetry OTLP exporter active → {endpoint}");
}
#[cfg(not(feature = "otel"))]
fn init_tracing() {
init_tracing_fmt_only();
}
fn main() -> Result<()> {
init_tracing();
let args: Vec<String> = std::env::args().collect();
let preset = args
.iter()
.position(|a| a == "--preset")
.and_then(|i| args.get(i + 1))
.map(|s| ToolPreset::from_str(s))
.or_else(|| {
std::env::var("CODELENS_PRESET")
.ok()
.map(|s| ToolPreset::from_str(&s))
})
.unwrap_or_else(|| state::ClientProfile::detect(None).default_preset());
let profile = cli_option_value(&args, "--profile")
.as_deref()
.and_then(ToolProfile::from_str)
.or_else(|| {
std::env::var("CODELENS_PROFILE")
.ok()
.and_then(|s| ToolProfile::from_str(&s))
});
let daemon_mode = cli_option_value(&args, "--daemon-mode")
.as_deref()
.map(RuntimeDaemonMode::from_str)
.or_else(|| {
std::env::var("CODELENS_DAEMON_MODE")
.ok()
.map(|s| RuntimeDaemonMode::from_str(&s))
})
.unwrap_or(RuntimeDaemonMode::Standard);
if args.iter().any(|arg| arg == "--print-surface-manifest") {
let surface = profile
.map(ToolSurface::Profile)
.unwrap_or_else(|| ToolSurface::Preset(preset));
let manifest = surface_manifest::build_surface_manifest(surface, daemon_mode);
println!("{}", serde_json::to_string_pretty(&manifest)?);
return Ok(());
}
let project_from_claude = std::env::var("CLAUDE_PROJECT_DIR").ok();
let project_from_mcp = std::env::var("MCP_PROJECT_DIR").ok();
let cwd = std::env::current_dir()?;
let project_source = select_startup_project_source(
&args,
project_from_claude.clone(),
project_from_mcp.clone(),
cwd,
);
let cmd_tool = cli_option_value(&args, "--cmd");
let cmd_args = cli_option_value(&args, "--args");
let transport = cli_option_value(&args, "--transport").unwrap_or_else(|| "stdio".to_owned());
#[cfg(feature = "http")]
let port: u16 = cli_option_value(&args, "--port")
.and_then(|s| s.parse().ok())
.unwrap_or(7837);
let project = resolve_startup_project(&project_source)?;
if !project_source.is_explicit() && project.as_path() == std::path::Path::new("/") {
anyhow::bail!(
"Refusing to start CodeLens on `/` without an explicit project root. Pass a path or set MCP_PROJECT_DIR/CLAUDE_PROJECT_DIR."
);
}
crate::tools::session::auto_set_embed_hint_lang(project.as_path());
let app_state = AppState::new(project, preset);
app_state.configure_transport_mode(&transport);
app_state.configure_daemon_mode(daemon_mode);
if let Some(profile) = profile {
app_state.set_surface(ToolSurface::Profile(profile));
app_state.set_token_budget(default_budget_for_profile(profile));
} else {
app_state.set_surface(ToolSurface::Preset(preset));
app_state.set_token_budget(default_budget_for_preset(preset));
}
#[cfg(feature = "http")]
if transport == "http" {
let startup_banner = format_http_startup_banner(
app_state.project().as_path(),
&project_source,
app_state.surface().as_label(),
app_state.token_budget(),
app_state.daemon_mode(),
port,
app_state.daemon_started_at(),
);
tracing::warn!("{startup_banner}");
}
if let Some(tool_name) = cmd_tool {
let state = Arc::new(app_state);
return run_oneshot(&state, &tool_name, cmd_args.as_deref());
}
match transport.as_str() {
#[cfg(feature = "http")]
"http" => {
let state = Arc::new(app_state.with_session_store());
server::transport_http::run_http(state, port)
}
#[cfg(not(feature = "http"))]
"http" => {
anyhow::bail!(
"HTTP transport requires the `http` feature. Rebuild with: cargo build --features http"
);
}
_ => run_stdio(Arc::new(app_state)),
}
}
#[path = "integration_tests/mod.rs"]
#[cfg(test)]
mod tests;