aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs85
1 files changed, 49 insertions, 36 deletions
diff --git a/src/main.rs b/src/main.rs
index c990f793..2637668e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,19 +21,23 @@ const NESTED_HERDR_MESSAGES: [&str; 6] = [
mod api;
mod app;
mod cli;
+mod client;
mod config;
mod detect;
mod events;
mod ghostty;
mod input;
mod integration;
+mod ipc;
mod layout;
+mod logging;
mod pane;
mod persist;
mod platform;
mod raw_input;
mod release_notes;
mod selection;
+mod server;
mod sound;
mod terminal_theme;
mod ui;
@@ -41,34 +45,7 @@ mod update;
mod workspace;
fn init_logging() {
- use std::fs::{self, OpenOptions};
- use tracing_subscriber::EnvFilter;
-
- let log_dir = crate::config::config_dir();
- let _ = fs::create_dir_all(&log_dir);
- let log_path = log_dir.join("herdr.log");
-
- // Rotate: truncate if over 5MB
- if let Ok(meta) = fs::metadata(&log_path) {
- if meta.len() > 5 * 1024 * 1024 {
- let _ = fs::remove_file(&log_path);
- }
- }
-
- let file = match OpenOptions::new().create(true).append(true).open(&log_path) {
- Ok(f) => f,
- Err(_) => return, // can't open log file, proceed without logging
- };
-
- let filter =
- EnvFilter::try_from_env("HERDR_LOG").unwrap_or_else(|_| EnvFilter::new("herdr=info"));
-
- tracing_subscriber::fmt()
- .with_env_filter(filter)
- .with_writer(file)
- .with_ansi(false)
- .with_target(false)
- .init();
+ crate::logging::init_file_logging("herdr.log");
}
const DEFAULT_CONFIG: &str = r##"# herdr configuration
@@ -102,9 +79,10 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# Navigate-mode actions
# new_workspace = "n"
# rename_workspace = "shift+n"
-# close_workspace = "d"
+# close_workspace = "shift+d"
# previous_workspace = "" # optional, unset by default
# next_workspace = "" # optional, unset by default
+# detach = "" # optional explicit detach shortcut in server/client mode
# new_tab = "c"
# rename_tab = "" # optional, unset by default
# previous_tab = "" # optional, unset by default
@@ -184,6 +162,15 @@ fn main() -> io::Result<()> {
}
// Subcommands and flags (no TUI, no logging needed)
+ if args.get(1).map(|s| s.as_str()) == Some("server") {
+ return server::headless::run_server();
+ }
+
+ // Client mode: connect to an existing server's client socket.
+ if args.get(1).map(|s| s.as_str()) == Some("client") {
+ return client::run_client();
+ }
+
if args.get(1).map(|s| s.as_str()) == Some("update") {
match update::self_update() {
Ok(_) => return Ok(()),
@@ -199,6 +186,7 @@ fn main() -> io::Result<()> {
println!();
println!("Usage: herdr [options]");
println!(" herdr update");
+ println!(" herdr server stop");
println!(" herdr workspace <subcommand> ...");
println!(" herdr tab <subcommand> ...");
println!(" herdr pane <subcommand> ...");
@@ -206,7 +194,12 @@ fn main() -> io::Result<()> {
println!(" herdr integration <subcommand> ...");
println!();
println!("Commands:");
- println!(" update Download and install the latest version");
+ println!(" server Run as headless server (no terminal, persists after client disconnect)");
+ println!(" server stop Stop the running server via the API socket");
+ println!(" client Connect to a running server as a thin client");
+ println!(
+ " update Download and install the latest version (run outside herdr)"
+ );
println!(" workspace workspace helpers over the socket api");
println!(" tab tab helpers over the socket api");
println!(" pane pane control helpers over the socket api");
@@ -214,7 +207,7 @@ fn main() -> io::Result<()> {
println!(" integration manage built-in agent integrations");
println!();
println!("Options:");
- println!(" --no-session Don't restore or save sessions");
+ println!(" --no-session Run monolithically (no server/client, escape hatch)");
println!(" --default-config Print default configuration and exit");
println!(" --version, -V Print version and exit");
println!(" --show-changelog Preview the current version's release notes");
@@ -257,7 +250,16 @@ fn main() -> io::Result<()> {
std::process::exit(1);
}
if !arg.starts_with('-')
- && !["update", "workspace", "pane", "wait", "integration"].contains(&arg.as_str())
+ && ![
+ "server",
+ "client",
+ "update",
+ "workspace",
+ "pane",
+ "wait",
+ "integration",
+ ]
+ .contains(&arg.as_str())
{
eprintln!("unknown command: {arg}");
eprintln!("run 'herdr --help' for usage");
@@ -274,6 +276,18 @@ fn main() -> io::Result<()> {
std::process::exit(1);
}
+ let no_session = args.iter().any(|a| a == "--no-session");
+
+ // Auto-detect launch: when --no-session is NOT set, use server/client mode.
+ // Check if a server is running, spawn one if needed, then attach as client.
+ if !no_session {
+ init_logging();
+ return server::autodetect::auto_detect_launch();
+ }
+
+ // --- Monolithic mode (--no-session escape hatch) ---
+ // This is the pre-mission single-process behavior.
+
init_logging();
let (api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
@@ -288,8 +302,7 @@ fn main() -> io::Result<()> {
Err(err) => return Err(err),
};
- let no_session = std::env::args().any(|a| a == "--no-session");
- let show_changelog = std::env::args().any(|a| a == "--show-changelog");
+ let show_changelog = args.iter().any(|a| a == "--show-changelog");
let in_tmux = std::env::var("TMUX").is_ok();
let original_hook = std::panic::take_hook();
@@ -321,7 +334,7 @@ fn main() -> io::Result<()> {
loaded_config.diagnostics.len() - 1
))
};
- info!("herdr starting, pid={}", std::process::id());
+ info!("herdr starting (monolithic), pid={}", std::process::id());
// Background update check (non-blocking, best-effort)
// Only checks for newer versions and notifies the TUI.
@@ -362,7 +375,7 @@ fn main() -> io::Result<()> {
let mut app = app::App::new(
config,
- no_session,
+ true, // no_session — monolithic mode never saves/restores sessions
config_diagnostic,
startup_release_notes,
api_rx,