use std::path::Path;
use std::process::Command;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Editor {
VsCode,
Helix,
Zed,
Neovim,
}
impl Editor {
pub fn hint(&self) -> &'static str {
match self {
Editor::VsCode => "install the IWE extension from the VSCode marketplace",
Editor::Helix => "add iwes as a markdown language server in languages.toml",
Editor::Zed => "install the IWE extension from the Zed extension gallery",
Editor::Neovim => "register iwes as a markdown LSP client",
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct Probes {
pub agents_file: Option<String>,
pub mcp_config: bool,
pub claude_dir: bool,
pub editors: Vec<Editor>,
pub git_repository: bool,
pub git_clean: bool,
pub nested_library: Option<String>,
}
impl Probes {
pub fn has_agent_surface(&self) -> bool {
self.agents_file.is_some() || self.mcp_config || self.claude_dir
}
pub fn agent_surface_note(&self) -> String {
let mut found = Vec::new();
if let Some(file) = &self.agents_file {
found.push(file.clone());
}
if self.mcp_config {
found.push(".mcp.json".to_string());
}
if self.claude_dir {
found.push(".claude/".to_string());
}
format!("found {}", found.join(", "))
}
}
pub fn probe(root: &Path) -> Probes {
let mut probes = Probes::default();
for candidate in ["AGENTS.md", "CLAUDE.md"] {
if root.join(candidate).is_file() {
probes.agents_file = Some(candidate.to_string());
break;
}
}
probes.mcp_config = root.join(".mcp.json").is_file();
probes.claude_dir = root.join(".claude").is_dir();
if root.join(".vscode").is_dir() {
probes.editors.push(Editor::VsCode);
}
if root.join(".helix").is_dir() {
probes.editors.push(Editor::Helix);
}
if root.join(".zed").is_dir() {
probes.editors.push(Editor::Zed);
}
if root.join(".nvim.lua").is_file() || root.join(".nvimrc").is_file() {
probes.editors.push(Editor::Neovim);
}
probes.git_repository = root.join(".git").exists();
if probes.git_repository {
probes.git_clean = git_is_clean(root);
}
probes.nested_library = find_parent_library(root);
probes
}
fn git_is_clean(root: &Path) -> bool {
Command::new("git")
.arg("status")
.arg("--porcelain")
.current_dir(root)
.output()
.map(|output| output.status.success() && output.stdout.is_empty())
.unwrap_or(false)
}
fn find_parent_library(root: &Path) -> Option<String> {
let mut current = root.parent();
while let Some(directory) = current {
if directory.join(".iwe").is_dir() {
return Some(directory.display().to_string());
}
current = directory.parent();
}
None
}