Skip to content

CLI reference

loof [--base DIR] [--vault-path DIR] [--json] <command>
OptionEnvironment variableBehavior
--base DIRLOOFAH_BASEUse DIR as the vault directory.
--vault-path DIRLOOFAH_VAULT_PATHUse an explicit vault directory.
--jsonNoneEmit a versioned JSON response or machine-readable JSON error.

--vault-path takes precedence over --base. Without either option, the CLI uses Loofah’s platform application-data directory, following the vault_path redirect in its global.json when the vault has been relocated.

Vault commands automatically build a missing or incompatible local Tantivy cache before running; desktop shares the same cache. First use may take longer, with progress on stderr. loof --json init explicitly builds, repairs, or refreshes the cache for an existing vault; it does not create a vault. Help, version, and doctor do not initialize the cache.

With --json, initialization streams NDJSON progress on stderr; stdout contains only the requested command’s response. Explicit init returns one final JSON response. Search and lists reconcile changes before querying; writes finish reconciliation before reporting success. A write_persisted error means the write was saved: do not repeat it; resolve the reported issue and run init. A cache_busy error is retryable.

meetings remains a compatibility alias for sessions. Plain-text invocations emit a deprecation warning when the alias is used; --json output remains machine-readable.

CommandOptionsResult
sessions list--query TEXT, --limit 1..200, --offset NUMBER, --tag NAME, --untaggedNewest sessions first; with --query, desktop full-text matching and relevance order. Defaults to 20 results. --tag filters to meetings carrying that tag (case-insensitive); repeatable, and all given tags must match. --untagged keeps only meetings without tags and cannot be combined with --tag. Each meeting’s JSON includes its normalized tags.
sessions search QUERY--limit 1..50, --offset NUMBEROne relevance-ranked result per session, using desktop word, phrase, and trailing-prefix matching. JSON includes session_id, title, created_at (epoch milliseconds), score, and plain-text title/content snippets. Source and speaker filters are no longer supported; use sessions get or transcript for detail.
sessions get IDNoneMetadata, canonical note, summaries, and action items.
sessions rename ID TITLENoneChanges only the session title, stored verbatim. Keeps the id, directory, metadata, and content. JSON returns sessions.rename with id and title; missing sessions return not_found (exit 2).
sessions new--title TEXT (required), --note FILE, --created-at RFC3339, --started-at RFC3339, --ended-at RFC3339, --tag NAME, --author NAME, --skill NAMECreates a meeting note in the vault and prints its id. --note seeds the note body from FILE, or from stdin when FILE is -. --created-at backdates the meeting on the timeline, which is useful when importing historical notes; --started-at and --ended-at set the meeting’s start and end. All three take an RFC 3339 timestamp such as 2024-05-01T09:00:00Z (offsets are normalized to UTC); invalid values are rejected before anything is written. --tag is repeatable and both tags the meeting and registers new tags in the vault’s tag list. --author records who wrote the note in the meeting’s metadata. When writing on behalf of someone other than the vault owner (an agent, for example claude-code), always set it because the app marks such meetings as not written by the owner. Left unset, the note counts as the vault owner’s own. --skill additionally records the skill used to produce the note (for example meeting-summarizer); it requires --author and is omitted when no skill was involved.
sessions note ID--kind note|summary|all, --set FILE, --append FILEThe selected note documents. Defaults to note. With --set or --append (mutually exclusive, and incompatible with --kind), replaces or extends the note body from FILE or stdin (-); fails when the meeting does not exist.
sessions transcript IDNoneThe full transcript as [HH:MM:SS] Speaker: ... lines, one per speaker turn.
sessions tag add ID TAG...NoneAdds one or more tags to the meeting and registers new ones in the vault’s tag registry. Tags are normalized (trimmed, # stripped, lowercased) and stored deduplicated and sorted; adding a tag the meeting already has is a no-op. Prints the meeting’s resulting tag list.
sessions tag remove ID TAG...NoneRemoves one or more tags from the meeting. Removing an absent tag is a no-op, and removed tags stay in the vault’s tag registry. Prints the meeting’s resulting tag list.
sessions delete IDNoneSoft-delete one exact session into recoverable vault trash. No prompt or confirmation flag; the invocation authorizes the move.
sessions path IDNonePrints the absolute path of the meeting’s session directory.
sessions attach ID FILE--name NAMEStores FILE as a note attachment of the meeting and prints the resulting attachment id. --name overrides the stored filename (defaults to the file’s name). Notes reference attachments as ![alt](attachments/<id>) with the id URL-encoded. The printed attachment id is authoritative and may differ from the input name when an existing attachment of the same name forces a deduplicated one.
sessions export ID--format markdown|md|json|pdf|txt|org, --include note,summary,transcript, --output FILE, --forceExport selected content or a legacy complete meeting. Text defaults to stdout; PDF requires a file. See export behavior.
Terminal window
loof --vault-path /absolute/vault --json sessions delete SESSION_ID

The exact ID is the confirmation in both human and JSON modes; this command never reads stdin or prompts. Resolve and verify the ID first. It validates sessions/<ID>/_meta.json directly without scanning the vault or matching titles. Directories without session metadata are refused. MCP remains read-only.

The JSON envelope uses command: "sessions.delete" (also through the meetings alias). data contains id, status: "deleted", mode: "soft", absolute original path, absolute trash_path, and deleted_at (UTC RFC 3339 timestamp observed after the move).

The whole directory moves atomically to .trash/<UTC-date>/sessions/<ID> with a numeric suffix on collisions. Notes, transcripts, recordings, summaries, tasks, attachments, unknown user files, and hidden files are preserved; existing trash is never overwritten or purged. sessions get ID returns not_found afterwards.

There is no CLI restore command. For manual recovery, quit Loofah and pause vault sync, locate the exact trash_path from the response in Finder, and move the complete directory back to path (sessions/<ID>). Restore the original ID as the directory name if the trash name has a collision suffix. If that destination exists, stop: never merge or replace it. Reopen Loofah and verify with loof --json sessions get ID. Agents should give these recovery steps to the user; do not move vault files on their behalf.

A missing or already-deleted ID returns not_found (exit 2), without scanning trash or moving anything. Malformed IDs, mismatched/corrupt metadata, symlinked session paths, and failed moves return operation_failed (exit 1). A failed rename keeps the original in place; there is no cross-filesystem copy/delete fallback. If the process is interrupted or its response is lost, the complete directory is at its original location or in dated trash; check sessions get ID before retrying, and use Finder for user-requested recovery. No persistent deletion receipt is written.

Terminal window
loof tags list

Lists every tag registered in the vault’s tags.json, one name per line (--json returns each tag’s id and name). The registry is append-only: tags stay listed after the last meeting carrying them loses the tag.

Terminal window
loof import recording.m4a --title "Weekly sync"
loof import recording.m4a --transcribe
loof import recording.m4a --into MEETING_ID

Creates a meeting from an audio file and prints its id. The audio is converted to the vault’s 16 kHz MP3 format, exactly as the desktop app’s import does, so a running app picks the meeting up automatically. Accepts wav, mp3, ogg, mp4, m4a, flac, webm, and aac files; --title defaults to the audio file’s name.

--created-at, --started-at, and --ended-at take RFC 3339 timestamps (normalized to UTC, rejected before anything is written when invalid) and set the new meeting’s metadata. --created-at backdates it on the timeline, which is what you want when importing a historical recording. --author NAME records who created the meeting; agents importing on a user’s behalf should always set it, and --skill NAME additionally records the skill used, if any (see sessions new). Because they describe a new meeting, all of these are rejected together with --into.

With --into MEETING_ID, the audio is imported into that existing meeting instead of creating a new one, such as a note created earlier with sessions new. The meeting keeps its title (--title is rejected together with --into), and the command fails without touching anything when the meeting does not exist or already has a recording; the CLI never replaces existing audio.

With --transcribe, the imported audio is transcribed immediately (see the transcribe command below). When transcription fails after a successful import, the command still reports the meeting id and exits non-zero. Plain output prints the id, while --json adds a transcript_error object to data.

Terminal window
loof transcribe MEETING_ID

Transcribes the meeting’s audio recording on-device with the speech-to-text model configured in the desktop app and replaces the meeting’s transcript, exactly as the app’s own batch transcription does. Prints the new transcript id (--json returns the meeting id plus a transcript object). Progress is written to stderr. The recording is kept after transcription.

The command fails with a clear message and a non-zero exit code when the meeting or its audio is missing, when no speech-to-text model is configured, when the configured provider or model is not supported by the CLI (only on-device soniqo-* models are), or when the model has not been downloaded yet. The CLI never downloads models, so open the desktop app once to download it. On-device transcription requires macOS on Apple Silicon.

Successful --json responses contain:

FieldMeaning
schema_versionCLI JSON contract version. Currently 2.
commandStable command identifier such as meetings.list.
dataCommand result.
paginationPage metadata when the command is paginated.

Pagination includes offset, limit, returned, optional total, and optional next_offset.

Terminal window
loof --json doctor

Checks the resolved vault path, session access, and local cache readiness without changing meeting data. It reports noncanonical session directories and directs you to open the updated desktop app for migration. CLI and MCP reads only use sessions/<id>/; they never migrate a vault. It exits with status 0 when ready is true and status 1 when ready is false. Inside a vault it also restores the root AGENTS.md agent guide when the file is missing or stale.

Terminal window
loof mcp

Starts the read-only MCP server over stdio. Do not write other output to the server’s stdout.

Terminal window
loof --help
loof sessions --help
loof sessions list --help
loof --version

Export runs headlessly and reads sessions without changing the vault. Write exports outside the source vault.

Terminal window
loof sessions export ID --format txt --include transcript --output transcript.txt
loof sessions export ID --format pdf --include note,summary,transcript --output session.pdf
loof sessions export ID --format org --include summary
loof --json sessions export ID --format md --include note,summary --output session.md

PDF, TXT, and Org default to note,summary. --include replaces the selection; it accepts comma-separated values or repeated flags. Sections appear in Note, Summary, Transcript order, regardless of flag order. Missing or blank sections are skipped. summary includes the session’s single summary.

Markdown accepts markdown and md. Text formats use stdout when --output is omitted. PDF requires --output FILE; --output - is rejected for PDF. Text --output - names a literal file. Existing files require --force, which requires --output; refusal returns output_exists (exit 4). File exports are written atomically. Source vault destinations are rejected, even with --force.

Desktop and CLI share text conversion and PDF rendering. Transcript exports preserve speaker turns, names, unknown-speaker labels, and the order across recording parts. Dates use the local timezone; CLI section labels are English. PDFs embed referenced, readable images from the session’s managed attachments. Text exports retain attachment references where supported; they do not copy files. Empty sessions still export title and metadata.

Existing Markdown and JSON calls without --include keep their complete-session artifact: the note, summary, transcripts, and existing metadata and layout. Their --json envelope behavior also stays unchanged, including writing that envelope to --output when both flags are used.

For PDF/TXT/Org or any export with --include, --json reports separately from the artifact. With --output, the file contains only the requested format and stdout contains a versioned meetings.export response with data.format, data.output, and data.bytes. Without a file, text appears in data.content alongside data.format. --format json --include ... retains the existing session JSON schema but filters note, summary, and transcript content; session metadata and action items remain.

Usage parsing errors return exit 2. Missing sessions return exit 2; existing destinations return exit 4. Rendering, invalid PDF destinations, unreadable source content, and other I/O failures return exit 1. With --json, errors go to stderr as the existing structured error response. Failed rendering leaves an existing destination intact.