difflore_cli/mcp_install/
snapshot.rs1use std::collections::BTreeSet;
2
3use super::{
4 InstallState, McpClientStatus, McpStatusSnapshot, TargetStatus,
5 common::{canonical_record_snapshot, probe_runtime_mcp_server, resolve_difflore_binary},
6 diagnosis::diagnose_status_snapshot,
7 registry::{self, AGENTS},
8};
9
10fn find_surface(agents: &[TargetStatus], name: &str) -> Option<TargetStatus> {
13 agents.iter().find(|agent| agent.name == name).cloned()
14}
15
16fn client_status(name: &'static str, surfaces: Vec<TargetStatus>) -> McpClientStatus {
17 let detected = surfaces.iter().any(|surface| surface.detected);
18 let detected_surfaces: Vec<&TargetStatus> =
19 surfaces.iter().filter(|surface| surface.detected).collect();
20 let installed = detected_surfaces
21 .iter()
22 .filter(|surface| matches!(surface.state, InstallState::Installed))
23 .count();
24 let conflicts = detected_surfaces
25 .iter()
26 .filter(|surface| matches!(surface.state, InstallState::Conflict))
27 .count();
28 let unknowns = detected_surfaces
29 .iter()
30 .filter(|surface| matches!(surface.state, InstallState::Unknown))
31 .count();
32 let missing = detected_surfaces
33 .iter()
34 .filter(|surface| matches!(surface.state, InstallState::NotInstalled))
35 .count();
36
37 let state = if conflicts > 0 || (installed > 0 && missing > 0) {
38 InstallState::Conflict
39 } else if unknowns > 0 {
40 InstallState::Unknown
41 } else if detected && installed == detected_surfaces.len() && installed > 0 {
42 InstallState::Installed
43 } else {
44 InstallState::NotInstalled
45 };
46
47 let detail = match state {
48 InstallState::Installed => Some(format!(
49 "{installed}/{} detected surface(s) installed",
50 detected_surfaces.len()
51 )),
52 InstallState::Conflict if installed > 0 && missing > 0 => Some(format!(
53 "partial install: {installed} installed, {missing} missing"
54 )),
55 InstallState::Conflict => Some(format!("{conflicts} conflicting surface(s)")),
56 InstallState::Unknown => Some(format!("{unknowns} unknown surface(s)")),
57 InstallState::NotInstalled if detected => {
58 Some("detected, but no DiffLore surface installed".into())
59 }
60 InstallState::NotInstalled => Some("not detected".into()),
61 };
62
63 McpClientStatus {
64 name,
65 detected,
66 state,
67 detail,
68 surfaces,
69 }
70}
71
72pub(super) fn collect_client_statuses_from_agents(agents: &[TargetStatus]) -> Vec<McpClientStatus> {
78 let mut clients: Vec<&'static str> = Vec::new();
79 let mut seen: BTreeSet<&'static str> = BTreeSet::new();
80 for spec in AGENTS {
81 if seen.insert(spec.client) {
82 clients.push(spec.client);
83 }
84 }
85 clients
86 .into_iter()
87 .map(|client| {
88 let surfaces: Vec<TargetStatus> = AGENTS
89 .iter()
90 .filter(|spec| spec.client == client)
91 .filter_map(|spec| find_surface(agents, spec.name))
92 .collect();
93 client_status(client, surfaces)
94 })
95 .collect()
96}
97
98pub(super) fn collect_agent_statuses(bin: &str) -> Vec<TargetStatus> {
102 AGENTS
103 .iter()
104 .map(|spec| registry::detect(spec, bin))
105 .collect()
106}
107
108pub(super) fn installed_targets_from_agents(agents: &[TargetStatus]) -> Vec<&'static str> {
109 agents
110 .iter()
111 .filter(|o| matches!(o.state, InstallState::Installed))
112 .map(|o| o.name)
113 .collect()
114}
115
116pub fn collect_status_snapshot() -> McpStatusSnapshot {
117 let bin = resolve_difflore_binary().unwrap_or_else(|_| "difflore".to_owned());
118 let agents = collect_agent_statuses(&bin);
119 let installed_targets = installed_targets_from_agents(&agents);
120 let canonical_record = canonical_record_snapshot(&bin, &installed_targets);
121 let clients = collect_client_statuses_from_agents(&agents);
122 McpStatusSnapshot {
123 binary: bin,
124 canonical_record,
125 runtime_probe: None,
126 diagnosis: None,
127 clients,
128 agents,
129 }
130}
131
132pub fn collect_status_snapshot_with_runtime_probe() -> McpStatusSnapshot {
133 let mut snapshot = collect_status_snapshot();
134 snapshot.runtime_probe = Some(probe_runtime_mcp_server(&snapshot.binary));
135 snapshot.diagnosis = Some(diagnose_status_snapshot(&snapshot));
136 snapshot
137}