use std::io::Write;
use anyhow::Result;
use clap::Subcommand;
use crate::mcp::BasemindServer;
use crate::mcp::params::*;
use crate::path::{RelPath, normalize_query_path};
use super::render::{Emit, emit};
use super::run_tool;
fn resolve_path(server: &BasemindServer, path: &str) -> RelPath {
match normalize_query_path(path, &server.state.shared.root) {
Some(rel) => RelPath::from(rel),
None => RelPath::from(path),
}
}
#[derive(Subcommand, Debug)]
pub enum GraphCmd {
Calls {
name: String,
#[arg(long, default_value = "callers")]
direction: String,
#[arg(long)]
path: Option<String>,
#[arg(long)]
max_depth: Option<u32>,
#[arg(long)]
max_nodes: Option<u32>,
},
Neighbors {
name: String,
#[arg(long)]
path: Option<String>,
#[arg(long, default_value = "both")]
direction: String,
#[arg(long)]
depth: Option<u32>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_nodes: Option<u32>,
},
Path {
from: String,
to: String,
#[arg(long)]
from_path: Option<String>,
#[arg(long)]
to_path: Option<String>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long)]
include_contains: bool,
#[arg(long)]
min_confidence: Option<f32>,
},
Subgraph {
name: String,
#[arg(long)]
path: Option<String>,
#[arg(long)]
depth: Option<u32>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_nodes: Option<u32>,
},
Communities {
#[arg(long, default_value = "all")]
edges: String,
#[arg(long, default_value = "label_propagation")]
algorithm: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_communities: Option<u32>,
#[arg(long)]
members_per_community: Option<u32>,
},
Map {
#[arg(long, default_value = "module")]
granularity: String,
#[arg(long)]
focus: Option<String>,
#[arg(long)]
depth: Option<u32>,
#[arg(long, default_value = "calls")]
edges: String,
#[arg(long, default_value_t = true)]
include_churn: bool,
#[arg(long)]
churn_window: Option<u32>,
#[arg(long)]
max_nodes: Option<u32>,
#[arg(long)]
max_edges: Option<u32>,
#[arg(long)]
max_tokens: Option<u32>,
},
Export {
#[arg(long, default_value = "node_link")]
format: String,
#[arg(long)]
focus: Option<String>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long, default_value = "label_propagation")]
algorithm: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_nodes: Option<u32>,
#[arg(long)]
max_edges: Option<u32>,
#[arg(long)]
write: bool,
},
Display {
#[arg(long, default_value = "html")]
format: String,
#[arg(long)]
focus: Option<String>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long, default_value = "label_propagation")]
algorithm: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_nodes: Option<u32>,
#[arg(long)]
max_edges: Option<u32>,
#[arg(long = "no-open")]
no_open: bool,
},
Open {
#[arg(long, default_value = "html")]
format: String,
#[arg(long)]
focus: Option<String>,
#[arg(long, default_value = "all")]
edges: String,
#[arg(long, default_value = "label_propagation")]
algorithm: String,
#[arg(long)]
min_confidence: Option<f32>,
#[arg(long)]
max_nodes: Option<u32>,
#[arg(long)]
max_edges: Option<u32>,
#[arg(long = "no-open")]
no_open: bool,
},
}
pub async fn run(server: &BasemindServer, cmd: GraphCmd, opts: &Emit, out: &mut impl Write) -> Result<()> {
let p = match cmd {
GraphCmd::Calls {
name,
direction,
path,
max_depth,
max_nodes,
} => GraphParams {
name: Some(name),
direction: Some(direction),
path: path.map(|s| resolve_path(server, &s)),
max_depth,
max_nodes,
..GraphParams::new(GraphMode::Calls)
},
GraphCmd::Neighbors {
name,
path,
direction,
depth,
edges,
min_confidence,
max_nodes,
} => GraphParams {
name: Some(name),
path: path.map(|s| resolve_path(server, &s)),
direction: Some(direction),
depth,
edges: Some(edges),
min_confidence,
max_nodes,
..GraphParams::new(GraphMode::Neighbors)
},
GraphCmd::Path {
from,
to,
from_path,
to_path,
edges,
include_contains,
min_confidence,
} => GraphParams {
from: Some(from),
from_path: from_path.map(|s| resolve_path(server, &s)),
to: Some(to),
to_path: to_path.map(|s| resolve_path(server, &s)),
edges: Some(edges),
include_contains: Some(include_contains),
min_confidence,
..GraphParams::new(GraphMode::Path)
},
GraphCmd::Subgraph {
name,
path,
depth,
edges,
min_confidence,
max_nodes,
} => GraphParams {
name: Some(name),
path: path.map(|s| resolve_path(server, &s)),
depth,
edges: Some(edges),
min_confidence,
max_nodes,
..GraphParams::new(GraphMode::Subgraph)
},
GraphCmd::Communities {
edges,
algorithm,
min_confidence,
max_communities,
members_per_community,
} => GraphParams {
edges: Some(edges),
algorithm: Some(algorithm),
min_confidence,
max_communities,
members_per_community,
..GraphParams::new(GraphMode::Communities)
},
GraphCmd::Map {
granularity,
focus,
depth,
edges,
include_churn,
churn_window,
max_nodes,
max_edges,
max_tokens,
} => GraphParams {
granularity: Some(granularity),
focus: focus.map(RelPath::from),
depth,
edges: Some(edges),
include_churn: Some(include_churn),
churn_window,
max_nodes,
max_edges,
max_tokens,
..GraphParams::new(GraphMode::Map)
},
GraphCmd::Export {
format,
focus,
edges,
algorithm,
min_confidence,
max_nodes,
max_edges,
write,
} => GraphParams {
format: Some(format),
focus: focus.map(RelPath::from),
edges: Some(edges),
algorithm: Some(algorithm),
min_confidence,
max_nodes,
max_edges,
write: Some(write),
..GraphParams::new(GraphMode::Export)
},
GraphCmd::Display {
format,
focus,
edges,
algorithm,
min_confidence,
max_nodes,
max_edges,
no_open,
} => GraphParams {
format: Some(format),
focus: focus.map(RelPath::from),
edges: Some(edges),
algorithm: Some(algorithm),
min_confidence,
max_nodes,
max_edges,
open: Some(!no_open),
..GraphParams::new(GraphMode::Display)
},
GraphCmd::Open {
format,
focus,
edges,
algorithm,
min_confidence,
max_nodes,
max_edges,
no_open,
} => GraphParams {
format: Some(format),
focus: focus.map(RelPath::from),
edges: Some(edges),
algorithm: Some(algorithm),
min_confidence,
max_nodes,
max_edges,
open: Some(!no_open),
..GraphParams::new(GraphMode::Open)
},
};
let key = p.mode.telemetry_key();
let r = run_tool(key, server.graph(Parameters(Lenient(p))).await)?;
emit(key, &r, opts, out)
}