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
77 /// Show task details
78 Show {
79 /// Task ID
80 id: String,
81 },
82
83 /// Append a work log entry to a task
84 Log {
85 /// Task ID
86 id: String,
87 /// Log entry body
88 message: String,
89 },
90
91 /// Update a task
92 Update {
93 /// Task ID
94 id: String,
95
96 /// Set status
97 #[arg(short, long)]
98 status: Option<String>,
99
100 /// Set priority (low, medium, high)
101 #[arg(short, long)]
102 priority: Option<String>,
103
104 /// Set effort (low, medium, high)
105 #[arg(short, long)]
106 effort: Option<String>,
107
108 /// Set title
109 #[arg(short = 't', long)]
110 title: Option<String>,
111
112 /// Set description
113 #[arg(short = 'd', long = "desc")]
114 desc: Option<String>,
115 },
116
117 /// Mark task(s) as closed
118 #[command(visible_alias = "close")]
119 Done {
120 /// Task IDs
121 #[arg(required = true)]
122 ids: Vec<String>,
123 },
124
125 /// Delete task(s)
126 Rm {
127 /// Skip warnings about dependents becoming unblocked
128 #[arg(short, long)]
129 force: bool,
130
131 /// Delete the whole subtree (task and descendants)
132 #[arg(short = 'r', long)]
133 recursive: bool,
134
135 /// Task IDs
136 #[arg(required = true)]
137 ids: Vec<String>,
138 },
139
140 /// Reopen task(s)
141 Reopen {
142 /// Task IDs
143 #[arg(required = true)]
144 ids: Vec<String>,
145 },
146
147 /// Manage dependencies / blockers
148 Dep {
149 #[command(subcommand)]
150 action: DepAction,
151 },
152
153 /// Manage labels
154 Label {
155 #[command(subcommand)]
156 action: LabelAction,
157 },
158
159 /// Search tasks by title or description
160 Search {
161 /// Search query
162 query: String,
163 },
164
165 /// Show tasks with no open blockers
166 Ready,
167
168 /// Recommend next task(s) to work on
169 Next {
170 /// Scoring strategy: impact (default) or effort
171 #[arg(short, long, default_value = "impact")]
172 mode: String,
173
174 /// Show signal breakdown and equation
175 #[arg(short, long)]
176 verbose: bool,
177
178 /// Maximum number of results
179 #[arg(short = 'n', long = "limit", default_value = "5")]
180 limit: usize,
181 },
182
183 /// Show task statistics (always JSON)
184 Stats,
185
186 /// Diagnose and repair CRDT document integrity
187 Doctor {
188 /// Apply non-destructive repairs
189 #[arg(long)]
190 fix: bool,
191 },
192
193 /// Compact accumulated delta files into the base snapshot
194 Tidy,
195
196 /// Export tasks to JSONL (one JSON object per line)
197 Export,
198
199 /// Import tasks from a JSONL file
200 Import {
201 /// Path to JSONL file (- for stdin)
202 file: String,
203 },
204
205 /// Sync project state with a peer via magic wormhole
206 Sync {
207 /// Wormhole code to connect to a peer (omit to generate one)
208 code: Option<String>,
209 },
210
211 /// Install the agent skill file (SKILL.md)
212 Skill {
213 /// Skills directory (writes managing-tasks-with-td/SKILL.md inside)
214 #[arg(long)]
215 dir: Option<String>,
216 },
217}
218
219#[derive(Subcommand)]
220pub enum DepAction {
221 /// Add a dependency (child is blocked by parent)
222 Add {
223 /// Task that is blocked
224 child: String,
225 /// Task that blocks it
226 parent: String,
227 },
228 /// Remove a dependency
229 Rm {
230 /// Task that was blocked
231 child: String,
232 /// Task that was blocking
233 parent: String,
234 },
235 /// Show child tasks
236 Tree {
237 /// Parent task ID
238 id: String,
239 },
240}
241
242#[derive(Subcommand)]
243pub enum LabelAction {
244 /// Add a label to a task
245 Add {
246 /// Task ID
247 id: String,
248 /// Label to add
249 label: String,
250 },
251 /// Remove a label from a task
252 Rm {
253 /// Task ID
254 id: String,
255 /// Label to remove
256 label: String,
257 },
258 /// List labels on a task
259 List {
260 /// Task ID
261 id: String,
262 },
263 /// List all distinct labels
264 ListAll,
265}
266
267#[derive(Subcommand)]
268pub enum ProjectAction {
269 /// Initialise a central project and bind the current directory to it
270 Init {
271 /// Project name
272 name: String,
273 },
274 /// Bind the current directory to an existing project
275 Bind {
276 /// Project name
277 name: String,
278 },
279 /// Remove the binding for the current directory
280 Unbind,
281 /// Delete a project from central storage and remove all directory bindings
282 Delete {
283 /// Project name
284 name: String,
285 },
286 /// List all known projects in central storage
287 List,
288}