use crate::app;
use crate::enhancement_trace::{PromptOptimizeTrace, read_latest_prompt_optimize_trace};
use crate::memory_gateway::load_config;
use serde::Serialize;
use std::fs;
use std::path::Path;
use ts_rs::TS;
#[derive(Debug, Clone, Serialize, TS)]
#[ts(export, export_to = "../frontend/src/lib/types/generated/")]
pub struct DesktopStatusResponse {
pub config_exists: bool,
pub vault_available: bool,
pub vault_root: Option<String>,
pub cwd_exists: bool,
pub cwd: String,
pub session_sources_available: bool,
pub claude_mcp_registered: bool,
pub codex_mcp_registered: bool,
pub mcp_config_detected: bool,
pub spool_mcp_command: String,
pub claude_mcp_snippet: String,
pub codex_mcp_snippet: String,
pub recent_enhancement: Option<PromptOptimizeTrace>,
}
pub fn collect_status(
config_path: &Path,
cwd: &Path,
vault_override: Option<&Path>,
provider_session_count: usize,
) -> DesktopStatusResponse {
let config_exists = config_path.exists() && config_path.is_file();
let cwd_exists = cwd.exists() && cwd.is_dir();
let resolved_vault = if let Some(override_path) = vault_override {
app::resolve_override_path(override_path, config_path)
.ok()
.map(|path| path.display().to_string())
} else if config_exists {
load_config(config_path)
.ok()
.map(|cfg| cfg.vault.root.display().to_string())
} else {
None
};
let vault_available = resolved_vault
.as_deref()
.map(|path| fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false))
.unwrap_or(false);
let claude_mcp_registered = detect_claude_spool_mcp();
let codex_mcp_registered = detect_codex_spool_mcp();
let spool_mcp_command = suggested_spool_mcp_command();
let recent_enhancement = read_latest_prompt_optimize_trace(config_path)
.ok()
.flatten();
let claude_mcp_snippet = format!(
r#""spool": {{
"type": "stdio",
"command": "{}",
"args": ["--config", "{}"]
}}"#,
spool_mcp_command,
config_path.display()
);
let codex_mcp_snippet = format!(
r#"[mcp_servers.spool]
type = "stdio"
command = "{}"
args = ["--config", "{}"]"#,
spool_mcp_command,
config_path.display()
);
DesktopStatusResponse {
config_exists,
vault_available,
vault_root: resolved_vault,
cwd_exists,
cwd: cwd.display().to_string(),
session_sources_available: provider_session_count > 0,
claude_mcp_registered,
codex_mcp_registered,
mcp_config_detected: claude_mcp_registered || codex_mcp_registered,
spool_mcp_command,
claude_mcp_snippet,
codex_mcp_snippet,
recent_enhancement,
}
}
fn suggested_spool_mcp_command() -> String {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("target/debug/spool-mcp")
.display()
.to_string()
}
fn detect_claude_spool_mcp() -> bool {
let Some(home) = crate::support::home_dir() else {
return false;
};
let path = home.join(".claude.json");
let content = match fs::read_to_string(path) {
Ok(content) => content,
Err(_) => return false,
};
let value: serde_json::Value = match serde_json::from_str(&content) {
Ok(value) => value,
Err(_) => return false,
};
value
.get("mcpServers")
.and_then(|servers| servers.get("spool"))
.is_some()
}
fn detect_codex_spool_mcp() -> bool {
let Some(home) = crate::support::home_dir() else {
return false;
};
let path = home.join(".codex/config.toml");
let content = match fs::read_to_string(path) {
Ok(content) => content,
Err(_) => return false,
};
let value: toml::Value = match toml::from_str(&content) {
Ok(value) => value,
Err(_) => return false,
};
value
.get("mcp_servers")
.and_then(|servers| servers.get("spool"))
.is_some()
}