//! Library entry point. `main.rs` calls `run()`. mod commands; mod creds; mod hosts; mod mcp; mod mcp_policy; mod pty; mod usage; mod window_state; use std::sync::Arc; // `Manager` trait must be in scope to call `.app_handle()` on the `&Window` // passed to the `on_window_event` closure below. Same pattern as the // `Emitter` trait needed for `.emit()` (see 2026-05-26 PR-1 session log). use tauri::Manager; use crate::mcp::{McpServerHandle, McpState, PendingActions}; use crate::pty::PtyManager; use crate::window_state::{PendingInits, WindowsState, MAIN_WINDOW_LABEL}; pub fn run() { let _ = tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) .with_writer(std::io::stderr) .try_init(); // keyring-core 1.x requires explicit store registration before any // Entry::new() call. We're Windows-only so the Credential Manager // backend is the only choice. Failure here means SSH passwords won't // be retrievable — log and continue (host configs still work without // saved passwords; users just see the prompt and type it manually). match windows_native_keyring_store::Store::new() { Ok(store) => keyring_core::set_default_store(store), Err(e) => tracing::warn!("keyring store init failed: {e}"), } // PtyManager and McpState are shared with the MCP server, so register // them as Arc rather than the plain T. Tauri commands access them // via `tauri::State<'_, Arc>` and deref / clone as needed. let ptys: Arc = Arc::new(PtyManager::new()); let mcp_state: Arc> = Arc::new(tokio::sync::RwLock::new(McpState::default())); // Pending action registry — separate managed state so mcp_action_reply can // grab it without needing to lock McpState or reach into TileService. let pending_actions: Arc = Arc::new(PendingActions::default()); // Cross-window workspace aggregator: every window pushes its tab list // here; backend debounces + writes the merged envelope to workspace.json. let windows_state: Arc = Arc::new(WindowsState::default()); // Pane-transfer pending-init registry: source window stashes a payload // keyed by the new window's label; target window pulls it during mount. let pending_inits: Arc = Arc::new(PendingInits::default()); let windows_state_for_event = Arc::clone(&windows_state); let pending_inits_for_event = Arc::clone(&pending_inits); tauri::Builder::default() .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_opener::init()) .manage(ptys) .manage(mcp_state) .manage(McpServerHandle::default()) .manage(pending_actions) .manage(windows_state) .manage(pending_inits) .manage(usage::UsageCache::default()) .on_window_event(move |window, event| { // When a non-main window closes, drop its workspaces from the // aggregator AND any unconsumed pending-init payload so neither // resurrect on next launch. Matches Chrome-style "closing a // detached window discards its tabs" intent. if let tauri::WindowEvent::CloseRequested { .. } = event { let label = window.label().to_string(); if label != MAIN_WINDOW_LABEL { pending_inits_for_event.by_label.lock().remove(&label); windows_state_for_event .forget(window.app_handle().clone(), &label); } } }) .invoke_handler(tauri::generate_handler![ commands::list_distros, commands::spawn_pane, commands::write_to_pane, commands::resize_pane, commands::kill_pane, commands::mark_pane_transferring, commands::claim_pane, commands::get_pane_ring, commands::create_pane_window, commands::take_pending_window_init, commands::push_window_workspaces, commands::save_workspace, commands::load_workspace, commands::list_ssh_hosts, commands::save_ssh_hosts, commands::set_host_password, commands::delete_host_password, commands::has_host_password, commands::mcp_start, commands::mcp_stop, commands::mcp_status, commands::mcp_regenerate_token, commands::mcp_update_state, commands::mcp_action_reply, commands::mcp_policy_load, commands::mcp_policy_save, commands::mcp_hard_deny_labels, usage::get_pane_context, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }