1use clap::{Parser, Subcommand};
2
3#[derive(Parser)]
4#[command(name = "td", version, about = "Todo tracker for AI agents")]
5pub struct Cli {
6 /// Output JSON
7 #[arg(short = 'j', long = "json", global = true)]
8 pub json: bool,
9
10 /// Select a project explicitly (overrides cwd binding)
11 #[arg(long, global = true)]
12 pub project: Option<String>,
13
14 #[command(subcommand)]
15 pub command: Command,
16}
17
18#[derive(Subcommand)]
19pub enum Command {
20 /// Initialize a central project and bind the current directory to it
21 Init {
22 /// Project name
23 name: String,
24 },
25
26 /// Bind the current directory to an existing project
27 Use {
28 /// Project name
29 name: String,
30 },
31
32 /// List all known projects in central storage
33 Projects,
34
35 /// Create a new task
36 #[command(visible_alias = "add")]
37 Create {
38 /// Task title
39 title: Option<String>,
40
41 /// Priority (low, medium, high)
42 #[arg(short, long, default_value = "medium")]
43 priority: String,
44
45 /// Effort (low, medium, high)
46 #[arg(short, long, default_value = "medium")]
47 effort: String,
48
49 /// Task type
50 #[arg(short = 't', long = "type", default_value = "task")]
51 task_type: String,
52
53 /// Description
54 #[arg(short = 'd', long = "desc")]
55 desc: Option<String>,
56
57 /// Parent task ID (creates a subtask)
58 #[arg(long)]
59 parent: Option<String>,
60
61 /// Labels (comma-separated)
62 #[arg(short, long)]
63 labels: Option<String>,
64 },
65
66 /// List tasks
67 #[command(visible_alias = "ls")]
68 List {
69 /// Filter by status
70 #[arg(short, long)]
71 status: Option<String>,
72
73 /// Filter by priority (low, medium, high)
74 #[arg(short, long)]
75 priority: Option<String>,
76
77 /// Filter by effort (low, medium, high)
78 #[arg(short, long)]
79 effort: Option<String>,
80
81 /// Filter by label
82 #[arg(short, long)]
83 label: Option<String>,
84 },
85
86 /// Show task details
87 Show {
88 /// Task ID
89 id: String,
90 },
91
92 /// Append a work log entry to a task
93 Log {
94 /// Task ID
95 id: String,
96 /// Log entry body
97 message: String,
98 },
99
100 /// Update a task
101 Update {
102 /// Task ID
103 id: String,
104
105 /// Set status
106 #[arg(short, long)]
107 status: Option<String>,
108
109 /// Set priority (low, medium, high)
110 #[arg(short, long)]
111 priority: Option<String>,
112
113 /// Set effort (low, medium, high)
114 #[arg(short, long)]
115 effort: Option<String>,
116
117 /// Set title
118 #[arg(short = 't', long)]
119 title: Option<String>,
120
121 /// Set description
122 #[arg(short = 'd', long = "desc")]
123 desc: Option<String>,
124 },
125
126 /// Mark task(s) as closed
127 #[command(visible_alias = "close")]
128 Done {
129 /// Task IDs
130 #[arg(required = true)]
131 ids: Vec<String>,
132 },
133
134 /// Delete task(s)
135 Rm {
136 /// Skip warnings about dependents becoming unblocked
137 #[arg(short, long)]
138 force: bool,
139
140 /// Delete the whole subtree (task and descendants)
141 #[arg(short = 'r', long)]
142 recursive: bool,
143
144 /// Task IDs
145 #[arg(required = true)]
146 ids: Vec<String>,
147 },
148
149 /// Reopen task(s)
150 Reopen {
151 /// Task IDs
152 #[arg(required = true)]
153 ids: Vec<String>,
154 },
155
156 /// Manage dependencies / blockers
157 Dep {
158 #[command(subcommand)]
159 action: DepAction,
160 },
161
162 /// Manage labels
163 Label {
164 #[command(subcommand)]
165 action: LabelAction,
166 },
167
168 /// Search tasks by title or description
169 Search {
170 /// Search query
171 query: String,
172 },
173
174 /// Show tasks with no open blockers
175 Ready,
176
177 /// Recommend next task(s) to work on
178 Next {
179 /// Scoring strategy: impact (default) or effort
180 #[arg(short, long, default_value = "impact")]
181 mode: String,
182
183 /// Show signal breakdown and equation
184 #[arg(short, long)]
185 verbose: bool,
186
187 /// Maximum number of results
188 #[arg(short = 'n', long = "limit", default_value = "5")]
189 limit: usize,
190 },
191
192 /// Show task statistics (always JSON)
193 Stats,
194
195 /// Compact accumulated delta files into the base snapshot
196 Tidy,
197
198 /// Export tasks to JSONL (one JSON object per line)
199 Export,
200
201 /// Import tasks from a JSONL file
202 Import {
203 /// Path to JSONL file (- for stdin)
204 file: String,
205 },
206
207 /// Sync project state with a peer via magic wormhole
208 Sync {
209 /// Wormhole code to connect to a peer (omit to generate one)
210 code: Option<String>,
211 },
212
213 /// Install the agent skill file (SKILL.md)
214 Skill {
215 /// Skills directory (writes managing-tasks-with-td/SKILL.md inside)
216 #[arg(long)]
217 dir: Option<String>,
218 },
219}
220
221#[derive(Subcommand)]
222pub enum DepAction {
223 /// Add a dependency (child is blocked by parent)
224 Add {
225 /// Task that is blocked
226 child: String,
227 /// Task that blocks it
228 parent: String,
229 },
230 /// Remove a dependency
231 Rm {
232 /// Task that was blocked
233 child: String,
234 /// Task that was blocking
235 parent: String,
236 },
237 /// Show child tasks
238 Tree {
239 /// Parent task ID
240 id: String,
241 },
242}
243
244#[derive(Subcommand)]
245pub enum LabelAction {
246 /// Add a label to a task
247 Add {
248 /// Task ID
249 id: String,
250 /// Label to add
251 label: String,
252 },
253 /// Remove a label from a task
254 Rm {
255 /// Task ID
256 id: String,
257 /// Label to remove
258 label: String,
259 },
260 /// List labels on a task
261 List {
262 /// Task ID
263 id: String,
264 },
265 /// List all distinct labels
266 ListAll,
267}