Skip to content

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 01 Jul 22:53
· 12 commits to main since this release

Added

  • Linux recording support for journal log. The mic-recording toggle
    (bare journal log) now works on Linux, not just macOS: it captures via
    ffmpeg -f pulse by default, or -f alsa on 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 through notify-send, degrading silently
    (same as macOS's osascript/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_autostop watchdog's silence-interval length (default 30 seconds)
    and silencedetect noise floor (default -35 dB) 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-sync and journal transcribe now write and index through the
    same internal/log.Land/IndexTranscript primitives the voice-note pipeline
    (journal log) uses, replacing bespoke os.WriteFile glue 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 pull gains a second, independently-optional
    gated model slot (diarization.*, same shape as transcriber.*) 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_id is
    empty (disabled) by default; journal models pull skips it entirely until
    configured, and attempts every configured model even if one fails, exiting
    non-zero only if any of them did. internal/models.Pull gained a
    PullFile variant supporting a configurable remote/local filename (needed
    since pyannote's repo has no model.bin) — Pull is now a thin wrapper
    around it with no behavior change for existing whisper-model call sites.
  • journal models pull gated/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). Set transcriber.gated: true and transcriber.accept_url
    in config; pull sends HF_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>, set HF_TOKEN" message instead of a raw
    401. MODELS.md now 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 the journal log --text and journal 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_mcp exactly 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
    via osascript (display notification), falling back to terminal-notifier if
    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
    (DefaultNotifier for production, FakeNotifier for tests) mirroring the
    existing Recorder pattern, so tests never pop a real OS notification.
  • Hammerspoon hotkey binding documented (docs/USAGE.md) — an init.lua
    snippet binding a single hotkey to bare journal log, plus an optional
    menubar-dot add-on, so the recording toggle is fully usable from one key press.
  • journal log recording toggle — mic capture (Phase 3, macOS only). The bare
    journal log command 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/--status give explicit
    control (--cancel discards the recording — no note is produced). The toggle state
    lives in a lockfile ($XDG_RUNTIME_DIR/journal-log.lock or
    <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 unless log.audio.keep_wav: true, in which case its
    path is recorded in the landed note's audio: 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/audio package: lockfile primitives (ReadLock/WriteLock/RemoveLock,
    injectable PIDAlive) and an injectable Recorder interface (FfmpegRecorder for
    production, FakeRecorder for 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 existing log.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 via whisper.cpp and run the full
    transcribe→shape→assemble→land→index pipeline. No network access: a missing model
    fails fast with "run journal models pull". Silent recordings skip the pipeline;
    transcription errors are retryable (WAV is kept). The transcriber frontmatter
    records the backend/model used; duration_sec is derived from the WAV header.
  • log.transcriber.{backend,model,model_dir} config keys replace the previous
    stub log.transcriber.{engine,model} keys. backend selects the engine
    (whisper.cpp, the only built-in); model_dir defaults to the same path as
    transcriber.model_dir so a single journal models pull serves both paths.
  • LogTranscriberModelDirAbs() helper on *config.Config mirrors
    TranscriberModelDirAbs() for the log transcriber path.
  • internal/log.Transcriber interface with a FakeTranscriber for tests and a
    WhisperCPP default 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 via log.shaping.enabled) cleans disfluencies,
    generates a title and summary, extracts @todo/@decision/@question markers,
    and tags the note. Notes always land to logs/YYYY-MM-DD-HHMM-<slug>.md even
    when shaping is unavailable (raw fallback). Index failure is non-fatal.
  • SourceVoice = "voice" source constant in internal/store; voice chunks
    are indexed separately from notes and transcripts. No schema migration required —
    the source TEXT column already accepts any value.
  • journal search --source voice (aliases log/logs) scopes search results
    to voice-note chunks.
  • log: config namespace with shaping, landing, audio, and transcriber
    keys; LogAbsPath()/LogRelPath() helpers mirror TranscriptsAbsPath().

Fixed

  • journal log hotkey/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 log now pops a
    ✕ journal log failed notification 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.