Scribe is a macOS desktop companion for a Claude planning-scribe skill. It
owns the live session for the current Tuple call, gives Claude a visible review
stream, and renders an internal Markdown handoff as Claude edits it.
Scribe does not need a repository-local configuration file or a meaningful process working directory. It writes nothing into a project until the user chooses Save As… for a finished handoff.
- Install Scribe in Applications and open it. With no call, it waits and detects the next Tuple call without a restart.
- Start transcription in Tuple when wanted. Scribe never starts or restarts transcription. If transcription stops during a call, Scribe reports the gap.
- On first use, choose Install Claude integration. This installs the
planning-scribeskill and a stable CLI shim at~/.scribe/bin/scribe; no shellPATHedits are needed. If a user-managedplanning-scribeskill is already present, Scribe backs it up besideSKILL.mdbefore installing its managed version. - Start
planning-scribefrom Claude in the Git repository being planned. The skill attaches that repository to the active call and learns Scribe's internal notes path. - After Tuple reports that the call ended, Claude performs its final notes pass and finishes the session. Scribe presents Plan ready with Copy and native Save As… actions. Save As copies the internal handoff; it does not move it.
The Tuple call ID is the Scribe session ID. There is no active-call picker. History is for opening recent Scribe sessions and recovering unsaved handoffs.
Tuple's CLI must be installed from Tuple Settings → Integrations → CLI Server.
Scribe checks /usr/local/bin/tuple, /opt/homebrew/bin/tuple, and then its own
PATH, so a Finder-launched app does not depend on a shell working directory.
SQLite is the single operational source of truth:
~/.scribe/
scribe.db
bin/scribe
sessions/<tuple-call-id>/notes.md
The database uses WAL, transactions, schema migrations, and a busy timeout so
the GUI can read while the CLI writes. It stores session state, normalized
source events and source health, Claude chat, decision reviews, file references,
Chronicle matches, and durable per-consumer cursors. The real internal
notes.md is the only non-database session document because Claude edits it and
the renderer watches it.
There are no project chat, transcript, event, or notes sidecars. Scribe has no
normal raw-transcript or Claude-chat export. A finished handoff is the only
normal export, and only an explicit Save As can put it in a project. File
references retain the Git HEAD captured when Claude posted them and open the
current attached-repository file in PhpStorm.
The latest five complete/interrupted sessions keep their full operational, source, and chat data; active and finalizing sessions are always retained. Older terminal-session operational data is pruned. Scribe keeps the latest five internal handoffs and also protects any older handoff that has never been saved externally, surfacing it in History for Save or Delete. A content hash tracks the last Save As, so subsequent edits make a handoff unsaved again. Cleanup only removes Scribe-owned internal files and never touches an exported destination.
Stale active sessions become interrupted after restart and remain recoverable.
For isolated development/tests, SCRIBE_HOME can replace ~/.scribe. Normal
installed use intentionally has one stable CLI-discoverable location.
The installed skill uses the absolute shim path, but examples below abbreviate
it as scribe:
scribe session attach --repo "$PWD"
scribe session current --json
scribe tick --wait --cursor planning-scribe --timeout 30s --limit 200
scribe session finishsession attach resolves the canonical Git root and returns JSON containing the
session ID, absolute internal notesPath, attached repository, state, and source
health. It never changes the notes path. tick performs source collection,
normalization, filtering, deduplication, chronological batch ordering, and a
transactional durable cursor for the named consumer. A late source event is
delivered once with its original occurredAt; the skill never parses SQLite,
Tuple, or Chronicle storage itself.
Visible chat and review commands bind to the active/finalizing Scribe session:
scribe say "The job already sets **tries** to `1`." \
--ref-heading "Decisions>Retry placement" \
--ref-snippet "Retries live in the job"
scribe ack "I’m checking the export path."
scribe decision "Retries live in the job, not the client." \
--id retry-placement \
--file app/Jobs/SyncRefundsJob.php:14
scribe unlink <message-id>
scribe read [<message-id>]say and decision require --ref-heading and --ref-snippet together when
using a note reference. --file accepts repository-relative
path[:line[-end]]; backticked paths are inferred. Errors are readable on
stderr and exit nonzero. CLI writes do not depend on the GUI being available.
Run scribe --help for the complete syntax.
Scribe discovers the active call with:
tuple call current --format json
It consumes machine-readable transcription and lifecycle records with Tuple's
durable per-call scribe-<call-id> cursor. The initial read catches up backlog,
processes serialize through a per-call lock, and restarts do not create gaps or
repeats. Speech occurrence time is the spoken/start time, not transcription
completion. Tuple's explicit call_ended moves Scribe to finalizing;
recording_ended only reports that transcription stopped.
Scribe implements the Chronicle schema-1 wire contract documented in the
authoritative Scribe integration document.
It reads the atomic sessions.json registry and ignores sessions.json.lock.
It never looks for current.json.
Scribe resolves the Chronicle root in this order:
- the Chronicle folder explicitly selected in Scribe and persisted in
scribe.db; CHRONICLE_HOMEinherited by the Scribe process;~/.chronicle.
The default is detected without prompting. Choose Chronicle folder appears
when no registry is found and remains available under Sources for a deliberate
override. A Finder-launched Scribe cannot inspect PhpStorm's
-Dchronicle.home, and it may not inherit a shell-only CHRONICLE_HOME; select
the matching root once in that situation. Once the root is known,
sessions.json supplies absolute log paths.
After the planning skill attaches a repository, Scribe matches its canonical
Git root against every session.repositories[].root, then prefers active and
time-overlapping sessions. Equally good matches require explicit selection.
Chronicle owns the active, completed, and interrupted states, including
stale PID/heartbeat demotion.
Scribe safely tails the selected absolute UTF-8 append-only JSONL log, defers a
possibly truncated final line, and treats malformed complete records as source
errors. It validates schema/version, IDs, gapless per-session sequence,
millisecond UTC timestamps, event data, and path rules; deduplicates by event ID
and source sequence; imports normalized records into SQLite; and merges by
occurredAt, never append order. redacted: true marks selected/snippet text as
untrusted while preserving accurate paths and ranges. audio_transcription is
rejected because Chronicle never emits it in Scribe mode.
Chronicle owns and prunes its registry/logs. Scribe never modifies or deletes them; Scribe retention applies only to imported SQLite records.
npm install
npm run tauri devBuild the frontend with npm run build and the app with
npm run tauri build. Rust checks live under src-tauri:
cargo test --manifest-path src-tauri/Cargo.toml --lib
cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
cargo fmt --manifest-path src-tauri/Cargo.toml -- --checkRelease builds check GitHub releases through Tauri's updater. Creating releases, tags, and updater artifacts is intentionally separate from this architecture.
The old cwd-driven .scribe.json, SCRIBE_CONFIG, SCRIBE_NOTES, and
repository Markdown/JSONL sidecar workflow is intentionally removed. Existing
project sidecars are left untouched but are not imported. They cannot safely
represent one installed app session shared by concurrent GUI and CLI processes;
SQLite and the internal handoff replace that ownership model.