use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use crate::explain::Format;
use crate::pulse::HeartbeatOpts;
#[derive(Parser, Debug)]
#[command(
name = "ct-steer",
version,
about = "Steer ad-hoc shell commands to the ct tool that serves them; install the PreToolUse hook.",
long_about = "ct-steer recognises the shell idioms a ct tool serves better (find | xargs grep, \
sed -i, cat | head, for-loops, && / || chains) and, as a Claude Code PreToolUse \
hook, steers the agent to the ct equivalent instead. Also reachable as `ct steer`. \
Subcommands: `hook` is the runtime hook (reads a PreToolUse envelope on stdin); \
`install`/`uninstall` wire it into .claude/settings.json; `check` shows what the \
hook would decide for a command. See `ct-steer --explain` for agent docs."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>,
#[arg(long, global = true)]
pub json: bool,
#[arg(long, global = true)]
pub quiet: bool,
#[arg(long, value_name = "SECS", global = true)]
pub timeout: Option<f64>,
#[command(flatten)]
pub heartbeat: HeartbeatOpts,
#[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
pub explain: Option<Format>,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Hook(HookArgs),
Install(InstallArgs),
Uninstall(InstallArgs),
Check(CheckArgs),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Mode {
Deny,
Ask,
Warn,
}
impl Mode {
pub fn to_lib(self) -> crate::steer::Mode {
match self {
Mode::Deny => crate::steer::Mode::Deny,
Mode::Ask => crate::steer::Mode::Ask,
Mode::Warn => crate::steer::Mode::Warn,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Tool {
#[value(name = "Bash")]
Bash,
#[value(name = "Grep")]
Grep,
#[value(name = "Glob")]
Glob,
#[value(name = "Read")]
Read,
#[value(name = "all")]
All,
}
impl Tool {
pub fn to_lib(self) -> crate::steer::install::Tool {
match self {
Tool::Bash => crate::steer::install::Tool::Bash,
Tool::Grep => crate::steer::install::Tool::Grep,
Tool::Glob => crate::steer::install::Tool::Glob,
Tool::Read => crate::steer::install::Tool::Read,
Tool::All => crate::steer::install::Tool::All,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Scope {
Project,
Local,
User,
}
impl Scope {
pub fn to_lib(self) -> crate::steer::install::Scope {
match self {
Scope::Project => crate::steer::install::Scope::Project,
Scope::Local => crate::steer::install::Scope::Local,
Scope::User => crate::steer::install::Scope::User,
}
}
}
#[derive(Args, Debug)]
pub struct HookArgs {
#[arg(long, value_enum, default_value_t = Mode::Deny)]
pub mode: Mode,
#[arg(long, value_name = "DIR")]
pub log_dir: Option<PathBuf>,
#[arg(long)]
pub no_log: bool,
}
#[derive(Args, Debug)]
pub struct InstallArgs {
#[arg(long, value_enum, default_value_t = Scope::Project)]
pub scope: Scope,
#[arg(long, value_enum, default_value_t = Mode::Deny)]
pub mode: Mode,
#[arg(long, value_enum, value_delimiter = ',', default_value = "Bash")]
pub tools: Vec<Tool>,
#[arg(long)]
pub all_tools: bool,
#[arg(long, value_name = "DIR")]
pub log_dir: Option<PathBuf>,
#[arg(long)]
pub no_log: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub print: bool,
}
#[derive(Args, Debug)]
pub struct CheckArgs {
#[arg(value_name = "COMMAND", required = true)]
pub command: String,
#[arg(long, value_enum, default_value_t = Mode::Deny)]
pub mode: Mode,
}