Releases: ericmann/journal
Release list
v3.0.3
Added
- Detached
journal logfailures now leave a readable trail. The
recording daemon and capture pipeline run with stdout/stderr on
/dev/null, so a transcription, land, or recording failure was previously
undiagnosable after the fact beyond a generic notification. Failures now
append a timestampedstage\terrorline to
<log.audio.tmp_dir>/pipeline.log(trivially truncated past 1MB), and the
✕ journal log failednotification includes the underlying error text
(truncated for readability) plus the log path. See
DATA-FLOWS.md and USAGE.md.
v3.0.2
Added
- GUI launchers can now find the journal without shell environment. Two
changes close the class of silent failures where a Hammerspoon hotkey or
Claude Desktop MCP config ranjournalin a launchd environment (no
~/.zshrcexports, minimal PATH) and died invisibly:- Global
default_repo—journal config set-default-repo <path>
records the repo in~/.config/journal/config.yaml(XDG-aware); repo
resolution falls back to it when no--journal-dirflag,$JOURNAL_DIR,
or enclosing.journalresolves. Explicit flag/env still wins and still
fails loudly; the "not inside a journal repo" error now names every
resolution method.journal configshows the current setting. Applies
tojournal mcptoo — Claude Desktop configs no longer need env blocks
for the single-repo case. - Homebrew fallback for external tools (
internal/toolpath) — ffmpeg,
whisper-cli/whisper-cpp/whisper, and terminal-notifier now resolve via
PATH or well-known install locations (/opt/homebrew/bin,
/usr/local/bin, Linuxbrew), so GUI-launched recordings work without
PATH gymnastics. PATH hits always win.
- Global
v3.0.1
Fixed
journal models pullnow actually provisions the modeljournal log
needs. The pull command'stranscriberslot downloads a faster-whisper
(CTranslate2) model for the WhisperX meeting pipeline — an artifact the
whisper.cpp backend behindjournal log <audio.wav>cannot read — yet every
missing-model error and doctor hint said "runjournal models pull", which
succeeded while fixing nothing. A newlog_transcriberpull slot (config
log.transcriber.{model_id,revision,checksum}, default
ggerganov/whisper.cpp) fetches the ggml artifact (ggml-<model>.bin), and
model resolution — shared byjournal log, its record-start preflight, and
journal doctor— now checks the manual-install path
(<model_dir>/<model>.bin, which always wins) and the pulled layout.
models list/verify/MODELS.mdcover the log model too. Set
log.transcriber.model_id: ""to keep manual installs authoritative.journal doctorno longer reports failed optional checks as[ok ].
v3.0.0's audio-toolchain checks (and the older reranker/Quill informational
checks) printed failure text — "whisper.cpp binary not found", "model missing
— runjournal models pull" — under an[ok ]mark and still concluded
"all checks passed". Optional-but-broken checks now render as[warn], the
summary reads "all required checks passed — N warning(s) above", and the
--jsonreport gains a per-checkwarnfield (additive;oksemantics and
the exit code are unchanged — warnings still never faildoctor).
v3.0.0
Added
- Linux recording support for
journal log. The mic-recording toggle
(barejournal log) now works on Linux, not just macOS: it captures via
ffmpeg -f pulseby default, or-f alsaon ALSA-only boxes (set
log.audio.backend: alsa), selected automatically by OS
(log.audio.backend: "", the default) with an explicit override available.
Desktop notifications on Linux go throughnotify-send, degrading silently
(same as macOS'sosascript/terminal-notifier) when it's absent. An
unsupported platform (anything other than macOS/Linux) fails fast on the
starting press, before the recording daemon spawns. macOS behavior
(avfoundation,osascript/terminal-notifier) is unchanged. log.audio.{silence_duration,silence_noise_db}config keys. The
silence_autostopwatchdog's silence-interval length (default30seconds)
andsilencedetectnoise floor (default-35dB) are now configurable
instead of hardcoded, so a noisy room or a different mic can tune when the
safety-net stop fires. Existing behavior is unchanged unless overridden.- Meeting pipeline migrated onto the shared land/index core (Phase 5c).
journal quill-syncandjournal transcribenow write and index through the
sameinternal/log.Land/IndexTranscriptprimitives the voice-note pipeline
(journal log) uses, replacing bespokeos.WriteFileglue in each command.
Frontmatter (source: quill/source: whisperx), filenames, and chunking
behavior are byte-identical to before — this is an internal refactor, not a
behavior change.journal models pullgains a second, independently-optional
gated model slot (diarization.*, same shape astranscriber.*) so the
meeting pipeline's pyannote diarization model can be provisioned as a fast
credential preflight — see docs/TRANSCRIBE.md and
docs/CONFIGURATION.md.diarization.model_idis
empty (disabled) by default;journal models pullskips it entirely until
configured, and attempts every configured model even if one fails, exiting
non-zero only if any of them did.internal/models.Pullgained a
PullFilevariant supporting a configurable remote/local filename (needed
since pyannote's repo has nomodel.bin) —Pullis now a thin wrapper
around it with no behavior change for existing whisper-model call sites. journal models pullgated/HuggingFace-token path (Phase 5b). Extends
Phase 2a's model provisioning with support for gated HuggingFace repos (the
first being pyannote's speaker-diarization models, arriving with the meeting
pipeline migration). Settranscriber.gated: trueandtranscriber.accept_url
in config;pullsendsHF_TOKEN(read from the environment only, never
config) as a Bearer token. A gated model with no/invalid token fails with an
explicit "accept terms at<url>, setHF_TOKEN" message instead of a raw
401.MODELS.mdnow records each installed model's gated status and
acceptance-page link. Ungated pulls are unaffected — no token, no behavior
change.- MCP tools
journal_log_text/journal_log_audio(Phase 5a). The MCP server
(journal mcp) now exposes thejournal log --textandjournal log <audio.wav>
pipelines as tools:journal_log_text(text)runs shape→assemble→land→index,
journal_log_audio(audio_path)runs transcribe→shape→assemble→land→index for a
server-local audio file. Both return{path, title, landed}and honor
local_only/local_only_mcpexactly like the rest of the MCP surface. The
mic-recording stage stays CLI-only and is never exposed over MCP — an MCP server
must not seize the user's microphone. - Start/finish desktop notifications for
journal log(Phase 4, macOS only).
The recording toggle now pairs the existing terminal output with a real desktop
notification: starting a recording pops "● recording", and the async pipeline
landing the note pops "✓ logged:<title>" with the note's relative path. Sent
viaosascript(display notification), falling back toterminal-notifierif
osascript is unavailable; a missing/failing notifier degrades silently (logged,
not surfaced) and never blocks or fails the recording or pipeline. internal/audio.Notifier: an injectable desktop-notification boundary
(DefaultNotifierfor production,FakeNotifierfor tests) mirroring the
existingRecorderpattern, so tests never pop a real OS notification.- Hammerspoon hotkey binding documented (
docs/USAGE.md) — aninit.lua
snippet binding a single hotkey to barejournal log, plus an optional
menubar-dot add-on, so the recording toggle is fully usable from one key press. journal logrecording toggle — mic capture (Phase 3, macOS only). The bare
journal logcommand now toggles mic recording: the first press starts a detached
background recorder (ffmpeg -f avfoundation, 16 kHz/mono/16-bit PCM) and prints
"● recording"; the second press stops it and hands the finalized WAV off to the
existing transcribe→shape→assemble→land→index pipeline asynchronously, so both
presses return immediately.--start/--stop/--cancel/--statusgive explicit
control (--canceldiscards the recording — no note is produced). The toggle state
lives in a lockfile ($XDG_RUNTIME_DIR/journal-log.lockor
<tmp>/journal-log/journal-log.lock) holding{pid, wav_path, started_at}; a dead
PID is detected and cleaned up automatically on the next press.log.audio.max_duration
(default 900s) self-finalizes a long recording;log.audio.silence_autostop(default
off) is an optional safety-net stop after sustained silence. The recorded WAV is
deleted after a successful run unlesslog.audio.keep_wav: true, in which case its
path is recorded in the landed note'saudio:frontmatter field. A WAV passed
directly (journal log <file>.wav) is never auto-deleted, regardless of this
setting — only recorder-produced scratch files are.internal/audiopackage: lockfile primitives (ReadLock/WriteLock/RemoveLock,
injectablePIDAlive) and an injectableRecorderinterface (FfmpegRecorderfor
production,FakeRecorderfor tests) so the mic/ffmpeg dependency never runs in tests.log.audio.{tmp_dir,max_duration,silence_autostop,keep_wav}config keys and the
LogAudioTmpDirAbs()config accessor, additive to the existinglog.audio.{device, sample_rate,channels}keys.journal log <audio.wav>— transcribe stage (Phase 2b). Pass a WAV file as
a positional argument to transcribe it locally viawhisper.cppand run the full
transcribe→shape→assemble→land→index pipeline. No network access: a missing model
fails fast with "runjournal models pull". Silent recordings skip the pipeline;
transcription errors are retryable (WAV is kept). Thetranscriberfrontmatter
records the backend/model used;duration_secis derived from the WAV header.log.transcriber.{backend,model,model_dir}config keys replace the previous
stublog.transcriber.{engine,model}keys.backendselects the engine
(whisper.cpp, the only built-in);model_dirdefaults to the same path as
transcriber.model_dirso a singlejournal models pullserves both paths.LogTranscriberModelDirAbs()helper on*config.Configmirrors
TranscriberModelDirAbs()for the log transcriber path.internal/log.Transcriberinterface with aFakeTranscriberfor tests and a
WhisperCPPdefault implementation. The boundary is injectable so commands never
exec a real binary in tests.journal log --text "..."— voice note capture (Phase 1). The full
shape→assemble→land→index pipeline is now available for typed text. The LLM
shaping step (configurable vialog.shaping.enabled) cleans disfluencies,
generates a title and summary, extracts@todo/@decision/@questionmarkers,
and tags the note. Notes always land tologs/YYYY-MM-DD-HHMM-<slug>.mdeven
when shaping is unavailable (raw fallback). Index failure is non-fatal.SourceVoice = "voice"source constant ininternal/store; voice chunks
are indexed separately from notes and transcripts. No schema migration required —
thesource TEXTcolumn already accepts any value.journal search --source voice(aliaseslog/logs) scopes search results
to voice-note chunks.log:config namespace withshaping,landing,audio, andtranscriber
keys;LogAbsPath()/LogRelPath()helpers mirrorTranscriptsAbsPath().
Fixed
journal loghotkey/mic-toggle no longer fails silently. When the
background transcribe→land pipeline can't run (most commonly whisper.cpp or the
model isn't installed), the detached pipeline process has no visible stdout, so
the only feedback the user gets is a desktop notification — and previously only
the success notification (✓ logged) ever fired.journal lognow pops a
✕ journal log failednotification naming the retained WAV to retry with, and a
low-key "empty recording — nothing to log" notification when a recording
transcribes to silence, so a hotkey press always resolves to a visible outcome.
v2.7.1
Added
journal dismiss— bulk-dismiss open todos in one commit. A newdismiss
subcommand selects all open@todos matching a--projectfilter, an
--before/--older-thanage window, or both, and rewrites each to@done YYYY-MM-DD(same rewrite asjournal done). Requires explicit confirmation
(--yesor an interactiveyprompt). All file edits and re-indexing land in a
single auto-commit whose message records the filter used (e.g.dismissed 5 todo(s) (project=acme, before=4w)). The optional--resolutionflag appends a
Resolution:line to each dismissed block. Singlejournal donebehavior is
unchanged.
Fixed
-
journal todaynow aggregates all of a day's notes, not just the daily file.
The Notes section previously read onlydaily/YYYY/MM/YYYY-MM-DD.md, so a day
with notes captured via--project(or any project note) was reported empty.
It now queries the index for all note chunks withsource=noteand
created_at >= midnight, grouping project note sections with a source label.
Meeting transcripts are excluded from Notes and continue to appear under
Today's meetings. The MCPtodaytool inherits the fix (it routes through
gatherToday). Thejournal://todayMCP resource is unchanged — it remains
the literal daily file; the aggregated view is thetodaytool. -
Embed retry now rides through transient Ollama runner restarts. The retry
window for transient embed-runner crashes (400 withEOF/do embedding requestbody — a known llama.cpp/Metal SIGTRAP flake on Apple Silicon) has
been extended from ~1.4 s to a 45 s wall-clock budget, sized to outlast a
model reload. Backoff is exponential (n²×100 ms), capped per-sleep at 5 s,
and jittered ±25%. Transport-level dial failures (ErrUnreachable) still fail
fast viamaxRetries; non-retryable 4xx (model not found, bad dimensions)
still fail immediately.
v2.7.0
This release rounds out the MCP surface (the agent-facing side of journal),
makes decisions & todos first-class, and adds project tooling — a docs site,
a curated release pipeline, and the agentic-workflow layer.
Added
- MCP
synthtool. The MCP server now exposes synthesis as asynthtool so
MCP clients (e.g. an agent drafting a weekly Slack summary) can run
weekly|daily|meetings|decisions|stalesynthesis jobs without shelling out to
the CLI. By default the tool calls the synthesis provider and returns the
generated text without writing a file (persist: false); setpersist: trueto
also write the draft note to disk, mirroringjournal synth --write. Honors
synth_provider,local_only, andvoice_profileexactly as the CLI does;
returns a clean{"error":"…"}when synthesis is unavailable.
Optional scoping params:kind(defaultweekly),days,project,date. - MCP
asktool — runs retrieve→synthesize and returns
{answer, citations}(grounded text +path:linereferences) so clients get a
direct "what did I decide about X" answer instead of raw chunks. When no chunks
match, returns "No relevant notes found." rather than calling the model
ungrounded. Honorslocal_only/ provider availability with a clear error
(#32). - MCP
statsandtodaytools — the same stable JSON as
journal stats --json/journal today --json, for "how's my note volume" and
"what does my day look like" (#31). - MCP resources —
journal://today,journal://recent, and
journal://projects/{slug}/indexexpose raw Markdown as addressable context via
resources/list/resources/read, so clients can pull journal context without
orchestrating tool calls. URIs are stable across server runs
(#37). - MCP prompts —
weekly reflection,decisions review, andproject status
are exposed as first-class, one-click prompts with journal context pre-assembled
(reusingsynth.AssembleWeekly/AssembleDecisions)
(#44). journal tags— list distinct#tagswith usage counts, and
journal tags rename <old> <new>rewrites a tag across all notes, re-indexes the
affected files, and auto-commits (--dry-runpreviews). Boundary-aware so#foo
isn't matched inside#foobar(#33).- First-class decisions & todos. Dedicated capture commands, crisp
date/statement/citation rendering, proactive surfacing injournal today, and
resolution notes onjournal done. Fully backward compatible — existing
@decision/@todo/@doneblocks parse and surface unchanged
(#47). - Documentation site at https://journal.eamann.com — an mdBook site
(Catppuccin Latte) with warm, second-person prose across 14 chapters (install,
capture, search, synthesis, meetings, configuration, integrations). Additive: the
docs/tree is untouched (#38). - One-click releases. A
prepare-releaseworkflow (Actions → Prepare Release →
version) finalizes the## [Unreleased]CHANGELOG section to the version, tags,
and triggers the Release workflow; and the GitHub Release notes now come from the
curated CHANGELOG section (scripts/changelog-section.sh+ GoReleaser
--release-notes) instead of an auto-generated commit list. See
docs/RELEASING.md. Deterministic, project-specific tooling. - Agentic-workflow layer +
CLAUDE.md. GitHub Actions for agent-ready issues
(auto-label → trigger → PR), a planning-approval gate for high-complexity work,
@claudePR feedback (gated to write-access collaborators), and a retro flow;
plus issue/PR templates, a label set, and a contributor guide grounded in the
real layout. Contributor/CI tooling — no change to the shipped binary
(#25).
Changed
- Better LLM-as-reranker quality. The rerank prompt is now a structured
relevance rubric (0/5/10 anchors) instead of "reply with only the number", and
score parsing is a robust multi-strategy parser (N/10fraction → labelled
Score: N→ last in-range number, skipping negatives). More reliable precision
on the optional reranking path (#36).
Fixed
journal index --watchsurvives transient embed failures. A per-file embed
error during a watch pass is now logged and skipped (continue) instead of
aborting the whole batch; only context cancellation still stops the run.
Debounce coalescing of rapid edits is now covered by tests
(#35).- Indexing retries transient Ollama embed-server failures (sporadic
400/
EOF) instead of failing the run, hardening largejournal indexpasses
(#7).