mod backup;
mod claude_json;
mod doctor;
mod git;
mod memory;
mod orphans;
mod output;
mod paths;
mod process;
mod prune;
mod secrets;
mod show;
mod transcripts;
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use colored::control;
use std::path::PathBuf;
use std::process::ExitCode;
#[derive(Clone, Copy, PartialEq, clap::ValueEnum)]
enum ColorMode {
Always,
Auto,
Never,
}
fn color_override(mode: ColorMode, json: bool) -> Option<bool> {
match mode {
ColorMode::Always => Some(true),
ColorMode::Never => Some(false),
ColorMode::Auto if json => Some(false),
ColorMode::Auto => None,
}
}
#[derive(Parser)]
#[command(
name = "midden",
about = "Resolve, audit, visualize, and clean coding-agent context and state",
version,
propagate_version = true
)]
struct Cli {
#[arg(long, default_value = "auto", global = true)]
color: ColorMode,
#[arg(long, global = true)]
json: bool,
#[arg(long, global = true, value_name = "PATH")]
config: Option<PathBuf>,
#[arg(long, global = true, value_name = "PATH")]
claude_home: Option<PathBuf>,
#[arg(long, global = true, value_name = "PATH")]
codex_home: Option<PathBuf>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Prune {
#[arg(long)]
apply: bool,
#[arg(long)]
transcripts: bool,
#[arg(long = "worktrees-only")]
worktrees_only: bool,
#[arg(long)]
force: bool,
},
Doctor {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long)]
fix: bool,
#[arg(long)]
force: bool,
#[arg(long = "show-secrets")]
show_secrets: bool,
},
Show {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long = "show-secrets")]
show_secrets: bool,
},
Memory {
#[command(subcommand)]
command: MemoryCommand,
},
Completions {
shell: Shell,
},
}
#[derive(Subcommand)]
enum MemoryCommand {
Show {
#[arg(default_value = ".")]
path: PathBuf,
#[arg(long, value_enum, default_value = "all")]
provider: memory::ProviderFilter,
#[arg(long)]
all: bool,
},
}
#[cfg(unix)]
#[expect(
unsafe_code,
reason = "restoring the default SIGPIPE disposition requires libc::signal"
)]
fn reset_sigpipe() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
}
#[cfg(not(unix))]
fn reset_sigpipe() {}
fn main() -> ExitCode {
reset_sigpipe();
let cli = Cli::parse();
if let Some(enabled) = color_override(cli.color, cli.json) {
control::set_override(enabled);
}
let env = paths::Env::new(cli.config.clone(), cli.claude_home.clone())
.with_codex_home(cli.codex_home.clone());
let result = match cli.command {
Command::Prune {
apply,
transcripts,
worktrees_only,
force,
} => prune::run(
&env,
prune::Options {
apply,
transcripts,
worktrees_only,
force,
json: cli.json,
},
),
Command::Doctor {
ref path,
fix,
force,
show_secrets,
} => doctor::run(
&env,
doctor::Options {
path: path.clone(),
fix,
force,
show_secrets,
json: cli.json,
},
),
Command::Show {
ref path,
show_secrets,
} => show::run(
&env,
show::Options {
path: path.clone(),
show_secrets,
json: cli.json,
},
),
Command::Memory {
command:
MemoryCommand::Show {
ref path,
provider,
all,
},
} => memory::run_show(
&env,
memory::ShowOptions {
path: path.clone(),
provider,
include_unassociated: all,
json: cli.json,
},
),
Command::Completions { shell } => {
clap_complete::generate(shell, &mut Cli::command(), "midden", &mut std::io::stdout());
return ExitCode::SUCCESS;
}
};
match result {
Ok(code) => code,
Err(e) => {
if cli.json {
let err = serde_json::json!({ "error": format!("{e:#}") });
eprintln!("{err}");
} else {
eprintln!("error: {e:#}");
}
ExitCode::from(2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_disables_color_in_auto_mode() {
assert_eq!(color_override(ColorMode::Auto, true), Some(false));
assert_eq!(color_override(ColorMode::Auto, false), None);
assert_eq!(color_override(ColorMode::Always, true), Some(true));
assert_eq!(color_override(ColorMode::Never, false), Some(false));
}
}