Skip to main content

intent_engine/
cli.rs

1use clap::{Parser, Subcommand};
2
3const LONG_ABOUT: &str = r#"
4Intent-Engine - AI Long-Term Task Memory System
5
6What does it offer beyond Claude Code's built-in TodoWrite?
7  ✅ Cross-session persistence (never lost)
8  ✅ Hierarchical task trees (parent-child, dependencies)
9  ✅ Decision logs (why you made choices)
10  ✅ Web Dashboard (visual management)
11
12When to use ie instead of TodoWrite?
13  • Would be a shame to lose it → use ie
14  • Use once and discard → use TodoWrite
15
16AI Workflow:
17  ie status   ← Run at session start to restore context
18  ie plan     ← Declarative task management (create/update/complete)
19  ie log      ← Record decisions, blockers, milestones
20  ie search   ← Search tasks and event history
21
22Key Rules:
23  • status:doing requires spec (description)
24  • status:done requires all children complete first
25  • parent_id:null creates independent root task
26
27Documentation:
28  • User Guide: docs/help/user-guide.md
29  • System Prompt: docs/system_prompt.md
30  • Interface Spec: docs/spec-03-interface-current.md
31"#;
32
33#[derive(Parser, Clone)]
34#[command(name = "intent-engine")]
35#[command(
36    about = "AI Long-Term Task Memory - Cross-session persistence, hierarchical tasks, decision logs"
37)]
38#[command(long_about = LONG_ABOUT)]
39#[command(version)]
40pub struct Cli {
41    /// Enable verbose output (-v)
42    #[arg(short, long, action = clap::ArgAction::Count)]
43    pub verbose: u8,
44
45    /// Suppress non-error output (-q)
46    #[arg(short, long)]
47    pub quiet: bool,
48
49    /// Output logs in JSON format
50    #[arg(long)]
51    pub json: bool,
52
53    #[command(subcommand)]
54    pub command: Commands,
55}
56
57#[derive(Subcommand, Clone)]
58pub enum Commands {
59    /// Create or update task structures declaratively
60    #[command(long_about = include_str!("../docs/help/plan.md"))]
61    Plan {
62        /// Output format (text or json)
63        #[arg(long, default_value = "text")]
64        format: String,
65    },
66
67    /// Record events (decisions, blockers, milestones, notes)
68    ///
69    /// Quick event logging for the current focused task or a specific task.
70    ///
71    /// Examples:
72    ///   ie log decision "Chose JWT authentication"
73    ///   ie log blocker "API rate limit hit" --task 42
74    ///   ie log milestone "MVP complete"
75    ///   ie log note "Consider caching optimization"
76    Log {
77        /// Event type: decision, blocker, milestone, note
78        #[arg(value_enum)]
79        event_type: LogEventType,
80
81        /// Event message (markdown supported)
82        message: String,
83
84        /// Target task ID (optional, uses current focused task if not specified)
85        #[arg(long)]
86        task: Option<i64>,
87
88        /// Output format (text or json)
89        #[arg(long, default_value = "text")]
90        format: String,
91    },
92
93    /// Unified search across tasks and events
94    ///
95    /// Smart keyword detection:
96    ///   - Query with ONLY status keywords (todo, doing, done) → filters by status
97    ///   - Query with other words → uses FTS5 full-text search
98    ///
99    /// Status filter examples (returns tasks with matching status):
100    ///   ie search "todo doing"     # All unfinished tasks (AI session start)
101    ///   ie search "todo"           # Only todo tasks
102    ///   ie search "done"           # Only completed tasks
103    ///
104    /// FTS5 search examples (full-text search):
105    ///   ie search "JWT authentication"
106    ///   ie search "API AND client"
107    ///   ie search "blocker" --events --no-tasks
108    Search {
109        /// Search query: status keywords (todo/doing/done) or FTS5 syntax
110        query: String,
111
112        /// Search in tasks (default: true)
113        #[arg(long, default_value = "true")]
114        tasks: bool,
115
116        /// Search in events (default: true)
117        #[arg(long, default_value = "true")]
118        events: bool,
119
120        /// Maximum number of results (default: 20)
121        #[arg(long)]
122        limit: Option<i64>,
123
124        /// Result offset for pagination (default: 0)
125        #[arg(long)]
126        offset: Option<i64>,
127
128        /// Filter by start time (e.g., "7d", "1w", "2025-01-01")
129        #[arg(long)]
130        since: Option<String>,
131
132        /// Filter by end time (e.g., "1d", "2025-12-31")
133        #[arg(long)]
134        until: Option<String>,
135
136        /// Output format (text or json)
137        #[arg(long, default_value = "text")]
138        format: String,
139    },
140
141    /// Initialize a new Intent-Engine project
142    ///
143    /// Creates a .intent-engine directory with database in the current working directory.
144    ///
145    /// Examples:
146    ///   ie init                    # Initialize in current directory
147    ///   ie init --at /my/project   # Initialize at specific directory
148    Init {
149        /// Custom directory to initialize (default: current directory)
150        #[arg(long)]
151        at: Option<String>,
152
153        /// Re-initialize even if .intent-engine already exists
154        #[arg(long)]
155        force: bool,
156    },
157
158    /// Dashboard management commands
159    #[command(subcommand)]
160    Dashboard(DashboardCommands),
161
162    /// Check system health and dependencies
163    Doctor,
164
165    /// Show current task context (focus spotlight)
166    ///
167    /// Displays the focused task with its complete context:
168    /// - Current task details (full info)
169    /// - Ancestors chain (full info)
170    /// - Siblings (id + name + status)
171    /// - Descendants (id + name + status + parent_id)
172    ///
173    /// Examples:
174    ///   ie status              # Show current focused task context
175    ///   ie status 42           # Show task 42's context (without changing focus)
176    ///   ie status -e           # Include event history
177    Status {
178        /// Task ID to inspect (optional, defaults to current focused task)
179        task_id: Option<i64>,
180
181        /// Include event history
182        #[arg(short = 'e', long)]
183        with_events: bool,
184
185        /// Output format (text or json)
186        #[arg(long, default_value = "text")]
187        format: String,
188    },
189
190    /// CRUD operations on individual tasks
191    ///
192    /// Direct task manipulation commands for creating, reading, updating,
193    /// and deleting tasks, plus workflow shortcuts (start, done, next).
194    ///
195    /// Examples:
196    ///   ie task create "Implement auth" --description "JWT-based auth"
197    ///   ie task get 42 --with-context
198    ///   ie task list --status todo
199    ///   ie task start 42
200    ///   ie task done
201    ///   ie task next
202    #[command(subcommand)]
203    Task(TaskCommands),
204
205    /// Manage LLM-generated suggestions
206    ///
207    /// View and dismiss suggestions generated by background analysis.
208    /// Suggestions include task structure reorganization hints and analysis errors.
209    ///
210    /// Examples:
211    ///   ie suggestions list
212    ///   ie suggestions dismiss 5
213    ///   ie suggestions dismiss --all
214    ///   ie suggestions clear
215    #[command(subcommand)]
216    Suggestions(SuggestionsCommands),
217
218    /// Configuration management (key-value store)
219    ///
220    /// Manage configuration settings stored in the project database.
221    /// Used for LLM endpoints, API keys, and other project-level settings.
222    ///
223    /// Examples:
224    ///   ie config set llm.endpoint "http://localhost:8080/v1/chat/completions"
225    ///   ie config get llm.api_key
226    ///   ie config list --prefix llm
227    ///   ie config unset llm.model
228    #[command(subcommand)]
229    Config(ConfigCommands),
230}
231
232#[derive(Subcommand, Clone)]
233pub enum SuggestionsCommands {
234    /// List all active suggestions
235    ///
236    /// Shows suggestions that haven't been dismissed yet.
237    ///
238    /// Examples:
239    ///   ie suggestions list
240    ///   ie suggestions list --format json
241    List {
242        /// Output format (text or json)
243        #[arg(long, default_value = "text")]
244        format: String,
245    },
246
247    /// Dismiss a specific suggestion
248    ///
249    /// Mark a suggestion as read/acted upon so it won't be shown again.
250    ///
251    /// Examples:
252    ///   ie suggestions dismiss 5
253    ///   ie suggestions dismiss --all
254    Dismiss {
255        /// Suggestion ID to dismiss (omit to use --all)
256        id: Option<i64>,
257
258        /// Dismiss all active suggestions
259        #[arg(long)]
260        all: bool,
261
262        /// Output format (text or json)
263        #[arg(long, default_value = "text")]
264        format: String,
265    },
266
267    /// Clear all dismissed suggestions from database
268    ///
269    /// Permanently removes dismissed suggestions to clean up the database.
270    /// Active (non-dismissed) suggestions are not affected.
271    ///
272    /// Examples:
273    ///   ie suggestions clear
274    Clear {
275        /// Output format (text or json)
276        #[arg(long, default_value = "text")]
277        format: String,
278    },
279}
280
281#[derive(Subcommand, Clone)]
282pub enum ConfigCommands {
283    /// Set a configuration value
284    ///
285    /// Examples:
286    ///   ie config set llm.endpoint "http://localhost:8080/v1/chat/completions"
287    ///   ie config set llm.api_key "sk-your-key"
288    Set {
289        /// Configuration key (e.g., llm.endpoint)
290        key: String,
291
292        /// Configuration value
293        value: String,
294
295        /// Output format (text or json)
296        #[arg(long, default_value = "text")]
297        format: String,
298    },
299
300    /// Get a configuration value
301    ///
302    /// Sensitive values (api_key, secret) are automatically masked.
303    ///
304    /// Examples:
305    ///   ie config get llm.endpoint
306    ///   ie config get llm.api_key    # Shows masked value
307    Get {
308        /// Configuration key
309        key: String,
310
311        /// Output format (text or json)
312        #[arg(long, default_value = "text")]
313        format: String,
314    },
315
316    /// List configuration entries
317    ///
318    /// Examples:
319    ///   ie config list
320    ///   ie config list --prefix llm
321    List {
322        /// Filter by key prefix (e.g., "llm" shows all llm.* keys)
323        #[arg(long)]
324        prefix: Option<String>,
325
326        /// Output format (text or json)
327        #[arg(long, default_value = "text")]
328        format: String,
329    },
330
331    /// Remove a configuration entry
332    ///
333    /// Examples:
334    ///   ie config unset llm.model
335    Unset {
336        /// Configuration key to remove
337        key: String,
338
339        /// Output format (text or json)
340        #[arg(long, default_value = "text")]
341        format: String,
342    },
343
344    /// Test LLM endpoint connectivity
345    ///
346    /// Sends a simple test message to verify the configured LLM endpoint is working.
347    ///
348    /// Examples:
349    ///   ie config test-llm
350    ///   ie config test-llm --prompt "Hello, introduce yourself"
351    TestLlm {
352        /// Custom test prompt (default: "你好,请用一句话介绍你自己")
353        #[arg(long)]
354        prompt: Option<String>,
355
356        /// Output format (text or json)
357        #[arg(long, default_value = "text")]
358        format: String,
359    },
360}
361
362#[derive(Subcommand, Clone)]
363pub enum TaskCommands {
364    /// Create a new task
365    ///
366    /// Examples:
367    ///   ie task create "Implement auth"
368    ///   ie task create "Add tests" --description "Unit + integration tests" --parent 42
369    ///   ie task create "Fix bug" --status doing --priority 1
370    Create {
371        /// Task name
372        name: String,
373
374        /// Task description/spec (markdown supported)
375        #[arg(short, long)]
376        description: Option<String>,
377
378        /// Parent task ID (0 = root task, omit = auto-parent to current focus)
379        #[arg(short, long)]
380        parent: Option<i64>,
381
382        /// Initial status (default: todo)
383        #[arg(short, long, default_value = "todo")]
384        status: String,
385
386        /// Priority (1=critical, 2=high, 3=medium, 4=low)
387        #[arg(long)]
388        priority: Option<i32>,
389
390        /// Task owner (default: human)
391        #[arg(long, default_value = "human")]
392        owner: String,
393
394        /// Metadata key=value pairs (e.g., --metadata type=epic --metadata tag=auth)
395        #[arg(long)]
396        metadata: Vec<String>,
397
398        /// IDs of tasks that block this task (this task depends on them)
399        #[arg(long = "blocked-by")]
400        blocked_by: Vec<i64>,
401
402        /// IDs of tasks that this task blocks (they depend on this task)
403        #[arg(long)]
404        blocks: Vec<i64>,
405
406        /// Output format (text or json)
407        #[arg(long, default_value = "text")]
408        format: String,
409    },
410
411    /// Get task details
412    ///
413    /// Examples:
414    ///   ie task get 42
415    ///   ie task get 42 --with-events
416    ///   ie task get 42 --with-context
417    Get {
418        /// Task ID
419        id: i64,
420
421        /// Include event history
422        #[arg(short = 'e', long)]
423        with_events: bool,
424
425        /// Include full context (ancestors, siblings, children, dependencies)
426        #[arg(short = 'c', long)]
427        with_context: bool,
428
429        /// Output format (text or json)
430        #[arg(long, default_value = "text")]
431        format: String,
432    },
433
434    /// Update an existing task
435    ///
436    /// Examples:
437    ///   ie task update 42 --name "New name"
438    ///   ie task update 42 --description "Updated spec" --priority 1
439    ///   ie task update 42 --status doing
440    ///   ie task update 42 --metadata type=epic --metadata "key="  (delete key)
441    Update {
442        /// Task ID
443        id: i64,
444
445        /// New task name
446        #[arg(long)]
447        name: Option<String>,
448
449        /// New description/spec
450        #[arg(short, long)]
451        description: Option<String>,
452
453        /// New status (todo, doing, done)
454        #[arg(short, long)]
455        status: Option<String>,
456
457        /// New priority (1=critical, 2=high, 3=medium, 4=low)
458        #[arg(long)]
459        priority: Option<i32>,
460
461        /// New active form text
462        #[arg(long)]
463        active_form: Option<String>,
464
465        /// New owner
466        #[arg(long)]
467        owner: Option<String>,
468
469        /// New parent task ID (0 = make root task)
470        #[arg(long)]
471        parent: Option<i64>,
472
473        /// Metadata key=value pairs to merge (key= to delete)
474        #[arg(long)]
475        metadata: Vec<String>,
476
477        /// Add dependency: this task is blocked by these task IDs
478        #[arg(long = "add-blocked-by")]
479        add_blocked_by: Vec<i64>,
480
481        /// Add dependency: this task blocks these task IDs
482        #[arg(long = "add-blocks")]
483        add_blocks: Vec<i64>,
484
485        /// Remove dependency: remove blocked-by relationship
486        #[arg(long = "rm-blocked-by")]
487        rm_blocked_by: Vec<i64>,
488
489        /// Remove dependency: remove blocks relationship
490        #[arg(long = "rm-blocks")]
491        rm_blocks: Vec<i64>,
492
493        /// Output format (text or json)
494        #[arg(long, default_value = "text")]
495        format: String,
496    },
497
498    /// List tasks with optional filters
499    ///
500    /// Examples:
501    ///   ie task list
502    ///   ie task list --status todo
503    ///   ie task list --parent 42
504    ///   ie task list --tree
505    List {
506        /// Filter by status (todo, doing, done)
507        #[arg(short, long)]
508        status: Option<String>,
509
510        /// Filter by parent ID (0 = root tasks only)
511        #[arg(short, long)]
512        parent: Option<i64>,
513
514        /// Sort by (id, priority, time, focus_aware)
515        #[arg(long)]
516        sort: Option<String>,
517
518        /// Maximum number of results
519        #[arg(long)]
520        limit: Option<i64>,
521
522        /// Result offset for pagination
523        #[arg(long)]
524        offset: Option<i64>,
525
526        /// Show as hierarchical tree
527        #[arg(long)]
528        tree: bool,
529
530        /// Output format (text or json)
531        #[arg(long, default_value = "text")]
532        format: String,
533    },
534
535    /// Delete a task
536    ///
537    /// Examples:
538    ///   ie task delete 42
539    ///   ie task delete 42 --cascade
540    Delete {
541        /// Task ID
542        id: i64,
543
544        /// Also delete all descendant tasks
545        #[arg(long)]
546        cascade: bool,
547
548        /// Output format (text or json)
549        #[arg(long, default_value = "text")]
550        format: String,
551    },
552
553    /// Start working on a task (sets status to doing and focuses it)
554    ///
555    /// Examples:
556    ///   ie task start 42
557    ///   ie task start 42 --description "Starting with validation layer"
558    Start {
559        /// Task ID
560        id: i64,
561
562        /// Update description before starting
563        #[arg(short, long)]
564        description: Option<String>,
565
566        /// Output format (text or json)
567        #[arg(long, default_value = "text")]
568        format: String,
569    },
570
571    /// Mark a task as done
572    ///
573    /// Examples:
574    ///   ie task done         # Complete current focused task
575    ///   ie task done 42      # Focus task 42 then complete it
576    Done {
577        /// Task ID (optional, defaults to current focused task)
578        id: Option<i64>,
579
580        /// Output format (text or json)
581        #[arg(long, default_value = "text")]
582        format: String,
583    },
584
585    /// Suggest the next task to work on
586    ///
587    /// Uses context-aware priority: subtasks of focused task first,
588    /// then top-level tasks, based on priority ordering.
589    ///
590    /// Examples:
591    ///   ie task next
592    Next {
593        /// Output format (text or json)
594        #[arg(long, default_value = "text")]
595        format: String,
596    },
597}
598
599#[derive(clap::ValueEnum, Clone, Debug)]
600pub enum LogEventType {
601    Decision,
602    Blocker,
603    Milestone,
604    Note,
605}
606
607impl LogEventType {
608    pub fn as_str(&self) -> &str {
609        match self {
610            LogEventType::Decision => "decision",
611            LogEventType::Blocker => "blocker",
612            LogEventType::Milestone => "milestone",
613            LogEventType::Note => "note",
614        }
615    }
616}
617
618#[derive(Subcommand, Clone)]
619pub enum DashboardCommands {
620    /// Start the Dashboard server
621    Start {
622        /// Port to bind (default: 11391)
623        #[arg(long)]
624        port: Option<u16>,
625
626        /// Auto-open browser
627        #[arg(long)]
628        browser: bool,
629
630        /// Start in daemon mode (background)
631        #[arg(long)]
632        daemon: bool,
633    },
634
635    /// Stop the Dashboard server
636    Stop {
637        /// Stop all running dashboards
638        #[arg(long)]
639        all: bool,
640    },
641
642    /// Show Dashboard status
643    Status {
644        /// Show all instances
645        #[arg(long)]
646        all: bool,
647    },
648
649    /// List registered projects
650    List,
651
652    /// Open Dashboard in browser
653    Open,
654}