1use clap::{Args, Parser, Subcommand};
15
16use crate::explain::Format;
17use crate::pulse::HeartbeatOpts;
18
19#[derive(Parser, Debug)]
20#[command(
21 name = "ct-steer",
22 version,
23 about = "Steer ad-hoc shell commands to the ct tool that serves them; install the PreToolUse hook.",
24 long_about = "ct-steer recognises the shell idioms a ct tool serves better (find | xargs grep, \
25 sed -i, cat | head, for-loops, && / || chains) and, as a Claude Code PreToolUse \
26 hook, steers the agent to the ct equivalent instead. Also reachable as `ct steer`. \
27 Subcommands: `hook` is the runtime hook (reads a PreToolUse envelope on stdin); \
28 `install`/`uninstall` wire it into .claude/settings.json; `check` shows what the \
29 hook would decide for a command. See `ct-steer --explain` for agent docs."
30)]
31pub struct Cli {
32 #[command(subcommand)]
33 pub command: Option<Command>,
34
35 #[arg(long, global = true)]
37 pub json: bool,
38
39 #[arg(long, global = true)]
41 pub quiet: bool,
42
43 #[arg(long, value_name = "SECS", global = true)]
45 pub timeout: Option<f64>,
46
47 #[command(flatten)]
48 pub heartbeat: HeartbeatOpts,
49
50 #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
52 pub explain: Option<Format>,
53}
54
55#[derive(Subcommand, Debug)]
57pub enum Command {
58 Hook(HookArgs),
60 Install(InstallArgs),
62 Uninstall(InstallArgs),
64 Check(CheckArgs),
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
70pub enum Mode {
71 Deny,
73 Ask,
75 Warn,
77}
78
79impl Mode {
80 pub fn to_lib(self) -> crate::steer::Mode {
82 match self {
83 Mode::Deny => crate::steer::Mode::Deny,
84 Mode::Ask => crate::steer::Mode::Ask,
85 Mode::Warn => crate::steer::Mode::Warn,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
92pub enum Tool {
93 #[value(name = "Bash")]
95 Bash,
96 #[value(name = "Grep")]
98 Grep,
99 #[value(name = "Glob")]
101 Glob,
102 #[value(name = "Read")]
104 Read,
105}
106
107impl Tool {
108 pub fn to_lib(self) -> crate::steer::install::Tool {
110 match self {
111 Tool::Bash => crate::steer::install::Tool::Bash,
112 Tool::Grep => crate::steer::install::Tool::Grep,
113 Tool::Glob => crate::steer::install::Tool::Glob,
114 Tool::Read => crate::steer::install::Tool::Read,
115 }
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
121pub enum Scope {
122 Project,
124 Local,
126 User,
128}
129
130impl Scope {
131 pub fn to_lib(self) -> crate::steer::install::Scope {
133 match self {
134 Scope::Project => crate::steer::install::Scope::Project,
135 Scope::Local => crate::steer::install::Scope::Local,
136 Scope::User => crate::steer::install::Scope::User,
137 }
138 }
139}
140
141#[derive(Args, Debug)]
142pub struct HookArgs {
143 #[arg(long, value_enum, default_value_t = Mode::Deny)]
145 pub mode: Mode,
146}
147
148#[derive(Args, Debug)]
149pub struct InstallArgs {
150 #[arg(long, value_enum, default_value_t = Scope::Project)]
152 pub scope: Scope,
153
154 #[arg(long, value_enum, default_value_t = Mode::Deny)]
156 pub mode: Mode,
157
158 #[arg(long, value_enum, value_delimiter = ',', default_value = "Bash")]
160 pub tools: Vec<Tool>,
161
162 #[arg(long)]
164 pub dry_run: bool,
165
166 #[arg(long)]
168 pub print: bool,
169}
170
171#[derive(Args, Debug)]
172pub struct CheckArgs {
173 #[arg(value_name = "COMMAND", required = true)]
175 pub command: String,
176
177 #[arg(long, value_enum, default_value_t = Mode::Deny)]
179 pub mode: Mode,
180}