Skip to main content

mps/
cli.rs

1use clap::{Parser, Subcommand};
2
3#[derive(Parser)]
4#[command(name = "mps", version, about = "Plain-text personal productivity CLI")]
5pub struct Cli {
6    /// Path to config file (default: ~/.mps_config.yaml)
7    #[arg(long, global = true)]
8    pub config_path: Option<String>,
9
10    /// Recreate config even if it exists
11    #[arg(long, global = true, default_value_t = false)]
12    pub force: bool,
13
14    #[command(subcommand)]
15    pub command: Option<Commands>,
16}
17
18#[derive(Subcommand)]
19pub enum Commands {
20    /// Open a date's .mps file in $EDITOR (default: today)
21    Open {
22        /// Date to open (today, yesterday, YYYYMMDD, last friday, …)
23        datesign: Option<String>,
24    },
25
26    /// List elements for a date as an indented tree
27    List {
28        /// Date (default: today)
29        datesign: Option<String>,
30        /// Filter by element type: task, note, log, reminder
31        #[arg(short = 't', long)]
32        r#type: Option<String>,
33        /// Filter by tag name
34        #[arg(short = 'g', long)]
35        tag: Option<String>,
36        /// Filter tasks by status: open, done
37        #[arg(short = 's', long)]
38        status: Option<String>,
39        /// Show elements from SINCE up to DATESIGN
40        #[arg(short = 'S', long)]
41        since: Option<String>,
42        /// Show human-readable ref column (task-1, mps-1.2, …)
43        #[arg(short = 'r', long)]
44        refs: bool,
45        /// List elements across all dates in the archive
46        #[arg(short = 'a', long)]
47        all: bool,
48        /// Filter character entries by person name
49        #[arg(short = 'n', long)]
50        name: Option<String>,
51    },
52
53    /// Append an element to today's file without opening an editor
54    Append {
55        /// Element type: task, note, log, reminder (aliases from config are resolved)
56        kind: String,
57        /// Element body text
58        body: Vec<String>,
59        /// Comma-separated tags (e.g. work,backend)
60        #[arg(long)]
61        tags: Option<String>,
62        /// Task status: open (default) or done
63        #[arg(long)]
64        status: Option<String>,
65        /// Time for reminders (e.g. 5pm, 10:30)
66        #[arg(long)]
67        at: Option<String>,
68        /// Start time for logs (HH:MM)
69        #[arg(long)]
70        start_time: Option<String>,
71        /// End time for logs (HH:MM)
72        #[arg(long)]
73        end_time: Option<String>,
74        /// Person name for character entries
75        #[arg(short = 'n', long)]
76        name: Option<String>,
77    },
78
79    /// Update an element's attributes in-place
80    Update {
81        /// Element ref: human (task-1) or epoch (20260428.1)
82        ref_path: String,
83        /// Set task status: open or done
84        #[arg(long)]
85        status: Option<String>,
86        /// Set log start time (HH:MM)
87        #[arg(long = "start-time")]
88        start_time: Option<String>,
89        /// Set log end time (HH:MM)
90        #[arg(long = "end-time")]
91        end_time: Option<String>,
92        /// Set reminder time
93        #[arg(long)]
94        at: Option<String>,
95        /// Date context for human refs (default: today)
96        #[arg(short = 'd', long)]
97        date: Option<String>,
98    },
99
100    /// Edit an element's body text in $EDITOR
101    Edit {
102        /// Element ref: human (task-1) or epoch (20260428.1)
103        ref_path: String,
104        /// Date context for human refs (default: today)
105        #[arg(short = 'd', long)]
106        date: Option<String>,
107    },
108
109    /// Delete an element from its file
110    Delete {
111        /// Element ref: human (task-1) or epoch (20260428.1)
112        ref_path: String,
113        /// Date context for human refs (default: today)
114        #[arg(short = 'd', long)]
115        date: Option<String>,
116        /// Skip confirmation prompt
117        #[arg(short = 'y', long)]
118        yes: bool,
119    },
120
121    /// Mark a task as done (shorthand for update REFPATH --status done)
122    Done {
123        /// Element ref: human (task-1) or epoch (20260428.1)
124        ref_path: String,
125        /// Date context for human refs (default: today)
126        #[arg(short = 'd', long)]
127        date: Option<String>,
128    },
129
130    /// Full-text search across all .mps files
131    Search {
132        /// Search query
133        query: String,
134        /// Filter by element type
135        #[arg(short = 't', long)]
136        r#type: Option<String>,
137        /// Filter by tag
138        #[arg(short = 'g', long)]
139        tag: Option<String>,
140        /// Search from this date onward
141        #[arg(short = 'S', long)]
142        since: Option<String>,
143        /// Filter character entries by person name
144        #[arg(short = 'n', long)]
145        name: Option<String>,
146    },
147
148    /// Show element counts and log durations
149    Stats {
150        /// Date (default: today)
151        datesign: Option<String>,
152        /// Stats from SINCE up to DATESIGN
153        #[arg(short = 'S', long)]
154        since: Option<String>,
155        /// Stats across all dates in the archive
156        #[arg(short = 'a', long)]
157        all: bool,
158    },
159
160    /// Show tag usage frequency bar chart
161    Tags {
162        /// Date (default: today)
163        datesign: Option<String>,
164        /// Filter by element type
165        #[arg(short = 't', long)]
166        r#type: Option<String>,
167        /// Filter tasks by status
168        #[arg(short = 's', long)]
169        status: Option<String>,
170        /// Tags from SINCE up to DATESIGN
171        #[arg(short = 'S', long)]
172        since: Option<String>,
173        /// Count tags across all dates
174        #[arg(short = 'a', long)]
175        all: bool,
176        /// Filter character entries by person name
177        #[arg(short = 'n', long)]
178        name: Option<String>,
179    },
180
181    /// Export elements to JSON or CSV on stdout
182    Export {
183        /// Date (default: today)
184        datesign: Option<String>,
185        /// Output format: json (default), csv
186        #[arg(short = 'f', long, default_value = "json")]
187        format: String,
188        /// Filter by element type
189        #[arg(short = 't', long)]
190        r#type: Option<String>,
191        /// Export from SINCE up to DATESIGN
192        #[arg(short = 'S', long)]
193        since: Option<String>,
194    },
195
196    /// View or edit MPS configuration
197    Config {
198        /// Subcommand: show (default), edit, init, check
199        subcommand: Option<String>,
200        /// Skip network reachability check (used with 'check' subcommand)
201        #[arg(long)]
202        no_network: bool,
203    },
204
205    /// Run git commands inside the storage directory
206    Git {
207        /// Git subcommand and args (auto = full cycle, autocommit = stage+commit)
208        #[arg(trailing_var_arg = true)]
209        args: Vec<String>,
210    },
211
212    /// Stage, commit, pull, and push (equivalent to git auto)
213    Autogit,
214
215    /// Run any shell command inside the storage directory
216    Cmd {
217        /// Command and arguments
218        #[arg(trailing_var_arg = true)]
219        args: Vec<String>,
220    },
221
222    /// Check for due reminders and open tasks; send desktop notifications
223    Notify {
224        /// Show what would be sent without actually sending
225        #[arg(long)]
226        dry_run: bool,
227        /// Override the time window in minutes (default: from config)
228        #[arg(long)]
229        window: Option<u64>,
230        /// Fire even if already notified within the window
231        #[arg(long)]
232        force: bool,
233    },
234
235    /// Manage the background notification daemon (systemd user timer)
236    Daemon {
237        /// Subcommand: install, remove, status, run
238        subcommand: String,
239    },
240
241    /// Inspect or edit the .mps.meta sidecar config file
242    Meta {
243        /// Subcommand: show (default), clear, edit, sync up, sync down
244        #[arg(trailing_var_arg = true)]
245        args: Vec<String>,
246    },
247
248    /// Interactive LLM chat session with your mps journal as context
249    Chat {
250        /// OpenAI-compatible base URL (default: auto-detect :11434 or :8080)
251        #[arg(long)]
252        url: Option<String>,
253        /// Model name (default: llama3.2)
254        #[arg(long)]
255        model: Option<String>,
256        /// Bearer API key (empty = no auth)
257        #[arg(long)]
258        api_key: Option<String>,
259        /// Days of journal history to load (default: 7)
260        #[arg(long)]
261        context_days: Option<u64>,
262        /// Load journal from this date onward (overrides --context-days)
263        #[arg(long)]
264        since: Option<String>,
265        /// Load entire journal history
266        #[arg(long)]
267        all: bool,
268        /// Stream tokens as they arrive (default: true)
269        #[arg(long)]
270        stream: Option<bool>,
271        /// Named session: resume existing or start new
272        #[arg(long, short = 's')]
273        session_name: Option<String>,
274        /// Force new session even if a named session already exists
275        #[arg(long)]
276        new: bool,
277        /// List all saved sessions and exit
278        #[arg(long)]
279        list_sessions: bool,
280    },
281
282    /// Start the HTTP API server
283    Serve {
284        /// TCP port to listen on (default: 3000)
285        #[arg(short = 'p', long)]
286        port: Option<u16>,
287        /// Host address to bind (default: 127.0.0.1)
288        #[arg(long)]
289        host: Option<String>,
290        /// Bearer token for API auth (empty = no auth)
291        #[arg(short = 't', long)]
292        token: Option<String>,
293    },
294
295    /// Interactive first-run setup wizard — creates ~/.mps_config.yaml
296    Init,
297
298    /// Migrate legacy .ms / non-standard .mps files into the journal
299    Import {
300        /// Preview what would be imported without writing anything (default when no flag given)
301        #[arg(long)]
302        dry_run: bool,
303        /// Execute the import; rename originals to *.imported afterwards
304        #[arg(long)]
305        yes: bool,
306        /// Execute the import; delete originals afterwards
307        #[arg(long, name = "move")]
308        move_: bool,
309    },
310
311    /// Print version
312    Version,
313}