mps-rs 1.6.1

MPS — plain-text personal productivity CLI (Rust)
Documentation
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
# mps

A plain-text personal productivity CLI. Append journal entries, tasks, reminders, logs, and notes to daily `.mps` files; search, list, and export them; sync via git. Single binary, no runtime dependencies.

Rust rewrite of [mps (Ruby)](https://github.com/mash-97/mps). Fully backward-compatible with all Ruby-generated files and config.

---

## Install

```bash
cargo install mps-rs
```

The installed binary is named `mps`.

### From source

```bash
git clone https://github.com/mash-97/mps-rs
cd mps-rs
cargo install --path .
```

---

## Quick start

```bash
mps                                          # open today's file in $EDITOR
mps list                                     # print today's elements
mps list --since monday                      # all elements since last Monday
mps append task "Fix the auth bug" --tags work,backend
mps append note "The edge case only appears under load"
mps append reminder "Team standup" --at 3pm
mps append log "Deep-work session" --start-time 09:00 --end-time 11:30
mps append character "Helped me think through the design" --name "Dr. Alice" --tags mentor
mps done task-1                              # mark task-1 done
mps edit note-1                              # rewrite note-1's body in $EDITOR
mps delete task-3 --yes                      # delete task-3 without confirmation
mps search "auth" --since "last week"
mps stats --since monday
mps export --format csv --since "20260501" > may.csv
mps autogit                                  # stage + commit + pull + push
mps notify --dry-run                         # preview due reminders + open tasks
mps daemon install                           # enable minutely background checker
mps meta show                                # inspect cross-device config layer
```

---

## Element types

| Type | Description |
|------|-------------|
| `task` | Something to do, with open/done status |
| `note` | Free-form observation or thought |
| `reminder` | Time-anchored alert |
| `log` | Timed work block (start/end time → duration) |
| `character` | Running monologue about a person |

---

## Commands

### `mps [open] [DATE]`

Open the `.mps` file for DATE in `$EDITOR` (falls back to `vim`). Creates the file if absent.

```bash
mps                   # today
mps open yesterday
mps open last friday
```

---

### `mps list [DATE]`

Print elements for DATE as an indented tree.

| Flag | Short | Description |
|------|-------|-------------|
| `--type TYPE` | `-t` | Filter by type: `task`, `note`, `log`, `reminder`, `character` |
| `--tag TAG` | `-g` | Filter by tag name |
| `--status STATUS` | `-s` | Filter tasks by `open` or `done` (hides all non-task elements) |
| `--name NAME` | `-n` | Filter character entries by person name |
| `--since DATE` | `-S` | Show elements from DATE up to the target date |
| `--refs` | `-r` | Show human-readable ref column (`task-1`, `mps-1.2`, …) |
| `--all` | `-a` | List across the entire archive, not just one date |

```bash
mps list
mps list --type task --status open
mps list --since monday --refs
mps list --all --name "Dr. Alice"
```

---

### `mps append TYPE BODY [FLAGS]`

Append one element to today's file without opening an editor. TYPE is resolved through `type_aliases` from config (e.g. `t` → `task`).

| Flag | Description |
|------|-------------|
| `--tags t1,t2` | Comma-separated tags |
| `--status open\|done` | Task status (default: `open`) |
| `--at TIME` | Time for reminders (`5pm`, `10:30`, …) |
| `--start-time HH:MM` | Start time for logs |
| `--end-time HH:MM` | End time for logs |
| `--name NAME` | `-n` Person name for character entries |

```bash
mps append task "Review the PR" --tags work,backend
mps append note "Cache invalidation approach"
mps append reminder "1:1 with manager" --at 2pm
mps append log "Debugging session" --start-time 14:00 --end-time 16:30
mps append character "Explained the system clearly" --name "Mahfuz Vai" --tags mentor,work
mps append task "Write tests" --status done --tags ci
```

---

### `mps edit REFPATH [--date DATE]`

Open an element's body text in `$EDITOR`. The body is written to a temp file; on save the original file is updated atomically. No-op if the content is unchanged.

```bash
mps edit task-1
mps edit note-2 --date yesterday
mps edit 20260601.1779000000.1   # epoch ref works too
```

---

### `mps delete REFPATH [FLAGS]`

Remove an element entirely from its file. Prompts for confirmation unless `--yes` is given.

| Flag | Short | Description |
|------|-------|-------------|
| `--yes` | `-y` | Skip confirmation prompt |
| `--date DATE` | `-d` | Date context for human refs (default: today) |

```bash
mps delete task-1
mps delete note-3 --yes
mps delete task-2 --date yesterday
```

---

### `mps update REFPATH [FLAGS]`

Update one element's attributes in-place. REFPATH is a human ref (`task-1`, `mps-1.2`) or an epoch ref (`20260428.1732500287.1`).

| Flag | Description |
|------|-------------|
| `--status open\|done` | Set task status |
| `--start-time HH:MM` | Set log start time |
| `--end-time HH:MM` | Set log end time |
| `--at TIME` | Set reminder time |
| `--date DATE` | `-d` Date context for human refs (default: today) |

```bash
mps update task-1 --status done
mps update mps-1.2 --end-time 17:00
mps update task-3 --status done --date yesterday
```

---

### `mps done REFPATH [--date DATE]`

Shorthand for `mps update REFPATH --status done`.

```bash
mps done task-1
mps done task-2 --date yesterday
```

---

### `mps search QUERY [FLAGS]`

Full-text search across all `.mps` files. Returns matching elements with date and ref.

| Flag | Short | Description |
|------|-------|-------------|
| `--type TYPE` | `-t` | Filter by element type |
| `--tag TAG` | `-g` | Filter by tag |
| `--name NAME` | `-n` | Filter character entries by person name |
| `--since DATE` | `-S` | Search from DATE onward |

```bash
mps search "auth"
mps search "design" --type character --name "Dr. Alice"
mps search "deploy" --since "last week" --type log
```

---

### `mps stats [DATE] [FLAGS]`

Show element counts and total log durations.

| Flag | Short | Description |
|------|-------|-------------|
| `--since DATE` | `-S` | Stats from DATE up to target date |
| `--all` | `-a` | Stats across the entire archive |

```bash
mps stats
mps stats --since monday
mps stats --all
```

---

### `mps tags [DATE] [FLAGS]`

Show tag usage as a frequency bar chart.

| Flag | Short | Description |
|------|-------|-------------|
| `--type TYPE` | `-t` | Count tags for this element type only |
| `--status STATUS` | `-s` | Restrict to tasks with this status |
| `--name NAME` | `-n` | Restrict to character entries for this person |
| `--since DATE` | `-S` | Tags from DATE up to target date |
| `--all` | `-a` | Count across the entire archive |

```bash
mps tags
mps tags --all --type task --status open
mps tags --since monday --name "Dr. Alice"
```

---

### `mps export [DATE] [FLAGS]`

Export elements to stdout as JSON or CSV.

| Flag | Short | Description |
|------|-------|-------------|
| `--format json\|csv` | `-f` | Output format (default: `json`) |
| `--type TYPE` | `-t` | Filter by element type |
| `--since DATE` | `-S` | Export from DATE up to target date |

CSV columns: `date`, `ref`, `type`, `tags`, `body`, `status`, `at`, `start`, `end`, `name`

```bash
mps export --format json
mps export --format csv --since "20260501" > may.csv
mps export --since monday --type log
```

---

### `mps config [show|edit]`

View or edit configuration.

```bash
mps config         # same as mps config show
mps config show
mps config edit    # opens config file in $EDITOR
```

---

### `mps notify [FLAGS]`

Check for due reminders and open tasks, then send desktop notifications via `notify-send`.

| Flag | Description |
|------|-------------|
| `--dry-run` | Print what would be sent without actually sending |
| `--window MINS` | Override the due-now window in minutes (default: from config) |
| `--force` | Fire even if already notified within the window |

**Reminders** fire when the current time falls within `window_minutes` of the reminder's `--at` time. Each reminder is deduplicated by its ref so it won't re-fire until the cooldown has elapsed.

**Open-task briefing** fires once per day at `task_notify_at` time (e.g. `"9am"`), listing all open tasks from today plus up to `overdue_days` past days.

```bash
mps notify --dry-run
mps notify --force              # re-fire even if already sent
mps notify --window 10          # 10-minute window
```

Notification settings live in `~/.mps_config.yaml` under the `notify:` key or in `.mps.meta` for cross-device sync (see [Meta config](#meta-config)).

---

### `mps daemon SUBCOMMAND`

Manage a systemd user timer that calls `mps daemon run` (= `mps notify`) once per minute.

| Subcommand | Description |
|------------|-------------|
| `install` | Write unit files, enable and start the timer |
| `remove` | Stop and disable the timer, remove unit files |
| `status` | Pass-through to `systemctl --user status mps-notify.timer` |
| `run` | Run one notify tick (invoked by systemd; also safe to call manually) |

```bash
mps daemon install
mps daemon status
mps daemon remove
```

Unit files are written to `~/.config/systemd/user/`. `.mps.local` (notification history) is automatically added to `.gitignore` on install so it stays off git.

---

### `mps meta [SUBCOMMAND]`

Inspect or edit the `.mps.meta` sidecar config file.

| Subcommand | Description |
|------------|-------------|
| `show` (default) | Pretty-print `.mps.meta` and `.mps.local` |
| `edit` | Open `.mps.meta` in `$EDITOR` |
| `clear` | Delete `.mps.local` (notification history + cache) |

```bash
mps meta            # same as mps meta show
mps meta edit
mps meta clear
```

---

### `mps git ARGS` / `mps autogit` / `mps cmd ARGS`

Run git or shell commands inside the storage directory.

```bash
mps git status
mps git auto          # add . + commit + pull + push
mps git autocommit    # add . + commit only
mps autogit           # same as mps git auto
mps cmd ls -la        # any shell command in storage dir
```

---

### `mps version`

Print the version string.

---

## File format

Files are named `YYYYMMDD.<epoch>.mps` (or `YYYYMMDD.mps` for files without an epoch). The format is identical to the Ruby gem — no migration needed.

```
@task[work, release, status: done]{
  Ship the API refactor
}

@note{
  The auth token expiry edge case only appears under concurrent load
}

@reminder[at: 3pm]{
  Team standup
}

@log[work, start: 09:00, end: 11:30]{
  Debugging the auth flow
}

@character[name: Dr. Alice, mentor, trusted]{
  Explained the layered caching approach in detail.
  Would consult again for architecture decisions.
}

@mps[sprint-42]{
  @task[backend]{
    Nested task inside a sprint block
  }
  @note{
    Retrospective note
  }
}
```

Brackets are optional — `@task{ body }` is valid. Tags and named attributes share the bracket: `[tag1, tag2, status: done, at: 5pm]`.

---

## Date formats

Accepted everywhere a DATE is expected:

| Input | Meaning |
|-------|---------|
| `today` | Today |
| `yesterday` | Yesterday |
| `monday``sunday` | Most recent occurrence of that weekday |
| `last friday` | The Friday before the most recent one |
| `3 days ago` | 3 days before today |
| `last week` | 7 days ago |
| `20260421` | Explicit YYYYMMDD |
| `2026-04-21` | Explicit YYYY-MM-DD |

---

## Configuration

Config file: `~/.mps_config.yaml`. Created automatically on first run. The same file written by the Ruby gem is accepted without changes (Ruby symbol-key YAML like `:storage_dir:` is handled transparently).

```yaml
mps_dir: /home/you/.mps
storage_dir: /home/you/.mps/mps
log_file: /home/you/.mps/mps.log
git_remote: origin
git_branch: master
default_command: list    # command run by bare `mps` invocation (open or list)

# Short-hand element type aliases (also accepts legacy key: aliases)
type_aliases:
  t: task
  n: note
  r: reminder
  l: log
  c: character

# Short-hand command aliases
command_aliases:
  a: append
  "+": append
  s: search
  l: list

# Canonical tag list synced across devices via .mps.meta
custom_tags:
  - work
  - personal
  - urgent

# Desktop notification settings
notify:
  enabled: true
  window_minutes: 5         # minutes either side of a reminder time counts as "due"
  task_notify_at: "9am"     # morning briefing time; omit to disable open-task briefing
  notify_open_tasks: true
  open_task_tags: []        # if non-empty, only tasks with these tags are included
  task_cooldown_minutes: 60 # min gap between repeat notifications for the same reminder
  overdue_days: 7           # how many past days to scan for overdue open tasks
```

With the above config:
- `mps a t "Fix the bug" --tags work` → appends a task (`a``append`, `t``task`)
- `mps + n "Interesting observation"` → appends a note
- `mps s "auth"` → searches for "auth"

Override the config path:

```bash
mps --config-path /path/to/other.yaml list
# or
MPS_CONFIG=/path/to/other.yaml mps list
```

---

## Meta config

`.mps.meta` is a JSON sidecar file at `~/.mps/mps/.mps.meta` that is **git-tracked** and synced across all your devices via `mps autogit`. It acts as a second config layer — machine-agnostic settings you want consistent everywhere.

Fields supported (union-merged with `~/.mps_config.yaml` at startup; meta wins for scalars, maps are unioned with YAML taking priority on key conflicts):

```json
{
  "version": 1,
  "config": {
    "type_aliases":    { "t": "task", "c": "character" },
    "command_aliases": { "a": "append", "+": "append" },
    "default_command": "list",
    "custom_tags":     ["work", "personal", "urgent", "health"],
    "notify": {
      "enabled":               true,
      "window_minutes":        5,
      "task_notify_at":        "9am",
      "notify_open_tasks":     true,
      "open_task_tags":        [],
      "task_cooldown_minutes": 60,
      "overdue_days":          7
    }
  }
}
```

Edit it directly with `mps meta edit`. Inspect the current state with `mps meta show`.

`.mps.local` (also in `~/.mps/mps/`) holds per-device transient state (notification history, cache). It is **gitignored** and never committed.

---

## Architecture

```
src/
  main.rs         Entry point — alias pre-processing, clap dispatch
  cli.rs          Cli + Commands (#[derive(Parser)])
  config.rs       Config struct; YAML load/init; Ruby symbol-key normalization
  meta.rs         MetaShared (.mps.meta) + MetaLocal (.mps.local) structs
  time_parse.rs   parse_time() — "5pm", "9:30am", "17:00", "noon"
  constants.rs    Filename regexes, new_file_name()
  date_parse.rs   parse_date() — natural-language + absolute formats
  error.rs        MpsError (thiserror)
  parser.rs       Position-based stack parser; mirrors Ruby Engines::Parser
  ref_resolver.rs Bidirectional epoch ↔ human ref (task-1, mps-1.2)
  store.rs        Store — all filesystem I/O; append, parse, search, rewrite
  elements/       Element enum + per-type Data structs
  commands/       One module per command + shared display helpers
```

Two sidecar files live in `storage_dir` (`~/.mps/mps/`):

| File | Tracked | Purpose |
|------|---------|---------|
| `.mps.meta` | Git-tracked | Second config layer — aliases, notify settings, custom tags |
| `.mps.local` | Gitignored | Per-device state — notification history, cache |

---

## Requirements

- Rust 1.70+
- `git` (for `git` / `autogit` commands)
- `$EDITOR` or `$VISUAL` (for `open` / `config edit` / `meta edit`; falls back to `vim`)
- `notify-send` (for `mps notify` / `mps daemon` — Linux desktop notifications; optional)

---

## Changelog

### v1.6.1 (2026-05-24)

Bug fixes and test quality improvements.

- **Fix**: reminder deduplication now respects `task_cooldown_minutes` (default 60 min) instead of the shorter `window_minutes` (default 5 min) — reminders were re-firing too frequently
- **Fix**: `merge_meta()` notify merge is now field-by-field — a `task_notify_at` in `.mps.meta` no longer silently overwrites `window_minutes` set in `~/.mps_config.yaml`
- **Fix**: `mps edit` now presents the element body to `$EDITOR` without leading indentation (dedented), then re-indents correctly on save
- **Fix**: `mps meta edit` validates the saved JSON immediately after the editor closes and warns if it is broken
- **Fix**: `mps notify` task briefing body is capped at 10 lines with an `… and N more` suffix to avoid overflowing the notification popup
- **Tests**: 559 tests total (up from 478) — new coverage across `dedent`, `extract/replace body`, `find_element_span` with nested elements, atomic save cleanup, MetaShared/MetaLocal JSON corruption fallback, notify cooldown, notify merge field semantics, time parser edge cases, and delete/replace correctness across multiple same-type elements

### v1.6.0 (2026-05-24)

- `mps edit REFPATH` — open an element's body in `$EDITOR`; writes back atomically, no-op if unchanged
- `mps delete REFPATH [--yes]` — remove an element from its file with confirmation prompt

### v1.5.0 (2026-05-06)

- `mps notify [--dry-run] [--window MINS] [--force]` — desktop notifications via `notify-send`
- `mps daemon install|remove|status|run` — systemd user timer for minutely checks
- `mps meta [show|edit|clear]` — inspect and edit the `.mps.meta` cross-device config layer
- `.mps.meta` — git-tracked JSON sidecar: aliases, notify schedule, custom tags synced across devices
- `.mps.local` — gitignored per-device state: notification history, tag cache

---

## License

MIT