use std::path::PathBuf;
use serde::Serialize;
use crate::cli::ClientKind;
const CARTOG_LOGO: &str = r"
___ _
/ __\__ _ _ __| |_ ___ __ _
/ / / _` | '__| __/ _ \ / _` |
/ /__| (_| | | | || (_) | (_| |
\____/\__,_|_| \__\___/ \__, |
|___/
code graph indexer · MCP wiring
";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Scope {
Project,
User,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MergeStrategy {
McpServers,
Mcp,
ContextServers,
VsCodeServers,
CodexToml,
HermesYaml,
}
#[derive(Debug)]
pub struct ClientSpec {
pub kind: ClientKind,
pub scope: Scope,
pub path: PathBuf,
pub strategy: MergeStrategy,
pub args: Vec<String>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct MergeOutcome {
pub new_json: String,
pub action: Action,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Action {
Created,
Updated,
Unchanged,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum IdeStatus {
Created,
Updated,
Unchanged,
Skipped,
Error,
}
impl IdeStatus {
fn icon(self) -> &'static str {
match self {
IdeStatus::Created | IdeStatus::Updated | IdeStatus::Unchanged => "+",
IdeStatus::Skipped => "!",
IdeStatus::Error => "x",
}
}
fn from_action(action: Action) -> Self {
match action {
Action::Created => IdeStatus::Created,
Action::Updated => IdeStatus::Updated,
Action::Unchanged => IdeStatus::Unchanged,
}
}
}
#[derive(Debug, Serialize)]
pub struct DiffPair {
pub before: Option<String>,
pub after: String,
}
#[derive(Debug, Serialize)]
pub struct IdeStep {
pub client: String,
pub scope: String,
pub path: String,
pub status: IdeStatus,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub diff: Option<DiffPair>,
}
#[derive(Debug, Serialize, Default)]
pub struct IdeSummary {
pub total: usize,
pub created: usize,
pub updated: usize,
pub unchanged: usize,
pub skipped: usize,
pub error: usize,
}
#[derive(Debug, Serialize)]
pub struct IdeReport {
pub steps: Vec<IdeStep>,
pub summary: IdeSummary,
}
impl IdeReport {
pub fn has_errors(&self) -> bool {
self.summary.error > 0
}
pub fn render_human(&self) -> String {
let mut out = String::new();
for step in &self.steps {
out.push_str(&format!(
"{} {} ({}, {}): {}\n",
step.status.icon(),
step.client,
step.scope,
step.path,
step.message,
));
if let Some(diff) = &step.diff {
if let Some(before) = &diff.before {
out.push_str(" --- before ---\n");
for line in before.lines() {
out.push_str(" ");
out.push_str(line);
out.push('\n');
}
}
out.push_str(" --- after ---\n");
for line in diff.after.lines() {
out.push_str(" ");
out.push_str(line);
out.push('\n');
}
}
}
let s = &self.summary;
out.push_str(&format!(
"\n{} clients: {} created, {} updated, {} unchanged, {} skipped, {} errors\n",
s.total, s.created, s.updated, s.unchanged, s.skipped, s.error
));
out
}
}
mod catalogue;
mod merge;
mod picker;
mod run;
pub use run::{cmd_ide, cmd_install};
#[cfg(test)]
mod tests;