mps-rs 1.6.1

MPS — plain-text personal productivity CLI (Rust)
Documentation

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). Fully backward-compatible with all Ruby-generated files and config.


Install

cargo install mps-rs

The installed binary is named mps.

From source

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

Quick start

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.

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
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. ttask).

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
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.

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)
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)
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.

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
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
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
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

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.

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.

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).


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)
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)
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.

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
mondaysunday 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).

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 (aappend, ttask)
  • mps + n "Interesting observation" → appends a note
  • mps s "auth" → searches for "auth"

Override the config path:

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):

{
  "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