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}