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 /// Manage projects
21 Project {
22 #[command(subcommand)]
23 action: ProjectAction,
24 },
25
26 /// Create a new task
27 #[command(visible_alias = "add")]
28 Create {
29 /// Task title
30 title: Option<String>,
31
32 /// Priority (low, medium, high)
33 #[arg(short, long, default_value = "medium")]
34 priority: String,
35
36 /// Effort (low, medium, high)
37 #[arg(short, long, default_value = "medium")]
38 effort: String,
39
40 /// Task type
41 #[arg(short = 't', long = "type", default_value = "task")]
42 task_type: String,
43
44 /// Description
45 #[arg(short = 'd', long = "desc")]
46 desc: Option<String>,
47
48 /// Parent task ID (creates a subtask)
49 #[arg(long)]
50 parent: Option<String>,
51
52 /// Labels (comma-separated)
53 #[arg(short, long)]
54 labels: Option<String>,
55 },
56
57 /// List tasks
58 #[command(visible_alias = "ls")]
59 List {
60 /// Filter by status
61 #[arg(short, long)]
62 status: Option<String>,
63
64 /// Filter by priority (low, medium, high)
65 #[arg(short, long)]
66 priority: Option<String>,
67
68 /// Filter by effort (low, medium, high)
69 #[arg(short, long)]
70 effort: Option<String>,
71
72 /// Filter by label
73 #[arg(short, long)]
74 label: Option<String>,
75
76 /// Filter by type (e.g. task, bug, feature)
77 #[arg(long = "type")]
78 task_type: Option<String>,
79
80 /// Show all tasks including closed
81 #[arg(short, long)]
82 all: bool,
83 },
84
85 /// Show task details
86 Show {
87 /// Task ID
88 id: String,
89 },
90
91 /// Append a work log entry to a task
92 Log {
93 /// Task ID
94 id: String,
95 /// Log entry body
96 message: String,
97 },
98
99 /// Update a task
100 Update {
101 /// Task ID
102 id: String,
103
104 /// Set status
105 #[arg(short, long)]
106 status: Option<String>,
107
108 /// Set priority (low, medium, high)
109 #[arg(short, long)]
110 priority: Option<String>,
111
112 /// Set effort (low, medium, high)
113 #[arg(short, long)]
114 effort: Option<String>,
115
116 /// Set title
117 #[arg(short = 't', long)]
118 title: Option<String>,
119
120 /// Set description
121 #[arg(short = 'd', long = "desc")]
122 desc: Option<String>,
123
124 /// Set task type (e.g. task, bug, feature)
125 #[arg(long = "type")]
126 task_type: Option<String>,
127
128 /// Set parent task ID (pass empty string "" to clear)
129 #[arg(long)]
130 parent: Option<String>,
131 },
132
133 /// Mark task(s) as closed
134 #[command(visible_alias = "close")]
135 Done {
136 /// Task IDs
137 #[arg(required = true)]
138 ids: Vec<String>,
139 },
140
141 /// Delete task(s)
142 Rm {
143 /// Skip warnings about dependents becoming unblocked
144 #[arg(short, long)]
145 force: bool,
146
147 /// Delete the whole subtree (task and descendants)
148 #[arg(short = 'r', long)]
149 recursive: bool,
150
151 /// Task IDs
152 #[arg(required = true)]
153 ids: Vec<String>,
154 },
155
156 /// Reopen task(s)
157 Reopen {
158 /// Task IDs
159 #[arg(required = true)]
160 ids: Vec<String>,
161 },
162
163 /// Manage dependencies / blockers
164 Dep {
165 #[command(subcommand)]
166 action: DepAction,
167 },
168
169 /// Manage labels
170 Label {
171 #[command(subcommand)]
172 action: LabelAction,
173 },
174
175 /// Search tasks by title or description
176 Search {
177 /// Search query
178 query: String,
179 },
180
181 /// Show tasks with no open blockers
182 Ready,
183
184 /// Recommend next task(s) to work on
185 Next {
186 /// Scoring strategy: impact (default) or effort
187 #[arg(short, long, default_value = "impact")]
188 mode: String,
189
190 /// Show signal breakdown and equation
191 #[arg(short, long)]
192 verbose: bool,
193
194 /// Maximum number of results
195 #[arg(short = 'n', long = "limit", default_value = "5")]
196 limit: usize,
197 },
198
199 /// Show task statistics (always JSON)
200 Stats,
201
202 /// Diagnose and repair CRDT document integrity
203 Doctor {
204 /// Apply non-destructive repairs
205 #[arg(long)]
206 fix: bool,
207 },
208
209 /// Compact accumulated delta files into the base snapshot
210 Tidy,
211
212 /// Export tasks to JSONL (one JSON object per line)
213 Export,
214
215 /// Import tasks from a JSONL file
216 Import {
217 /// Path to JSONL file (- for stdin)
218 file: String,
219 },
220
221 /// Sync project state with a peer via magic wormhole
222 Sync {
223 /// Wormhole code to connect to a peer (omit to generate one)
224 code: Option<String>,
225 },
226
227 /// Launch a read-only web UI
228 #[command(name = "webui")]
229 WebUi {
230 /// Listen address
231 #[arg(long, default_value = "127.0.0.1")]
232 host: String,
233
234 /// Listen port
235 #[arg(long, default_value_t = 8080)]
236 port: u16,
237 },
238
239 /// Install the agent skill file (SKILL.md)
240 Skill {
241 /// Skills directory (writes managing-tasks-with-td/SKILL.md inside)
242 #[arg(long)]
243 dir: Option<String>,
244 },
245}
246
247#[derive(Subcommand)]
248pub enum DepAction {
249 /// Add a dependency (child is blocked by parent)
250 Add {
251 /// Task that is blocked
252 child: String,
253 /// Task that blocks it
254 parent: String,
255 },
256 /// Remove a dependency
257 Rm {
258 /// Task that was blocked
259 child: String,
260 /// Task that was blocking
261 parent: String,
262 },
263 /// Show child tasks
264 Tree {
265 /// Parent task ID
266 id: String,
267 },
268}
269
270#[derive(Subcommand)]
271pub enum LabelAction {
272 /// Add a label to a task
273 Add {
274 /// Task ID
275 id: String,
276 /// Label to add
277 label: String,
278 },
279 /// Remove a label from a task
280 Rm {
281 /// Task ID
282 id: String,
283 /// Label to remove
284 label: String,
285 },
286 /// List labels on a task
287 List {
288 /// Task ID
289 id: String,
290 },
291 /// List all distinct labels
292 ListAll,
293}
294
295#[derive(Subcommand)]
296pub enum ProjectAction {
297 /// Initialise a central project and bind the current directory to it
298 Init {
299 /// Project name
300 name: String,
301 },
302 /// Bind the current directory to an existing project
303 Bind {
304 /// Project name
305 name: String,
306 },
307 /// Remove the binding for the current directory
308 Unbind,
309 /// Delete a project from central storage and remove all directory bindings
310 Delete {
311 /// Project name
312 name: String,
313 },
314 /// List all known projects in central storage
315 List,
316}