intent_engine/cli.rs
1use clap::{Parser, Subcommand};
2
3#[derive(Parser, Clone)]
4#[command(name = "intent-engine")]
5#[command(about = "A command-line database service for tracking strategic intent", long_about = None)]
6#[command(version)]
7pub struct Cli {
8 /// Enable verbose output (-v)
9 #[arg(short, long, action = clap::ArgAction::Count)]
10 pub verbose: u8,
11
12 /// Suppress non-error output (-q)
13 #[arg(short, long)]
14 pub quiet: bool,
15
16 /// Output logs in JSON format
17 #[arg(long)]
18 pub json: bool,
19
20 #[command(subcommand)]
21 pub command: Commands,
22}
23
24#[derive(Subcommand, Clone)]
25pub enum Commands {
26 /// Task management commands
27 #[command(subcommand)]
28 Task(TaskCommands),
29
30 /// Workspace state management
31 Current {
32 /// Set the current task ID (for backward compatibility)
33 #[arg(long)]
34 set: Option<i64>,
35
36 #[command(subcommand)]
37 command: Option<CurrentAction>,
38 },
39
40 /// Generate analysis and reports
41 Report {
42 /// Time duration (e.g., "7d", "2h", "30m")
43 #[arg(long)]
44 since: Option<String>,
45
46 /// Filter by status
47 #[arg(long)]
48 status: Option<String>,
49
50 /// Filter by name pattern (FTS5)
51 #[arg(long)]
52 filter_name: Option<String>,
53
54 /// Filter by spec pattern (FTS5)
55 #[arg(long)]
56 filter_spec: Option<String>,
57
58 /// Output format
59 #[arg(long, default_value = "json")]
60 format: String,
61
62 /// Return summary only
63 #[arg(long)]
64 summary_only: bool,
65 },
66
67 /// Create or update task structures declaratively
68 ///
69 /// Reads a JSON plan from stdin and creates/updates tasks atomically.
70 /// Supports hierarchical nesting and name-based dependencies.
71 ///
72 /// Example:
73 /// echo '{"tasks": [{"name": "Task A", "children": [{"name": "Task B"}]}]}' | ie plan
74 Plan {
75 /// Show what would be created without actually doing it
76 #[arg(long)]
77 dry_run: bool,
78
79 /// Output format (text or json)
80 #[arg(long, default_value = "text")]
81 format: String,
82 },
83
84 /// Event logging commands
85 #[command(subcommand)]
86 Event(EventCommands),
87
88 /// Unified search across tasks and events
89 Search {
90 /// Search query (supports FTS5 syntax like "JWT AND authentication")
91 query: String,
92
93 /// Search in tasks (default: true)
94 #[arg(long, default_value = "true")]
95 tasks: bool,
96
97 /// Search in events (default: true)
98 #[arg(long, default_value = "true")]
99 events: bool,
100
101 /// Maximum number of results (default: 20)
102 #[arg(long)]
103 limit: Option<i64>,
104 },
105
106 /// Check system health and dependencies
107 Doctor,
108
109 /// Start MCP server for AI assistants (JSON-RPC stdio)
110 #[command(name = "mcp-server")]
111 McpServer,
112
113 /// Restore session context for AI agents (Focus Restoration - Phase 1)
114 ///
115 /// This command returns all context needed to restore work continuity:
116 /// - Current focused task
117 /// - Parent task and siblings progress
118 /// - Child tasks status
119 /// - Recent events (decisions, blockers, notes)
120 /// - Suggested next commands
121 ///
122 /// Designed for SessionStart hooks to inject context at the beginning of new sessions.
123 #[command(name = "session-restore")]
124 SessionRestore {
125 /// Number of recent events to include (default: 3)
126 #[arg(long, default_value = "3")]
127 include_events: usize,
128
129 /// Workspace path (default: current directory)
130 #[arg(long)]
131 workspace: Option<String>,
132 },
133
134 /// Dashboard management commands
135 #[command(subcommand)]
136 Dashboard(DashboardCommands),
137
138 /// Unified setup command for AI tool integrations
139 ///
140 /// This command provides a unified interface for setting up intent-engine integration
141 /// with various AI assistant tools. It handles both hook installation and MCP server
142 /// configuration in one step.
143 ///
144 /// Features:
145 /// - User-level or project-level installation
146 /// - Atomic setup with rollback on failure
147 /// - Built-in connectivity testing
148 /// - Diagnosis mode for troubleshooting
149 Setup {
150 /// Target tool to configure (claude-code, gemini-cli, codex)
151 #[arg(long)]
152 target: Option<String>,
153
154 /// Installation scope: user (default, recommended), project, or both
155 #[arg(long, default_value = "user")]
156 scope: String,
157
158 /// Show what would be done without actually doing it
159 #[arg(long)]
160 dry_run: bool,
161
162 /// Overwrite existing configuration
163 #[arg(long)]
164 force: bool,
165
166 /// Run diagnosis on existing setup instead of installing
167 #[arg(long)]
168 diagnose: bool,
169
170 /// Custom config file path (advanced)
171 #[arg(long)]
172 config_path: Option<String>,
173
174 /// Project directory for INTENT_ENGINE_PROJECT_DIR env var
175 #[arg(long)]
176 project_dir: Option<String>,
177 },
178
179 // ========================================
180 // Hybrid Commands - High-Frequency Aliases
181 // ========================================
182 // These are convenience aliases for the most common operations.
183 // They provide a verb-centric, streamlined UX for frequent actions.
184 // See: docs/architecture-03-cli-hybrid-command.md
185 /// Add a new task (alias for 'task add')
186 #[command(alias = "a")]
187 Add {
188 /// Task name
189 name: String,
190
191 /// Detailed specification (markdown)
192 #[arg(long)]
193 spec: Option<String>,
194
195 /// Parent task ID
196 #[arg(long)]
197 parent: Option<i64>,
198
199 /// Priority (critical, high, medium, low)
200 #[arg(long)]
201 priority: Option<String>,
202 },
203
204 /// Start a task and set focus (alias for 'task start')
205 #[command(alias = "s")]
206 Start {
207 /// Task ID
208 id: i64,
209
210 /// Include events summary
211 #[arg(long, default_value = "true")]
212 with_events: bool,
213 },
214
215 /// Complete the current focused task (alias for 'task done')
216 #[command(alias = "d")]
217 Done,
218
219 /// Switch to a different task (alias for 'task switch')
220 #[command(alias = "sw")]
221 Switch {
222 /// Task ID
223 id: i64,
224
225 /// Include events summary
226 #[arg(long)]
227 with_events: bool,
228 },
229
230 /// Record an event for current task (alias for 'event add')
231 Log {
232 /// Event type (decision, blocker, milestone, note)
233 #[arg(value_parser = ["decision", "blocker", "milestone", "note"])]
234 event_type: String,
235
236 /// Event data/description
237 data: String,
238
239 /// Task ID (optional, uses current task if not specified)
240 #[arg(long)]
241 task_id: Option<i64>,
242 },
243
244 /// Get the next recommended task (alias for 'task pick-next')
245 #[command(alias = "n")]
246 Next {
247 /// Output format (text or json)
248 #[arg(long, default_value = "text")]
249 format: String,
250 },
251
252 /// List tasks with filters (alias for 'task list')
253 ///
254 /// Examples:
255 /// ie ls # List all tasks
256 /// ie ls todo # List todo tasks
257 /// ie ls doing # List doing tasks
258 /// ie ls done # List done tasks
259 #[command(alias = "ls")]
260 List {
261 /// Filter by status (todo, doing, done)
262 status: Option<String>,
263
264 /// Filter by parent ID (use "null" for no parent)
265 #[arg(long)]
266 parent: Option<String>,
267 },
268
269 /// Get task context (alias for 'task context')
270 #[command(alias = "ctx")]
271 Context {
272 /// Task ID (optional, uses current task if omitted)
273 task_id: Option<i64>,
274 },
275
276 /// Get task details (alias for 'task get')
277 Get {
278 /// Task ID
279 id: i64,
280
281 /// Include events summary
282 #[arg(long)]
283 with_events: bool,
284 },
285}
286
287#[derive(Subcommand, Clone)]
288pub enum CurrentAction {
289 /// Set the current task (low-level atomic command, prefer 'ie task start')
290 #[command(hide = true)]
291 Set {
292 /// Task ID to set as current
293 task_id: i64,
294 },
295
296 /// Clear the current task (low-level atomic command, prefer 'ie task done')
297 #[command(hide = true)]
298 Clear,
299}
300
301#[derive(Subcommand, Clone)]
302pub enum TaskCommands {
303 /// Add a new task
304 Add {
305 /// Task name
306 #[arg(long)]
307 name: String,
308
309 /// Parent task ID
310 #[arg(long)]
311 parent: Option<i64>,
312
313 /// Read spec from stdin
314 #[arg(long)]
315 spec_stdin: bool,
316 },
317
318 /// Get a task by ID
319 Get {
320 /// Task ID
321 id: i64,
322
323 /// Include events summary
324 #[arg(long)]
325 with_events: bool,
326 },
327
328 /// Update a task
329 Update {
330 /// Task ID
331 id: i64,
332
333 /// New task name
334 #[arg(long)]
335 name: Option<String>,
336
337 /// New parent task ID
338 #[arg(long)]
339 parent: Option<i64>,
340
341 /// New status
342 #[arg(long)]
343 status: Option<String>,
344
345 /// Task complexity (1-10)
346 #[arg(long)]
347 complexity: Option<i32>,
348
349 /// Task priority (critical, high, medium, low)
350 #[arg(long)]
351 priority: Option<String>,
352
353 /// Read spec from stdin
354 #[arg(long)]
355 spec_stdin: bool,
356 },
357
358 /// Delete a task
359 Del {
360 /// Task ID
361 id: i64,
362 },
363
364 /// List tasks with filters
365 ///
366 /// Examples:
367 /// ie task list # List all tasks
368 /// ie task list todo # List todo tasks
369 /// ie task list doing # List doing tasks
370 /// ie task list done # List done tasks
371 List {
372 /// Filter by status (todo, doing, done)
373 status: Option<String>,
374
375 /// Filter by parent ID (use "null" for no parent)
376 #[arg(long)]
377 parent: Option<String>,
378 },
379
380 /// Start a task (atomic: update status + set current)
381 Start {
382 /// Task ID
383 id: i64,
384
385 /// Include events summary
386 #[arg(long)]
387 with_events: bool,
388 },
389
390 /// Complete the current focused task (atomic: check children + update status + clear current)
391 /// This command only operates on the current_task_id. It will:
392 /// - Check all subtasks are done
393 /// - Update the task status to done
394 /// - Clear the current_task_id
395 ///
396 /// Prerequisites: A task must be set as current (via `current --set <ID>`)
397 Done,
398
399 /// Intelligently recommend the next task to work on
400 ///
401 /// This command uses a context-aware priority model to recommend a single task:
402 /// 1. First priority: Subtasks of the current focused task (depth-first)
403 /// 2. Second priority: Top-level tasks (breadth-first)
404 ///
405 /// The command is non-interactive and does not modify task status.
406 PickNext {
407 /// Output format (text or json)
408 #[arg(long, default_value = "text")]
409 format: String,
410 },
411
412 /// Create a subtask under current task and switch to it
413 SpawnSubtask {
414 /// Subtask name
415 #[arg(long)]
416 name: String,
417
418 /// Read spec from stdin
419 #[arg(long)]
420 spec_stdin: bool,
421 },
422
423 /// Switch to a specific task (atomic: update to doing + set current)
424 Switch {
425 /// Task ID
426 id: i64,
427 },
428
429 /// Add a dependency between tasks
430 ///
431 /// Creates a dependency where BLOCKED_TASK depends on BLOCKING_TASK.
432 /// The BLOCKING_TASK must be completed before BLOCKED_TASK can be started.
433 ///
434 /// Example: `task depends-on 42 41` means Task 42 depends on Task 41
435 /// (Task 41 must be done before Task 42 can start)
436 #[command(name = "depends-on")]
437 DependsOn {
438 /// Task ID that has the dependency (blocked task)
439 blocked_task_id: i64,
440
441 /// Task ID that must be completed first (blocking task)
442 blocking_task_id: i64,
443 },
444
445 /// Get task context (ancestors, siblings, children)
446 ///
447 /// Shows the full family tree of a task to understand its strategic context.
448 /// If no task ID is provided, uses the current focused task.
449 Context {
450 /// Task ID (optional, uses current task if omitted)
451 task_id: Option<i64>,
452 },
453}
454
455#[derive(Subcommand, Clone)]
456pub enum EventCommands {
457 /// Add a new event
458 Add {
459 /// Task ID (optional, uses current task if not specified)
460 #[arg(long)]
461 task_id: Option<i64>,
462
463 /// Log type
464 #[arg(long = "type")]
465 log_type: String,
466
467 /// Read discussion data from stdin
468 #[arg(long)]
469 data_stdin: bool,
470 },
471
472 /// List events for a task (or globally if task_id not specified)
473 List {
474 /// Task ID (optional, lists all events if not specified)
475 #[arg(long)]
476 task_id: Option<i64>,
477
478 /// Maximum number of events to return (default: 50)
479 #[arg(long)]
480 limit: Option<i64>,
481
482 /// Filter by log type (e.g., "decision", "blocker", "milestone", "note")
483 #[arg(long = "type")]
484 log_type: Option<String>,
485
486 /// Filter events created within duration (e.g., "7d", "24h", "30m")
487 #[arg(long)]
488 since: Option<String>,
489 },
490}
491
492#[derive(Subcommand, Clone)]
493pub enum DashboardCommands {
494 /// Start the Dashboard web server for current project
495 Start {
496 /// Custom port (default: 11391)
497 #[arg(long)]
498 port: Option<u16>,
499
500 /// Run in foreground (default: daemon mode)
501 #[arg(long)]
502 foreground: bool,
503
504 /// Automatically open browser (default: false, use --browser to enable)
505 #[arg(long)]
506 browser: bool,
507 },
508
509 /// Stop the Dashboard server
510 Stop {
511 /// Stop all Dashboard instances
512 #[arg(long)]
513 all: bool,
514 },
515
516 /// Show Dashboard status
517 Status {
518 /// Show status for all projects
519 #[arg(long)]
520 all: bool,
521 },
522
523 /// List all registered Dashboard instances
524 List,
525
526 /// Open Dashboard in browser
527 Open,
528}