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 /// Verify hooks are working (tests hook invocation)
160 #[arg(short, long)]
161 check: bool,
162 },
163
164 /// Launch interactive TUI
165 Tui(TuiArgs),
166
167 /// Display event stream
168 Watch {
169 /// Filter by session ID
170 #[arg(short, long)]
171 session: Option<String>,
172
173 /// Filter by event type
174 #[arg(short = 't', long = "type")]
175 event_type: Option<String>,
176
177 /// Filter by permission mode
178 #[arg(short, long)]
179 mode: Option<String>,
180
181 /// Filter by framework
182 #[arg(long, short = 'f')]
183 framework: Option<String>,
184
185 /// Poll interval in milliseconds
186 #[arg(long, default_value = "500")]
187 poll_ms: u64,
188 },
189
190 /// Run cleanup to remove old data
191 #[command(hide = true)]
192 Cleanup {
193 /// Show what would be removed without actually removing
194 #[arg(long = "dry", visible_alias = "dry-run")]
195 dry_run: bool,
196 },
197
198 /// Manage the OpenTelemetry server
199 #[command(hide = true, subcommand)]
200 Otel(OtelCommands),
201
202 /// Upgrade mi6 to the latest version
203 Upgrade {
204 /// Target version (only for cargo installs)
205 #[arg(short = 'V', long)]
206 version: Option<String>,
207
208 /// Skip confirmation prompt
209 #[arg(short, long)]
210 yes: bool,
211
212 /// Check for updates without installing
213 #[arg(long = "dry", visible_alias = "dry-run")]
214 dry_run: bool,
215
216 /// Installation method to use (defaults to current method)
217 #[arg(short, long, value_enum)]
218 method: Option<UpgradeMethod>,
219
220 /// Source path for cargo_source method
221 #[arg(long = "source-path", requires_if("cargo_source", "method"))]
222 source_path: Option<PathBuf>,
223 },
224
225 /// Uninstall mi6 from the system
226 Uninstall {
227 /// Skip all confirmation prompts (auto-delete data)
228 #[arg(short, long)]
229 yes: bool,
230
231 /// Keep mi6 data (database, config, TUI settings)
232 #[arg(long)]
233 keep_data: bool,
234
235 /// Show what would happen without actually doing it
236 #[arg(long = "dry", visible_alias = "dry-run")]
237 dry_run: bool,
238 },
239}
240
241/// Subcommands for ingesting data into mi6.
242#[derive(Subcommand, Debug)]
243pub enum IngestCommands {
244 /// Ingest an event from AI framework hooks
245 Event {
246 /// Event type (SessionStart, PreToolUse, etc.)
247 event_type: Option<String>,
248
249 /// JSON payload (reads from stdin if not provided)
250 json_payload: Option<String>,
251
252 /// AI framework logging the event
253 #[arg(long, short = 'f')]
254 framework: Option<String>,
255
256 /// Verify hook invocation without processing (for status --check)
257 #[arg(long, hide = true)]
258 ping: bool,
259 },
260
261 /// Ingest events from a transcript file
262 Transcript {
263 /// Path to the JSONL transcript file
264 path: PathBuf,
265 },
266
267 /// Ingest events from a Codex session file
268 CodexSession {
269 /// Session ID (UUID from filename) or file path
270 session_or_path: String,
271
272 /// Treat the argument as a file path instead of session ID
273 #[arg(long, short = 'p')]
274 file: bool,
275 },
276
277 /// Ingest OpenTelemetry data from stdin
278 Otel,
279}
280
281/// Installation method for upgrading mi6.
282#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
283pub enum UpgradeMethod {
284 /// Homebrew (brew install mi6)
285 Brew,
286 /// GitHub releases (standalone binary)
287 Github,
288 /// Cargo from local source (cargo install --path)
289 CargoSource,
290 /// Cargo from crates.io (cargo install mi6)
291 CargoCrates,
292}
293
294/// Subcommands for managing the OpenTelemetry server.
295#[derive(Subcommand, Debug)]
296pub enum OtelCommands {
297 /// Start the OTel server (daemonized)
298 Start {
299 /// Port to listen on
300 #[arg(short, long, default_value = "4318")]
301 port: u16,
302
303 /// Processing mode
304 #[arg(short, long)]
305 mode: Option<OtelMode>,
306 },
307
308 /// Stop the running OTel server
309 Stop {
310 /// Port to stop
311 #[arg(short, long, default_value = "4318")]
312 port: u16,
313 },
314
315 /// Restart the OTel server
316 Restart {
317 /// Port to restart on
318 #[arg(short, long, default_value = "4318")]
319 port: u16,
320
321 /// Processing mode
322 #[arg(short, long)]
323 mode: Option<OtelMode>,
324 },
325
326 /// Show OTel server status
327 Status {
328 /// Port to check
329 #[arg(short, long, default_value = "4318")]
330 port: u16,
331 },
332
333 /// Run OTel server in foreground (for debugging)
334 Run {
335 /// Port to listen on
336 #[arg(short, long, default_value = "4318")]
337 port: u16,
338
339 /// Processing mode
340 #[arg(short, long)]
341 mode: Option<OtelMode>,
342 },
343}