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
 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    /// Launch a read-only web UI
212    #[command(name = "webui")]
213    WebUi {
214        /// Listen address
215        #[arg(long, default_value = "127.0.0.1")]
216        host: String,
217
218        /// Listen port
219        #[arg(long, default_value_t = 8080)]
220        port: u16,
221    },
222
223    /// Install the agent skill file (SKILL.md)
224    Skill {
225        /// Skills directory (writes managing-tasks-with-td/SKILL.md inside)
226        #[arg(long)]
227        dir: Option<String>,
228    },
229}
230
231#[derive(Subcommand)]
232pub enum DepAction {
233    /// Add a dependency (child is blocked by parent)
234    Add {
235        /// Task that is blocked
236        child: String,
237        /// Task that blocks it
238        parent: String,
239    },
240    /// Remove a dependency
241    Rm {
242        /// Task that was blocked
243        child: String,
244        /// Task that was blocking
245        parent: String,
246    },
247    /// Show child tasks
248    Tree {
249        /// Parent task ID
250        id: String,
251    },
252}
253
254#[derive(Subcommand)]
255pub enum LabelAction {
256    /// Add a label to a task
257    Add {
258        /// Task ID
259        id: String,
260        /// Label to add
261        label: String,
262    },
263    /// Remove a label from a task
264    Rm {
265        /// Task ID
266        id: String,
267        /// Label to remove
268        label: String,
269    },
270    /// List labels on a task
271    List {
272        /// Task ID
273        id: String,
274    },
275    /// List all distinct labels
276    ListAll,
277}
278
279#[derive(Subcommand)]
280pub enum ProjectAction {
281    /// Initialise a central project and bind the current directory to it
282    Init {
283        /// Project name
284        name: String,
285    },
286    /// Bind the current directory to an existing project
287    Bind {
288        /// Project name
289        name: String,
290    },
291    /// Remove the binding for the current directory
292    Unbind,
293    /// Delete a project from central storage and remove all directory bindings
294    Delete {
295        /// Project name
296        name: String,
297    },
298    /// List all known projects in central storage
299    List,
300}