CLI Reference

minuta is the command-line companion to the macOS/iOS app. It reads and writes the same on-disk Automerge files, takes the same cross-process write lock, and speaks the same schema.

Source: Shared/Sources/MinutaCLI/. Build: cd Shared && swift build -c release --product minuta.

Global flags

Every subcommand accepts these:

FlagDescription
--storage <path>Override storage discovery.
--jsonMachine-readable JSON on stdout.
-v, --verboseExtra diagnostics on stderr.
-q, --quietSuppress non-error stderr output.

Storage discovery order (first match wins):

  1. --storage <path> flag
  2. MINUTA_STORAGE environment variable
  3. <realHome>/Library/Application Support/minuta/storage-path.txt written by the app
  4. ~/Documents/Minuta default

See Cross-Process Safety for the discovery mechanics (including the Catalyst container trap).

Run minuta storage path to see which source produced the current path.

Output contract

  • stdout: data only (tables, --json, --csv, primary IDs).
  • stderr: progress, warnings, -v diagnostics.

This keeps pipelines like minuta export csv --today | sort clean even with -v.

Exit codes

CodeMeaning
0success
1generic error (not found, unexpected failure)
2storage unreachable — path resolution failed or target is inaccessible
3validation error (bad flag, end before start, unparseable date)
4reserved — concurrent-write retry limit (unreachable under current locking)
5.minuta-version marker absent — app needs upgrading to Phase-0+

Timer commands

minuta start

minuta start [--tag <name>] [--comment <text>] [--at <iso-datetime>]

Start a new timer. Unknown tag names are created with an auto-generated color (via getOrCreateTag). Prints the new record ID to stdout.

minuta stop

minuta stop [--id <record-id>]

Stops running timers. Default behavior stops every running timer. Prints stopped IDs and durations.

minuta status

Shows currently-running timers with live duration. One-shot; exits after printing.

Record commands

minuta records list

minuta records list [filters] [--json] [--csv]

Filters (shared with export csv and records show):

  • --from <date> / --to <date> (inclusive; local time zone)
  • --tag <name> (repeatable)
  • --untagged
  • --running / --no-running
  • --today / --this-week / --this-month / --last-week / --last-month

JSON schema (stable):

{
  "records": [
    {
      "id": "3B13041F-E156-…",
      "startTime": "2026-04-22T04:00:00Z",
      "endTime": "2026-04-22T05:30:00Z",
      "duration": 5400,
      "running": false,
      "tag": {
        "id": "…",
        "name": "Coding",
        "color": "#AC5843",
        "archived": false
      },
      "comment": "wrote tests",
      "images": []
    }
  ]
}

Times are always UTC ISO 8601 in JSON. Display mode shows local time.

minuta records show <id>

Prints the full detail of a single record. ID can be a full UUID or the 8-char prefix shown by records list.

minuta records add

minuta records add --start <datetime> [--end <datetime>] [--tag <name>] [--comment <text>]

Creates a completed or running record at arbitrary times. --end omitted produces a running timer. Tag is auto-created.

minuta records edit <id>

minuta records edit <id> [--start <dt>] [--end <dt>] [--tag <name>] [--comment <text>]
                         [--clear-tag] [--clear-comment] [--clear-end]

Updates fields in place. --clear-end reopens a completed record as a running timer.

minuta records delete <id>

minuta records delete <id> [--force]

Delete a record and its images. Refuses to delete running timers without --force (stop them first).

Images

minuta records image add    <record-id> <path-to-image>
minuta records image list   <record-id>
minuta records image remove <record-id> <index>
minuta records image export <record-id> <index> <output-path>

Images get UUID-scheme filenames ({recordId}_{uuid}.{ext}) and are stored alongside the record’s month file.

Tag commands

minuta tags list [--archived] [--all]
minuta tags search <query>
minuta tags add <name> [--color <hex>]
minuta tags rename <name> <new-name>
minuta tags recolor <name> <hex>
minuta tags archive <name>
minuta tags unarchive <name>
minuta tags delete <name> [--force]

tags delete refuses to remove a tag that’s still referenced by records unless --force is set. Records keep their tagId field; the tag simply disappears and those records become untagged.

Duplicate tags add (case-insensitive) exits 0, not an error — it prints the existing tag’s ID so idempotent scripts work cleanly.

JSON schema:

{
  "tags": [
    { "id": "…", "name": "Work", "color": "#8B5EA4", "archived": false }
  ]
}

Export

minuta export csv

minuta export csv [filters] [-o <path>]

Writes CSV to stdout, or to -o file. Columns: Date, Start Time, End Time, Duration, Tag, Comment. Shares filters with records list.

minuta export report

minuta export report [--format png|svg] [--from <date> --to <date> | --prev-month]
    [--tag <name>]... [--untagged] [--title <text>] [--with-images]
    [-o <path>] [--open]

Renders a time report. Defaults: PNG for the current month.

PNG rendering uses AppKit’s native NSImage SVG loader — the output is A4 landscape at 2x scale (1684×1190). SVG rendering is pure Swift and can be piped into other converters (rsvg-convert, inkscape) for PDF or higher-resolution output.

Date range:

  • Default — when neither --from/--to nor --prev-month is passed, the current calendar month is used. The report title auto-fills to “April 2026”.
  • --prev-month — previous calendar month. Mutually exclusive with --from/--to.
  • --from <date> --to <date> — explicit inclusive range.

Output:

  • -o <path> — write to the given path. Extension doesn’t matter; the format is determined by --format.
  • No -o, PNG format — writes to a temp file in $TMPDIR and prints the path to stdout.
  • No -o, SVG format — writes SVG directly to stdout (pipe-friendly).

Other flags:

  • --open — after writing, launch the result in Preview via open(1). If -o was omitted, the temp file is used. Works with both PNG and SVG.
  • --with-images — embed up to 8 record images in the SVG as data: URIs. SVG format only (PNG flattens, so the distinction is irrelevant).
  • --title — override the auto-generated heading.

Examples:

minuta export report                         # current month PNG → temp path
minuta export report --open                  # …and open it in Preview
minuta export report --prev-month --open     # previous month
minuta export report --format svg -o r.svg   # write an SVG
minuta export report --format svg | rsvg-convert -f pdf -o r.pdf

Import

minuta import hey <path-to-hey.csv> [--dry-run] [--resume]

Parses HEY email time-tracking CSV (Start,End,Duration,Category,Notes with YYYY-MM-DD HH:mm timestamps). Creates new tags as needed, then creates records.

  • --dry-run: parse and report, write nothing.
  • --resume: skip records already present with the same start/end/tag tuple (idempotent re-runs).

Tags are written first (so records can reference them by ID), then records. Progress goes to stderr every 100 records.

Conflicts

minuta conflicts resolve [--dry-run]

Scans the storage folder for sync-service conflict files:

  • Dropbox: name (conflicted copy 2026-04-22).automerge
  • Google Drive: name (1).automerge
  • iCloud numeric suffix: name 2.automerge

Each conflict is merged into the base document via Automerge’s CRDT merge (commutative). Resolved conflict files are deleted.

  • --dry-run: print the groups that would merge; touch nothing.

Runs automatically on app launch; the CLI surfaces it for headless/cron use.

Doctor

minuta doctor [--json]

Runs diagnostic checks:

  1. storage reachable — does the resolved path exist?
  2. writable — create and delete a sentinel file.
  3. tags file — does tags.automerge load?
  4. record files — enumerate every month file; report corrupt ones.
  5. image integrity — count image files on disk that no record references (orphans).
  6. sync conflicts — count (conflicted copy) files. Points at conflicts resolve.
  7. .minuta-version — present, absent, or unreadable.
  8. writelockLOCK_EX|LOCK_NB probe. If held, another process is writing.
  9. environment — effective TZ and Calendar.current.identifier, so cron/launchd users can diagnose day-boundary surprises.

Exit code 0 if no FAIL checks. WARN entries don’t fail the command but are surfaced on stderr.

The recommended cron pattern is TZ=America/Los_Angeles minuta export csv --today — otherwise launchd’s default TZ=UTC will roll over at the wrong moment.

Storage

minuta storage path

Prints the resolved storage path to stdout and metadata (source, version marker, TZ, calendar) to stderr. --json flattens it into a single object.

Useful for debugging discovery mismatches between the app and CLI. The source field is one of "--storage flag", "MINUTA_STORAGE env", "storage-path.txt", or "default".

Shell completion

swift-argument-parser generates completion scripts from the command tree. The CLI ships generated copies at scripts/cli-completions/minuta.{bash,zsh,fish}, regenerated by scripts/generate-cli-manuals.sh.

Install per shell:

# zsh (add to ~/.zshrc)
source /path/to/minuta/scripts/cli-completions/minuta.zsh

# bash (add to ~/.bashrc)
source /path/to/minuta/scripts/cli-completions/minuta.bash

# fish
cp /path/to/minuta/scripts/cli-completions/minuta.fish ~/.config/fish/completions/minuta.fish

Or regenerate on demand:

minuta --generate-completion-script zsh > /usr/local/share/zsh/site-functions/_minuta

Man pages

Man pages are generated from the same swift-argument-parser definitions via the GenerateManual plugin. Run:

./scripts/generate-cli-manuals.sh

This produces Shared/.build/plugins/GenerateManual/outputs/minuta/*.1 (one per subcommand, plus a top-level minuta.1). Install:

cp Shared/.build/plugins/GenerateManual/outputs/minuta/*.1 /usr/local/share/man/man1/
man minuta
man minuta-export-report

Homebrew installation (Phase 4) will handle man pages and completions automatically.

Automation

minuta is designed for cron/launchd/CI use. Key patterns:

Time zone pitfall. launchd and cron commonly run with TZ=UTC and a stripped locale. minuta export csv --today at 03:00 local under TZ=UTC returns yesterday’s window. Set TZ explicitly:

# ~/.config/launchd or crontab
TZ=America/Los_Angeles
0 18 * * *   /usr/local/bin/minuta export csv --today -o /tmp/today.csv

minuta storage path and minuta doctor both print the effective TimeZone.current.identifier so you can verify.

Daily report email.

TZ=America/Los_Angeles minuta export report --from $(date -v-1d +%Y-%m-%d) --to $(date -v-1d +%Y-%m-%d) -o /tmp/yesterday.svg

Pipeline contract. stdout is data only; -v and progress go to stderr. So this works:

minuta export csv --this-month | duckdb -c "SELECT Tag, SUM(Duration) FROM read_csv_auto('/dev/stdin') GROUP BY Tag"

Health check. In CI or nightly scripts:

minuta doctor --json | jq -e '.checks[] | select(.status == "FAIL") | .name'
# exits nonzero if any FAIL exists

Related