use crate::cli::args::InventoryArgs;
use assay_core::discovery::config_files::scan_config_files;
use assay_core::discovery::inventory_carrier::{
to_inventory_carrier_v0, CoverageState, ScannerCoverage,
};
use assay_core::discovery::processes::scan_processes;
use assay_core::discovery::types::DiscoverySource;
use std::collections::BTreeMap;
use std::io::Write;
use super::config_path::{detect_config_path, McpClient};
pub async fn run(args: InventoryArgs) -> anyhow::Result<i32> {
let mut servers = scan_config_files(super::discover::get_config_search_paths());
if !args.no_process_scan {
servers.extend(scan_processes());
}
let mut config_sources: BTreeMap<String, CoverageState> = BTreeMap::new();
for (client, key) in [
(McpClient::Claude, "claude_desktop"),
(McpClient::Cursor, "cursor"),
] {
let state = match detect_config_path(client) {
Some(path) if path.exists() => CoverageState::Complete,
Some(_) => CoverageState::NotScanned,
None => CoverageState::Unsupported,
};
config_sources.insert(key.to_string(), state);
}
for server in &servers {
if let DiscoverySource::ConfigFile { client, .. } = &server.source {
config_sources.insert(client.clone(), CoverageState::Complete);
}
}
let coverage = ScannerCoverage {
config_sources,
process_scan: process_scan_coverage(args.no_process_scan),
network_scan: CoverageState::Unsupported,
};
let carrier = to_inventory_carrier_v0(&servers, &coverage);
let rendered = format!("{}\n", serde_json::to_string_pretty(&carrier)?);
if args.out == "-" {
std::io::stdout().write_all(rendered.as_bytes())?;
} else {
std::fs::write(&args.out, rendered)?;
}
Ok(crate::exit_codes::EXIT_SUCCESS)
}
fn process_scan_coverage(no_process_scan: bool) -> CoverageState {
if no_process_scan {
CoverageState::NotScanned
} else {
CoverageState::Partial
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heuristic_process_coverage_never_supports_absence() {
assert_eq!(process_scan_coverage(false), CoverageState::Partial);
assert!(!process_scan_coverage(false).supports_absence_claim());
}
#[test]
fn scoped_out_process_coverage_is_not_scanned_and_unsupporting() {
assert_eq!(process_scan_coverage(true), CoverageState::NotScanned);
assert!(!process_scan_coverage(true).supports_absence_claim());
}
}