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