cortex_runtime/cli/
start.rs1use crate::cartography::mapper::Mapper;
4use crate::cli::output::{self, Styled};
5use crate::extraction::loader::ExtractionLoader;
6use crate::renderer::chromium::ChromiumRenderer;
7use crate::renderer::{NoopRenderer, Renderer};
8use crate::server::Server;
9use anyhow::{Context, Result};
10use std::path::PathBuf;
11use std::sync::Arc;
12use tracing::{error, info, warn};
13
14pub const SOCKET_PATH: &str = "/tmp/cortex.sock";
16
17pub fn pid_file_path() -> PathBuf {
19 dirs::home_dir()
20 .unwrap_or_else(|| PathBuf::from("/tmp"))
21 .join(".cortex/cortex.pid")
22}
23
24pub fn check_already_running() -> Option<i32> {
26 let pid_path = pid_file_path();
27 if !pid_path.exists() {
28 return None;
29 }
30 let pid_str = std::fs::read_to_string(&pid_path).ok()?;
31 let pid: i32 = pid_str.trim().parse().ok()?;
32
33 #[cfg(unix)]
35 {
36 let output = std::process::Command::new("kill")
37 .args(["-0", &pid.to_string()])
38 .output();
39 if matches!(output, Ok(o) if o.status.success()) {
40 return Some(pid);
41 }
42 }
43
44 let _ = std::fs::remove_file(&pid_path);
46 None
47}
48
49pub async fn run_with_http(http_port: Option<u16>) -> Result<()> {
51 run_inner(http_port).await
52}
53
54pub async fn run() -> Result<()> {
56 run_inner(None).await
57}
58
59async fn run_inner(http_port: Option<u16>) -> Result<()> {
60 let s = Styled::new();
61
62 if let Some(pid) = check_already_running() {
64 eprintln!(" {} Cortex is already running (PID {pid}).", s.warn_sym());
65 eprintln!(" Use 'cortex restart' or 'cortex stop' first.");
66 std::process::exit(1);
67 }
68
69 let socket_path = PathBuf::from(SOCKET_PATH);
71 if socket_path.exists() {
72 std::fs::remove_file(&socket_path).ok();
73 }
74
75 let pid_path = pid_file_path();
77 if let Some(parent) = pid_path.parent() {
78 std::fs::create_dir_all(parent).ok();
79 }
80
81 tracing_subscriber::fmt()
83 .with_env_filter(
84 tracing_subscriber::EnvFilter::from_default_env()
85 .add_directive("cortex=info".parse().unwrap()),
86 )
87 .init();
88
89 info!("starting Cortex v{}", env!("CARGO_PKG_VERSION"));
90
91 std::fs::write(&pid_path, std::process::id().to_string())
93 .context("failed to write PID file")?;
94
95 if !output::is_quiet() {
96 eprintln!(
97 " {} Cortex v{} started (PID {})",
98 s.ok_sym(),
99 env!("CARGO_PKG_VERSION"),
100 std::process::id()
101 );
102 eprintln!(" Listening on {SOCKET_PATH}");
103 }
104
105 let server = match ChromiumRenderer::new().await {
107 Ok(renderer) => {
108 info!("Chromium renderer initialized");
109 let renderer: Arc<dyn Renderer> = Arc::new(renderer);
110
111 let extractor_loader = match ExtractionLoader::new() {
113 Ok(loader) => {
114 info!("Extraction loader initialized");
115 Arc::new(loader)
116 }
117 Err(e) => {
118 warn!("Failed to initialize extraction loader: {e}");
119 warn!("MAP requests will use fallback extractors");
120 Arc::new(
121 ExtractionLoader::new()
122 .unwrap_or_else(|_| panic!("ExtractionLoader must initialize")),
123 )
124 }
125 };
126
127 let mapper = Arc::new(Mapper::new(Arc::clone(&renderer), extractor_loader));
129
130 Server::new(&socket_path).with_mapper(renderer, mapper)
131 }
132 Err(e) => {
133 warn!("Failed to initialize Chromium: {e}");
134 warn!("Running in HTTP-only mode (no browser fallback, no PERCEIVE)");
135 let renderer: Arc<dyn Renderer> = Arc::new(NoopRenderer);
136 let extractor_loader = Arc::new(
137 ExtractionLoader::new()
138 .unwrap_or_else(|_| panic!("ExtractionLoader must initialize")),
139 );
140 let mapper = Arc::new(Mapper::new(Arc::clone(&renderer), extractor_loader));
141 Server::new(&socket_path).with_mapper(renderer, mapper)
142 }
143 };
144
145 let shutdown = server.shutdown_handle();
146
147 let shutdown_signal = shutdown.clone();
149 tokio::spawn(async move {
150 let _ = tokio::signal::ctrl_c().await;
151 info!("received shutdown signal");
152 shutdown_signal.notify_one();
153 });
154
155 if let Some(port) = http_port {
157 let rest_state = server.shared_state();
158 tokio::spawn(async move {
159 if let Err(e) = crate::rest::start(port, rest_state).await {
160 error!("REST API error: {e}");
161 }
162 });
163 if !output::is_quiet() {
164 eprintln!(" REST API listening on http://127.0.0.1:{port}");
165 }
166 }
167
168 let result = server.start().await;
170
171 let _ = std::fs::remove_file(&pid_path);
173 let _ = std::fs::remove_file(&socket_path);
174
175 if !output::is_quiet() {
176 eprintln!(" {} Cortex stopped.", s.ok_sym());
177 }
178
179 result
180}