cli.rs

  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}