1pub mod mcp;
4
5use clap::{Parser, Subcommand};
6use std::io::Write;
7use std::path::PathBuf;
8use things3_core::{Result, ThingsConfig, ThingsDatabase};
9
10#[derive(Parser, Debug)]
11#[command(name = "things3")]
12#[command(about = "Things 3 CLI with integrated MCP server")]
13#[command(version)]
14pub struct Cli {
15 #[arg(long, short)]
17 pub database: Option<PathBuf>,
18
19 #[arg(long)]
21 pub fallback_to_default: bool,
22
23 #[arg(long, short)]
25 pub verbose: bool,
26
27 #[command(subcommand)]
28 pub command: Commands,
29}
30
31#[derive(Subcommand, Debug, PartialEq)]
32pub enum Commands {
33 Inbox {
35 #[arg(long, short)]
37 limit: Option<usize>,
38 },
39 Today {
41 #[arg(long, short)]
43 limit: Option<usize>,
44 },
45 Projects {
47 #[arg(long)]
49 area: Option<String>,
50 #[arg(long, short)]
52 limit: Option<usize>,
53 },
54 Areas {
56 #[arg(long, short)]
58 limit: Option<usize>,
59 },
60 Search {
62 query: String,
64 #[arg(long, short)]
66 limit: Option<usize>,
67 },
68 Mcp,
70 Health,
72}
73
74pub fn print_tasks<W: Write>(
75 _db: &ThingsDatabase,
76 tasks: &[things3_core::Task],
77 writer: &mut W,
78) -> Result<()> {
79 if tasks.is_empty() {
80 writeln!(writer, "No tasks found")?;
81 return Ok(());
82 }
83
84 writeln!(writer, "Found {} tasks:", tasks.len())?;
85 for task in tasks {
86 writeln!(writer, " • {} ({:?})", task.title, task.task_type)?;
87 if let Some(notes) = &task.notes {
88 writeln!(writer, " Notes: {notes}")?;
89 }
90 if let Some(deadline) = &task.deadline {
91 writeln!(writer, " Deadline: {deadline}")?;
92 }
93 if !task.tags.is_empty() {
94 writeln!(writer, " Tags: {}", task.tags.join(", "))?;
95 }
96 writeln!(writer)?;
97 }
98 Ok(())
99}
100
101pub fn print_projects<W: Write>(
102 _db: &ThingsDatabase,
103 projects: &[things3_core::Project],
104 writer: &mut W,
105) -> Result<()> {
106 if projects.is_empty() {
107 writeln!(writer, "No projects found")?;
108 return Ok(());
109 }
110
111 writeln!(writer, "Found {} projects:", projects.len())?;
112 for project in projects {
113 writeln!(writer, " • {} ({:?})", project.title, project.status)?;
114 if let Some(notes) = &project.notes {
115 writeln!(writer, " Notes: {notes}")?;
116 }
117 if let Some(deadline) = &project.deadline {
118 writeln!(writer, " Deadline: {deadline}")?;
119 }
120 if !project.tags.is_empty() {
121 writeln!(writer, " Tags: {}", project.tags.join(", "))?;
122 }
123 writeln!(writer)?;
124 }
125 Ok(())
126}
127
128pub fn print_areas<W: Write>(
129 _db: &ThingsDatabase,
130 areas: &[things3_core::Area],
131 writer: &mut W,
132) -> Result<()> {
133 if areas.is_empty() {
134 writeln!(writer, "No areas found")?;
135 return Ok(());
136 }
137
138 writeln!(writer, "Found {} areas:", areas.len())?;
139 for area in areas {
140 writeln!(writer, " • {}", area.title)?;
141 if let Some(notes) = &area.notes {
142 writeln!(writer, " Notes: {notes}")?;
143 }
144 if !area.tags.is_empty() {
145 writeln!(writer, " Tags: {}", area.tags.join(", "))?;
146 }
147 writeln!(writer)?;
148 }
149 Ok(())
150}
151
152pub fn health_check(db: &ThingsDatabase) -> Result<()> {
153 println!("🔍 Checking Things 3 database connection...");
154
155 let tasks = db.get_inbox(Some(1))?;
157 println!("✅ Database connection successful!");
158 println!(" Found {} tasks in inbox", tasks.len());
159
160 let projects = db.get_projects(None)?;
162 println!(" Found {} projects", projects.len());
163
164 let areas = db.get_areas()?;
166 println!(" Found {} areas", areas.len());
167
168 println!("🎉 All systems operational!");
169 Ok(())
170}
171
172pub fn start_mcp_server(db: ThingsDatabase, config: ThingsConfig) -> Result<()> {
173 println!("🚀 Starting MCP server...");
174
175 let _server = mcp::ThingsMcpServer::new(db, config);
176
177 println!("✅ MCP server would start here");
180 println!(" (This is a placeholder - actual MCP server implementation would go here)");
181
182 Ok(())
183}