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