scrybe 2.1.0

Local-first meeting recording, transcription, and notes for macOS.
scrybe-2.1.0 is not a library.
Visit the last successful build: scrybe-0.1.0

scrybe

Crates.io License: Apache-2.0

Local-first meeting transcription and notes. No bot, no account, no vendor cloud by default.

scrybe is an open-source meeting transcription tool built around one constraint: the meeting artifacts belong on the user's machine as ordinary files. It captures audio locally, transcribes it with either local Whisper or a user-configured OpenAI-compatible provider, generates Markdown notes, and writes everything under ~/scrybe/.

Current release: v2.1.0.

What Works Today

The supported user path today is macOS:

  • scrybe init writes the default local macOS profile on macOS.
  • scrybe init --profile default writes the hermetic synthetic profile used by CI and cross-platform smoke tests.
  • scrybe record creates a session folder with source-separated audio.opus, centered playback.opus, transcript.md, notes.md, and meta.toml.
  • --source synthetic runs the hermetic smoke path used by CI.
  • --source mic records Core Audio's default microphone resolved once at session start; --input-device <uid> or [record].input_device pins an exact macOS Core Audio device UID.
  • scrybe devices lists macOS input-device UIDs and identifies the current default.
  • --source mic+system records microphone plus macOS system audio through ScreenCaptureKit when built with mic-capture,system-capture-mac on macOS 13+. It requires the broader Screen & System Audio Recording permission.
  • --whisper-model <PATH> enables local whisper.cpp transcription when built with whisper-local.
  • --llm openai-compat enables real notes through Ollama, vLLM, OpenAI, Groq, Together, or any compatible /chat/completions endpoint when built with llm-openai-compat.
  • scrybe record TITLE --shell enables the native macOS recording shell: a five-bar 4 Hz waveform cycling between the current macOS appearance foreground and recording red, an optional Scrybe image mark, a compact floating elapsed-time pill, and one shared Stop & save path for the tray, pill, and global hotkey.
  • scrybe list, scrybe show <id>, scrybe doctor, scrybe repair <session>, scrybe notes <session>, and scrybe bench are available in the CLI. A session is addressed by its folder name or an unambiguous session-ID prefix, never by a path: every session resolves beneath the configured storage root, and --root is the only way to point a command at a different one.
  • scrybe bench stt --corpus <MANIFEST> --whisper-model <FILE> --sherpa-model <DIR> compares both local providers on a checksum-validated English paired corpus when built with whisper-local,stt-sherpa and an explicitly provisioned native runtime. Manual acquisition and measurement scope. Whisper remains the default; the historical multilingual corpus is Whisper-only.

Linux, Windows, and Android crates are present in the workspace as adapter surfaces and scaffolds. They are not the polished end-user install path yet. The project keeps those adapters in-tree so the trait contracts, config, tests, and packaging work stay cross-platform from the start.

Install

Install the native macOS application from the DMG attached to the latest GitHub release. Choose the Apple-silicon (aarch64) or Intel (x86_64) image, verify it against the signed checksum manifest, open the image, and drag Scrybe.app to /Applications.

The application uses Scrybe's stable self-signed community certificate. It is not signed with Apple Developer ID and is not notarized, so Gatekeeper rejects the first launch. Verify the release and certificate fingerprint, then use System Settings → Privacy & Security → Open Anyway. Do not disable Gatekeeper globally or remove quarantine metadata. Exact verification and installation steps are in INSTALL.md.

Install the command-line application from crates.io:

cargo install scrybe
scrybe doctor

This builds Scrybe locally with microphone capture, ScreenCaptureKit system audio, Whisper, Opus, and OpenAI-compatible notes enabled. It requires Rust 1.95 and Xcode Command Line Tools. The prebuilt CLI installer remains available:

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/Mathews-Tom/scrybe/releases/latest/download/scrybe-installer.sh | sh
scrybe doctor

The native app, CLI archive verification, source builds, and community trust limitations are documented in INSTALL.md.

First Local Recording Setup

In the application

Open Scrybe. An installation that cannot record yet opens on guided setup, which walks through four steps and can be left at any point:

  1. Welcome — where recordings are kept, that no meeting bot joins a call, and that the transcription-model download is the only network request setup makes.
  2. Recording — the microphone, and what the Microphone and Screen & System Audio Recording permissions are for. macOS raises its own dialog the first time a recording needs one; if you have already refused a capability, each has a button that opens the System Settings pane where it is granted.
  3. Transcription and notes — the transcription model, shown with its source, upstream revision, licence, exact byte count, SHA-256, destination, and disk requirement before anything is requested. Nothing is fetched until you confirm it; the download is cancellable, and it is promoted to its final name only after its size and digest both match exactly. Local notes are checked separately, and setup can be finished without them.
  4. Ready — capture, transcription, notes, storage, and privacy reported separately. Capture is reported as not checked: Scrybe does not read whether macOS has granted microphone and system-audio recording, because no way to read it without prompting exists in this release. macOS asks the first time a recording needs the grant; if you have already refused it, macOS will not ask again, and step 2 above is where it is granted through System Settings instead.

Setup never asks for an account or an API key. Recording remains unavailable while transcription or storage is blocked; notes are optional and visibly so.

Settings carries the same model storage, the same readiness report, and a diagnostics list whose repairs run only when you choose one. Open advanced configuration opens config.toml for the settings no form models.

Reading what is already recorded

Sessions lists what is under the storage root, grouped by the day each meeting was recorded and newest first. A session that never finished, and one that can still be recovered, are marked as such in words as well as by the edge beside them; a session that never wrote a start time is filed under Undated rather than dropped. Only the first page is read, and the list says how many there are when there are more.

Search matches folder names and titles for every session, and notes and transcript text for completed ones. Searching again abandons the search still running, so a slower answer to an earlier question can never replace a newer one.

Opening a session shows what is actually on disk for it: its state, identity, times and length, how it was captured, which providers produced its text, and which of the five artifacts are present and which are not. Notes open first. The transcript is one button away and is read fifty lines at a time — the whole document never crosses into the window, so a long meeting costs the same to show as a short one.

Five actions, each offered only when the session accepts it and each saying why beside itself when it does not:

  • Regenerate notes — replaces notes.md from the durable transcript, through the provider config.toml names. Offered only for a completed session that has a transcript.
  • Repair recording — completes a recording that was interrupted. Offered only when durable state survives for it to recover.
  • Reveal in Finder — opens the session's folder. The folder is resolved in Rust beneath the storage root; the window never handles a path.
  • Copy notes and Copy transcript — read the document and put it on the clipboard, both in Rust. The transcript is never assembled in the window, for the same reason it is never read there whole.

A session with a playback.opus gets a player, using the platform's own controls. One without gets no player at all rather than a control that does nothing: playback.opus is written only for a two-channel capture, so a mono session is complete, has audio, and has nothing to play. The audio reaches the window through a scheme that serves that one file and no other — a URL on it names a session and the word playback, and anything else is refused before any file is opened.

Nothing on these screens edits a note or a transcript, and nothing deletes, archives, or moves a session. The files on disk stay the source of truth; the window is a way to read them.

From the terminal

mkdir -p ~/Library/Application\ Support/dev.scrybe.scrybe/models
curl -L -o ~/Library/Application\ Support/dev.scrybe.scrybe/models/ggml-small.en.bin \
  https://huggingface.co/ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-small.en.bin
shasum -a 256 ~/Library/Application\ Support/dev.scrybe.scrybe/models/ggml-small.en.bin
# expect c6138d6d58ecc8322097e0f987c32f1be8bb0a18532a3f88f734d1bbf9c41e5d

ollama pull gemma4:latest
scrybe init
scrybe doctor

The URL is pinned to an immutable upstream revision, and the digest above is the one the application's own catalog (scrybe-application/models.toml) carries — so a file fetched by hand and one installed through the application are the same 487,614,201 bytes. A resolve/main URL names whatever that branch points at today and no checked-in digest can describe it; do not substitute one.

scrybe doctor reports whether the model is present and whether a configured local notes endpoint is answering. A missing model is reported before a recording starts rather than when transcription does.

On macOS, bare scrybe init writes the local recording profile:

  • [record].source = "mic+system"
  • [record].input_device = "<Core Audio UID from scrybe devices>" (pins the meeting microphone instead of following later OS default changes)
  • [record].llm = "openai-compat"
  • [record].system_backend = "sck" (ScreenCaptureKit; use "tap" only for the macOS 14.4+ legacy Core Audio Tap recovery path)
  • [stt].model = "small.en" and [stt].language = "en"
  • [llm].model = "gemma4:latest"
  • [shell].indicators = ["menu-bar-waveform", "menu-bar-label", "floating-window"] (the default when [shell] is omitted)

The macOS platform data path is ~/Library/Application Support/dev.scrybe.scrybe/. Other platforms resolve the model path through their native data directory convention. Pass --profile default for the synthetic smoke-test profile, or override local model choices with --whisper-model <PATH> and --llm-model <MODEL>.

If a config file already exists, scrybe init refuses to overwrite it; pass --force only when you intentionally want to replace the existing config with fresh profile defaults.

Record with the native macOS shell:

scrybe record "client-call" --shell
# Stop from the menu bar, floating panel, global hotkey, or Ctrl-C.
scrybe list
scrybe show <session-id>

The ergonomic scrybe record TITLE resolves capture source, system-audio backend, Whisper model, and LLM kind from your config and platform probes. Add --shell to construct the configured indicators before capture starts; without it, recording remains headless. ScreenCaptureKit runs directly from the invoking terminal; the legacy tap backend auto-launches through the .app bundle so its Audio Capture TCC grant binds correctly. Run scrybe doctor after configuration to inspect the selected backend and accept or decline its live permission probe. Use scrybe rec --title TITLE --source … --system-backend … --whisper-model … --llm … --shell when you need explicit flag control (CI, debugging, alternate hardware setups).

Select a microphone explicitly:

scrybe devices
scrybe rec --title "client-call" --source mic --input-device <uid>

The terminal prints each accepted transcript chunk while recording, then reports transcript flush, audio encoding, notes generation, and metadata-writing progress after the first stop request. During a shell recording, the menu and panel change to Saving, freeze the elapsed time, and disable Stop & save until finalization returns. A second Ctrl-C aborts finalization immediately. Run scrybe repair <session-folder> to recover unfinished audio or reconstruct missing metadata, then scrybe notes <session-folder> to regenerate missing notes.

For cloud or hosted-compatible LLMs, configure [llm] with a base URL, model, and an environment-variable name for the API key. Secrets stay in the environment, not in config.toml.

Storage Model

Every session is a directory:

~/scrybe/
└── 2026-05-02-1430-acme-discovery-01HXY7K9RZ/
    ├── audio.opus
    ├── playback.opus
    ├── transcript.md
    ├── notes.md
    ├── meta.toml
    ├── pid.lock
    ├── transcript.partial.jsonl
    └── .stignore

The filesystem is the database. meta.toml, notes.md, audio.opus, and playback.opus use atomic replace. transcript.md is append-only. Audio is treated as the source of truth so failed or improved transcription can be regenerated later.

For --source mic+system, audio.opus preserves the source master with the user's microphone on the left channel and system audio on the right. playback.opus contains the same meeting as a centered stereo listening mix, preventing either speaker from playing in only one ear. The exact source layout remains recorded in meta.toml under [audio].layout as stereo:mic-l,system-r; downstream transcription and archival tools must use audio.opus, not the convenience playback mix. Mono sessions produce only audio.opus.

Architecture

scrybe is a Rust workspace with a small core and platform adapters:

Package Role
scrybe Published application and scrybe binary (scrybe-cli/)
scrybe-meeting-core Published session, storage, config, provider, hook, diarization, and pipeline library
scrybe-meeting-application Published session, configuration, diagnostics, and recording services shared by every frontend (scrybe-application/)
scrybe-meeting-capture-mac Published macOS microphone and system-audio adapter
scrybe-meeting-capture-mic Published microphone adapter via cpal
scrybe-capture-linux Private PipeWire/Pulse adapter surface
scrybe-capture-win Private WASAPI adapter surface
scrybe-android Private Android FFI adapter surface

The important public seams are:

  • AudioCapture for platform audio.
  • ContextProvider for meeting metadata.
  • SttProvider and LlmProvider for transcription and notes backends.
  • Diarizer for speaker attribution.
  • Hook for post-session actions.

The Tier-1 stability contract is documented in docs/system-design.md. In short: AudioCapture, MeetingContext, LifecycleEvent, ConsentAttestation, the meta.toml schema, storage invariants, and the Apache-2.0 license are frozen for the v1 series.

Privacy and Network Posture

  • The published application includes OpenAI-compatible provider support, but no network provider runs unless selected in configuration.
  • API keys are read from named environment variables.
  • There is no account system, sync service, telemetry, hosted backend, or bot that joins calls.
  • Courtesy notification is part of the recording flow and is recorded in meta.toml.
  • scrybe mcp (feature agent-access, off by default) is a read-only local-agent surface over ~/scrybe/: it exposes list_recent_meetings, search_meetings, get_meeting, get_meeting_notes, and get_meeting_transcript as MCP tools over stdio JSON-RPC. It is handed a read-only view of the shared session services whose trait surface has no method that repairs, regenerates, writes, or deletes anything, so there is no mutation a tool handler could reach. There is no network listener either — stdio only, reachable only by spawning it as a child process. The server refuses to start unless [agent_access].enabled = true is set explicitly in config.toml.

Run the egress audit locally:

cargo build -p scrybe --release --no-default-features
python3 scripts/check-egress-baseline.py

Current Limitations

  • macOS is the only polished binary distribution target today.
  • --source mic+system defaults to ScreenCaptureKit on macOS 13+ and requires Screen & System Audio Recording. This privacy permission covers screen recording in addition to system audio; deny it if that scope is unacceptable. ScreenCaptureKit runs from the invoking terminal and does not require an application bundle or signing identity.
  • The macOS 14.4+ Core Audio Tap backend remains available as [record].system_backend = "tap" for recovery. It requires the narrower Audio Capture permission and a signed .app bundle. scrybe doctor reports bundle state and offers repair with the project self-signed identity; it never creates or auto-selects a signing identity. A failed or silent Tap switches once to ScreenCaptureKit after a 1.5 s startup window, so a quiet desktop can switch before external audio begins.
  • Reading a session offers no paging control: the first twenty are shown and the count says how many there are. Search is submitted rather than run per keystroke.
  • The native recording shell requires the default cli-shell feature and explicit --shell; headless recording remains available. The menu-bar-label indicator is the Scrybe branding slot, and the status item never includes elapsed time. Configured indicators contain only recording state, elapsed time, and the stop control—never captured or generated meeting content.
  • Native application releases are self-signed with the stable Scrybe community certificate and are not Apple-notarized. Gatekeeper rejection is expected until the user chooses Open Anyway after verifying the release. Apple Developer Program enrollment remains deferred.
  • Linux and Windows recording remain parked until hardware-qualified release paths exist.

Development

Required toolchain is pinned by the repository:

cargo fmt --all --check
cargo clippy --workspace --all-targets --no-default-features -- -D warnings
cargo check --workspace --all-targets --no-default-features
cargo test --workspace --all-targets --no-default-features

Additional gates used by CI include cargo audit, cargo deny check, coverage, LoC budget, egress audit, release planning, and advisory reproducibility checks.

The desktop application is qualified against the built bundle rather than against a test harness, which needs a real macOS login session and so runs locally rather than in CI:

python3 scripts/qualify-desktop-app.py --hermetic --scenario lifecycle
python3 scripts/qualify-desktop-app.py --hermetic --scenario setup
python3 scripts/qualify-desktop-app.py --hermetic --scenario library
python3 scripts/qualify-desktop-app.py --hermetic --scenario model-download-live
python3 scripts/qualify-desktop-app.py --hermetic --scenario recording
python3 scripts/qualify-desktop-app.py --hermetic --scenario installed

Each drives the shipped shape of the application against a disposable storage root and configuration, and reports what it observed rather than what it assumed. library is the one that answers a question no unit test can: whether the webview may load a scrybe-audio:// URL at all. That is decided by the content security policy, and the policy check in that script compares the built artifact against a constant the same file declares — so it passes for any string written in both places. Driving the real webview at a real URL and reading back what the protocol handler served is what tells a correctly-admitted scheme from one admitted under a directive that does nothing.

recording answers a different one. Every surface a recording is stopped from is a platform event, and the dispatch behind each is reachable from a unit test — but that the built application wires them to it is not. A tray item built disabled, a command missing from a capability file, a permission absent from the generated manifest: each leaves the unit tests passing and the application inert. That scenario records a real session through the synthetic source and the stub providers, so it needs no device, no permission grant and no network, and asserts on the session the pipeline actually wrote.

installed copies the community-signed bundle out of the build tree and drives that copy. It verifies the running process came from the copy, records a session and reads the same session through the reader, reads both the Scrybe status item's name and its actions from the native accessibility tree, and checks the explicit community trust contract: stable certificate, expected Gatekeeper rejection, and no notarization ticket. The native accessibility observation requires a human-granted TCC permission for the invoking terminal. model-download-live is the release-only proof that the checked-in production catalog can complete its pinned Hugging Face transfer and promote the exact expected bytes inside a disposable model root.

Project docs:

License

Apache-2.0. See LICENSE.