1pub mod cli;
2pub mod collect;
3pub mod queue;
4pub mod queue_path;
5
6use clap::{Arg, ArgAction, Args, Command, CommandFactory, Parser, Subcommand};
7use std::path::PathBuf;
8
9#[derive(Parser)]
10#[command(
11 name = "sq",
12 version,
13 about = "Lightweight task-list CLI with structured sources",
14 long_about = "sq is a lightweight task-list CLI with structured sources.\n\nIt manages tasks in a JSONL file. You can use it directly from the shell or instruct agents to manage them for you."
15)]
16pub struct Cli {
17 #[arg(
19 short = 'q',
20 long = "queue",
21 value_name = "PATH",
22 global = true,
23 display_order = 900
24 )]
25 pub queue: Option<PathBuf>,
26
27 #[command(subcommand)]
28 pub command: Commands,
29}
30
31pub fn build_cli() -> Command {
32 let mut cmd = Cli::command()
33 .propagate_version(true)
34 .disable_version_flag(true)
35 .arg(
36 Arg::new("version")
37 .short('v')
38 .long("version")
39 .help("Print version")
40 .action(ArgAction::Version)
41 .global(true),
42 );
43 let root_help = crate::cli::help::root_after_help(cmd.get_styles());
44 cmd = cmd.after_help(root_help);
45
46 cmd = cmd.mut_subcommand("collect", |subcmd| {
47 let help = crate::cli::commands::collect::after_help(subcmd.get_styles());
48 subcmd.after_help(help)
49 });
50
51 cmd = cmd.mut_subcommand("add", |subcmd| {
52 let help = crate::cli::commands::add::after_help(subcmd.get_styles());
53 subcmd.after_help(help)
54 });
55
56 cmd = cmd.mut_subcommand("edit", |subcmd| {
57 let help = crate::cli::commands::edit::after_help(subcmd.get_styles());
58 subcmd.after_help(help)
59 });
60
61 cmd = cmd.mut_subcommand("list", |subcmd| {
62 let help = crate::cli::commands::list::after_help(subcmd.get_styles());
63 subcmd.after_help(help)
64 });
65
66 cmd = cmd.mut_subcommand("rm", |subcmd| {
67 let help = crate::cli::commands::rm::after_help(subcmd.get_styles());
68 subcmd.after_help(help)
69 });
70
71 cmd = cmd.mut_subcommand("close", |subcmd| {
72 let help = crate::cli::commands::status::close_after_help(subcmd.get_styles());
73 subcmd.long_about("").after_help(help)
74 });
75
76 cmd
77}
78
79#[derive(Subcommand)]
80pub enum Commands {
81 Add(AddArgs),
83 Collect(CollectArgs),
85 List(ListArgs),
87 Show(ShowArgs),
89 Edit(EditArgs),
91 Close(StatusArgs),
93 Rm(RmArgs),
95 Prime(PrimeArgs),
97}
98
99#[derive(Args)]
100#[command(about = "Output task workflow context for AI agents")]
101pub struct PrimeArgs {
102 #[arg(long = "prelude", display_order = 1)]
104 pub prelude: bool,
105}
106
107#[derive(Parser)]
108pub struct AddArgs {
109 #[arg(long = "title", value_name = "TITLE", display_order = 1)]
111 pub title: Option<String>,
112
113 #[arg(long = "description", value_name = "TEXT", display_order = 2)]
115 pub description: Option<String>,
116
117 #[arg(long = "priority", value_name = "PRIORITY", display_order = 3)]
119 pub priority: Option<String>,
120
121 #[arg(long = "diff", value_name = "PATH", display_order = 10)]
123 pub diff: Vec<String>,
124
125 #[arg(long = "file", value_name = "PATH", display_order = 11)]
127 pub file: Vec<String>,
128
129 #[arg(long = "text", value_name = "STRING", display_order = 12)]
131 pub text: Vec<String>,
132
133 #[arg(long = "directory", value_name = "PATH", display_order = 13)]
135 pub directory: Vec<String>,
136
137 #[arg(long = "stdin", value_name = "TYPE", display_order = 14)]
139 pub stdin: Option<String>,
140
141 #[arg(long = "metadata", value_name = "JSON", display_order = 15)]
143 pub metadata: Option<String>,
144
145 #[arg(long = "blocked-by", value_name = "IDS", display_order = 16)]
147 pub blocked_by: Option<String>,
148
149 #[arg(long = "json", display_order = 17)]
151 pub json: bool,
152}
153
154#[derive(Args)]
155#[command(about = "Collect tasks from stdin")]
156pub struct CollectArgs {
157 #[arg(long = "title", value_name = "TITLE", display_order = 1)]
159 pub title: Option<String>,
160
161 #[arg(long = "description", value_name = "TEXT", display_order = 2)]
163 pub description: Option<String>,
164
165 #[arg(long = "priority", value_name = "PRIORITY", display_order = 3)]
167 pub priority: Option<String>,
168
169 #[arg(long = "by-file", display_order = 10)]
171 pub by_file: bool,
172
173 #[arg(long = "stdin-format", value_name = "FORMAT", display_order = 11)]
175 pub stdin_format: Option<String>,
176
177 #[arg(long = "title-template", value_name = "TEMPLATE", display_order = 12)]
179 pub title_template: Option<String>,
180
181 #[arg(long = "metadata", value_name = "JSON", display_order = 13)]
183 pub metadata: Option<String>,
184
185 #[arg(long = "blocked-by", value_name = "IDS", display_order = 14)]
187 pub blocked_by: Option<String>,
188
189 #[arg(long = "json", display_order = 15)]
191 pub json: bool,
192}
193
194#[derive(Parser)]
195pub struct ListArgs {
196 #[arg(long = "status", value_name = "STATUS", display_order = 1)]
198 pub status: Vec<String>,
199
200 #[arg(long = "all", display_order = 2)]
202 pub all: bool,
203
204 #[arg(long = "priority", value_name = "PRIORITY", display_order = 3)]
206 pub priority: Vec<String>,
207
208 #[arg(long = "ready", display_order = 4)]
210 pub ready: bool,
211
212 #[arg(long = "json", display_order = 10)]
214 pub json: bool,
215
216 #[arg(long = "filter", value_name = "EXPR", display_order = 11)]
218 pub filter: Option<String>,
219
220 #[arg(long = "sort", value_name = "PATH", display_order = 12)]
222 pub sort: Option<String>,
223
224 #[arg(long = "reverse", display_order = 13)]
226 pub reverse: bool,
227}
228
229#[derive(Parser)]
230pub struct ShowArgs {
231 pub id: Option<String>,
233
234 #[arg(long = "json")]
236 pub json: bool,
237
238 #[arg(long = "full")]
240 pub full: bool,
241}
242
243#[derive(Parser)]
244pub struct EditArgs {
245 pub id: Option<String>,
247
248 #[arg(long = "set-title", value_name = "TITLE", display_order = 1)]
250 pub set_title: Option<String>,
251
252 #[arg(long = "set-description", value_name = "TEXT", display_order = 2)]
254 pub set_description: Option<String>,
255
256 #[arg(long = "set-status", value_name = "STATUS", display_order = 3)]
258 pub set_status: Option<String>,
259
260 #[arg(long = "set-priority", value_name = "PRIORITY", display_order = 4)]
262 pub set_priority: Option<String>,
263
264 #[arg(long = "clear-priority", display_order = 5)]
266 pub clear_priority: bool,
267
268 #[arg(long = "add-diff", value_name = "PATH", display_order = 10)]
270 pub add_diff: Vec<String>,
271
272 #[arg(long = "add-file", value_name = "PATH", display_order = 11)]
274 pub add_file: Vec<String>,
275
276 #[arg(long = "add-text", value_name = "STRING", display_order = 12)]
278 pub add_text: Vec<String>,
279
280 #[arg(long = "add-directory", value_name = "PATH", display_order = 13)]
282 pub add_directory: Vec<String>,
283
284 #[arg(long = "rm-source", value_name = "INDEX", display_order = 14)]
286 pub rm_source: Vec<usize>,
287
288 #[arg(long = "set-metadata", value_name = "JSON", display_order = 15)]
290 pub set_metadata: Option<String>,
291
292 #[arg(long = "merge-metadata", value_name = "JSON", display_order = 16)]
294 pub merge_metadata: Option<String>,
295
296 #[arg(long = "set-blocked-by", value_name = "IDS", display_order = 17)]
298 pub set_blocked_by: Option<String>,
299
300 #[arg(long = "json", display_order = 18)]
302 pub json: bool,
303}
304
305#[derive(Parser)]
306pub struct StatusArgs {
307 pub id: Option<String>,
309
310 #[arg(long = "json")]
312 pub json: bool,
313}
314
315#[derive(Parser)]
316pub struct RmArgs {
317 pub id: Option<String>,
319
320 #[arg(long = "json")]
322 pub json: bool,
323}