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:
| Flag | Description |
|---|---|
--storage <path> | Override storage discovery. |
--json | Machine-readable JSON on stdout. |
-v, --verbose | Extra diagnostics on stderr. |
-q, --quiet | Suppress non-error stderr output. |
Storage discovery order (first match wins):
--storage <path>flagMINUTA_STORAGEenvironment variable<realHome>/Library/Application Support/minuta/storage-path.txtwritten by the app~/Documents/Minutadefault
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,
-vdiagnostics.
This keeps pipelines like minuta export csv --today | sort clean even with -v.
Exit codes
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | generic error (not found, unexpected failure) |
| 2 | storage unreachable — path resolution failed or target is inaccessible |
| 3 | validation error (bad flag, end before start, unparseable date) |
| 4 | reserved — 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/--tonor--prev-monthis 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$TMPDIRand 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 viaopen(1). If-owas omitted, the temp file is used. Works with both PNG and SVG.--with-images— embed up to 8 record images in the SVG asdata: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:
- storage reachable — does the resolved path exist?
- writable — create and delete a sentinel file.
- tags file — does
tags.automergeload? - record files — enumerate every month file; report corrupt ones.
- image integrity — count image files on disk that no record references (orphans).
- sync conflicts — count
(conflicted copy)files. Points atconflicts resolve. - .minuta-version — present, absent, or unreadable.
- writelock —
LOCK_EX|LOCK_NBprobe. If held, another process is writing. - environment — effective
TZandCalendar.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
- Cross-Process Safety — how the app and CLI share one storage folder.
- Automerge Storage Architecture — the on-disk format both tools read and write.