mi6_cli/
args.rs

1use clap::{Args, Parser, Subcommand, ValueEnum};
2use mi6_core::OtelMode;
3use std::path::PathBuf;
4use std::sync::LazyLock;
5
6/// Version string constructed at runtime.
7/// Shows "X.Y.Z (commit)" for git installs, or just "X.Y.Z" for crates.io installs.
8static VERSION: LazyLock<String> = LazyLock::new(|| {
9    let version = env!("CARGO_PKG_VERSION");
10    let commit = env!("MI6_GIT_COMMIT");
11    if commit.is_empty() {
12        version.to_string()
13    } else {
14        format!("{version} ({commit})")
15    }
16});
17
18/// Build the long version string with git commit (if available).
19fn long_version() -> &'static str {
20    &VERSION
21}
22
23/// A unified CLI for monitoring and managing agentic coding sessions.
24///
25/// Run without arguments to launch the interactive TUI.
26#[derive(Parser, Debug)]
27#[command(name = "mi6", version = long_version(), about)]
28#[command(args_conflicts_with_subcommands = true)]
29#[command(disable_help_subcommand = true)]
30pub struct Cli {
31    #[command(subcommand)]
32    pub command: Option<Commands>,
33
34    /// TUI arguments (used when no subcommand is provided)
35    #[command(flatten)]
36    pub tui_args: TuiArgs,
37}
38
39/// Arguments for the TUI interface.
40#[derive(Args, Debug, Default, Clone, PartialEq, Eq)]
41pub struct TuiArgs {
42    /// Refresh interval in milliseconds
43    #[arg(short, long, value_name = "MS")]
44    pub interval: Option<u64>,
45
46    /// Transcript poll interval in milliseconds
47    #[arg(long = "transcript-poll", value_name = "MS")]
48    pub transcript_poll: Option<u64>,
49
50    /// Show all sessions including dead ones
51    #[arg(short, long)]
52    pub all: bool,
53
54    /// Color theme (default, minimal, dracula)
55    #[arg(short, long, value_name = "THEME")]
56    pub theme: Option<String>,
57
58    /// Columns to display (comma or space separated)
59    #[arg(short = 'c', long, value_name = "COLS", num_args = 1..)]
60    pub columns: Option<Vec<String>>,
61
62    /// Enable expert mode (shows mi6 process stats)
63    #[arg(long, hide = true)]
64    pub expert: bool,
65}
66
67#[derive(Subcommand, Debug)]
68pub enum Commands {
69    /// Enable mi6 for agent framework(s)
70    Enable {
71        /// Frameworks to enable (claude, cursor, codex, etc.)
72        #[arg(value_name = "FRAMEWORKS")]
73        frameworks: Vec<String>,
74
75        /// Install hooks to project config instead of global
76        #[arg(long, conflicts_with = "settings_local")]
77        local: bool,
78
79        /// Install hooks to project local config (not committed to git)
80        #[arg(long, conflicts_with = "local")]
81        settings_local: bool,
82
83        /// Print hooks to stdout instead of writing to file
84        #[arg(long)]
85        print: bool,
86
87        /// Only initialize database, skip hook installation
88        #[arg(long)]
89        db_only: bool,
90
91        /// Only install hooks, skip database initialization
92        #[arg(long)]
93        hooks_only: bool,
94
95        /// Configure OpenTelemetry for token tracking
96        #[arg(long, conflicts_with = "no_otel")]
97        otel: bool,
98
99        /// Port for OTel server
100        #[arg(long, default_value = "4318")]
101        otel_port: u16,
102
103        /// Remove OTel configuration
104        #[arg(long, conflicts_with = "otel")]
105        no_otel: bool,
106    },
107
108    /// Disable mi6 for agent framework(s)
109    Disable {
110        /// Frameworks to disable
111        #[arg(value_name = "FRAMEWORKS")]
112        frameworks: Vec<String>,
113
114        /// Remove from project config instead of global
115        #[arg(long, conflicts_with = "settings_local")]
116        local: bool,
117
118        /// Remove from project local config
119        #[arg(long, conflicts_with = "local")]
120        settings_local: bool,
121
122        /// Print what would be removed without modifying files
123        #[arg(long)]
124        print: bool,
125    },
126
127    /// Ingest data into mi6
128    #[command(subcommand)]
129    Ingest(IngestCommands),
130
131    /// Show session details
132    Session {
133        /// Session ID or PID
134        session_or_pid: String,
135
136        /// Filter by machine ID (for multi-machine scenarios)
137        #[arg(long, short = 'm')]
138        machine: Option<String>,
139
140        /// Output as JSON (all fields)
141        #[arg(long)]
142        json: bool,
143
144        /// Filter to specific fields (comma-separated)
145        #[arg(long, value_delimiter = ',')]
146        fields: Option<Vec<String>>,
147    },
148
149    /// Show mi6 status
150    Status {
151        /// Output as JSON
152        #[arg(long)]
153        json: bool,
154
155        /// Show additional details (framework config paths)
156        #[arg(short, long)]
157        verbose: bool,
158    },
159
160    /// Launch interactive TUI
161    Tui(TuiArgs),
162
163    /// Display event stream
164    Watch {
165        /// Filter by session ID
166        #[arg(short, long)]
167        session: Option<String>,
168
169        /// Filter by event type
170        #[arg(short = 't', long = "type")]
171        event_type: Option<String>,
172
173        /// Filter by permission mode
174        #[arg(short, long)]
175        mode: Option<String>,
176
177        /// Filter by framework
178        #[arg(long, short = 'f')]
179        framework: Option<String>,
180
181        /// Poll interval in milliseconds
182        #[arg(long, default_value = "500")]
183        poll_ms: u64,
184    },
185
186    /// Run garbage collection to remove old data
187    #[command(hide = true)]
188    Gc {
189        /// Show what would be removed without actually removing
190        #[arg(long = "dry", visible_alias = "dry-run")]
191        dry_run: bool,
192    },
193
194    /// Manage the OpenTelemetry server
195    #[command(hide = true, subcommand)]
196    Otel(OtelCommands),
197
198    /// Upgrade mi6 to the latest version
199    Upgrade {
200        /// Target version (only for cargo installs)
201        #[arg(short = 'V', long)]
202        version: Option<String>,
203
204        /// Skip confirmation prompt
205        #[arg(short, long)]
206        yes: bool,
207
208        /// Check for updates without installing
209        #[arg(long = "dry", visible_alias = "dry-run")]
210        dry_run: bool,
211
212        /// Installation method to use (defaults to current method)
213        #[arg(short, long, value_enum)]
214        method: Option<UpgradeMethod>,
215
216        /// Source path for cargo_source method
217        #[arg(long = "source-path", requires_if("cargo_source", "method"))]
218        source_path: Option<PathBuf>,
219    },
220
221    /// Uninstall mi6 from the system
222    Uninstall {
223        /// Skip confirmation prompt
224        #[arg(long)]
225        confirm: bool,
226
227        /// Keep mi6 data (database, config, TUI settings)
228        #[arg(long)]
229        keep_data: bool,
230
231        /// Show what would happen without actually doing it
232        #[arg(long = "dry", visible_alias = "dry-run")]
233        dry_run: bool,
234    },
235}
236
237/// Subcommands for ingesting data into mi6.
238#[derive(Subcommand, Debug)]
239pub enum IngestCommands {
240    /// Ingest an event from AI framework hooks
241    Event {
242        /// Event type (SessionStart, PreToolUse, etc.)
243        event_type: Option<String>,
244
245        /// JSON payload (reads from stdin if not provided)
246        json_payload: Option<String>,
247
248        /// AI framework logging the event
249        #[arg(long, short = 'f')]
250        framework: Option<String>,
251    },
252
253    /// Ingest events from a transcript file
254    Transcript {
255        /// Path to the JSONL transcript file
256        path: PathBuf,
257    },
258
259    /// Ingest events from a Codex session file
260    CodexSession {
261        /// Session ID (UUID from filename) or file path
262        session_or_path: String,
263
264        /// Treat the argument as a file path instead of session ID
265        #[arg(long, short = 'f')]
266        file: bool,
267    },
268
269    /// Ingest OpenTelemetry data from stdin
270    Otel,
271}
272
273/// Installation method for upgrading mi6.
274#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
275pub enum UpgradeMethod {
276    /// Homebrew (brew install mi6)
277    Brew,
278    /// GitHub releases (standalone binary)
279    Github,
280    /// Cargo from local source (cargo install --path)
281    CargoSource,
282    /// Cargo from crates.io (cargo install mi6)
283    CargoCrates,
284}
285
286/// Subcommands for managing the OpenTelemetry server.
287#[derive(Subcommand, Debug)]
288pub enum OtelCommands {
289    /// Start the OTel server (daemonized)
290    Start {
291        /// Port to listen on
292        #[arg(short, long, default_value = "4318")]
293        port: u16,
294
295        /// Processing mode
296        #[arg(short, long)]
297        mode: Option<OtelMode>,
298    },
299
300    /// Stop the running OTel server
301    Stop {
302        /// Port to stop
303        #[arg(short, long, default_value = "4318")]
304        port: u16,
305    },
306
307    /// Restart the OTel server
308    Restart {
309        /// Port to restart on
310        #[arg(short, long, default_value = "4318")]
311        port: u16,
312
313        /// Processing mode
314        #[arg(short, long)]
315        mode: Option<OtelMode>,
316    },
317
318    /// Show OTel server status
319    Status {
320        /// Port to check
321        #[arg(short, long, default_value = "4318")]
322        port: u16,
323    },
324
325    /// Run OTel server in foreground (for debugging)
326    Run {
327        /// Port to listen on
328        #[arg(short, long, default_value = "4318")]
329        port: u16,
330
331        /// Processing mode
332        #[arg(short, long)]
333        mode: Option<OtelMode>,
334    },
335}