changelog
scratch notebook for Claude Code I use daily — per-thread STATE docs that resume where the last session died, plus an INDEX that recompiles itself
Create-or-resume + the self-saving notebook.
One command to continue a thread: /ccx:start-thread on an existing topic now resumes that thread instead of stopping on its STATE doc. And the notebook keeps itself fresh — three new hooks make persistence automatic:
Added
- Create-or-resume
start-thread— reopening a known topic resumes its scratch workspace; no duplicate threads. auto-compile-indexhook (PostToolUse on Write|Edit) — any STATE-doc write kicks a detached,.pending-debounced INDEX recompile. The INDEX saves itself.record-session-threadhook — records{display, slug, source, ts}per session under, linking sessions to threads./.sessions/ state-freshness-guardhook (Stop) — if work is newer than the thread's STATE, blocks the stop once with a "persist Status/summary first" nudge (10 min grace, 30 min throttle,SKIP_STATE_GUARD=1escape hatch).
Fixed
./-prefixed and trailing-slash paths normalize correctly;extra_sectionsstrings are sanitized; adopts are counted in the tidy tally.
CI
- Test suite + plugin validation now run on every push and PR.
Verified: 73/73 tests (new hook e2e sections on tmpdir fixtures), claude plugin validate pass.
Full changelog: v0.3.0...v0.4.0
Hardening release: unicode-correct identity, defensive parsing, and a config-driven INDEX.
Added
extra_sections— config-driven live sections rendered into the INDEX.tidy-scratchnow adopts file-rich directories that lack a STATE doc (writes one) instead of proposing deletion.
Fixed
- Unicode-aware slugify with NFC-normalized identity and a
normalizeForMatchduplicate guard; slug cuts and summary clips never split a surrogate pair. - Summaries clipped to 240 chars and status lines to 200 at render time, keeping the INDEX bounded.
- Backlink hook anchored to the project's own scratch root (no more cross-project matches).
- CRLF STATE files parse correctly; live
git/ghcalls are capped at 5s so a hung remote can't stall a compile. - Config values validated per-field — one bad field falls back to its default instead of discarding the whole file.
Docs
- Fixed stale two-hook references left from the 0.2.0 hook removal.
Full changelog: v0.2.0...v0.3.0
Slimming release: the plugin stops policing host projects.
Changed
- Removed the
block-stray-scriptshook and its project-specific allowlist — ccx no longer vetoes where scripts live in the host project; it only manages the scratch notebook. - Redesigned the ccx icon: transparent mono wordmark that works in both light and dark themes.
CI
announce-releasenow fails soft when theBLOG_DISPATCH_TOKENsecret is absent, so a release never shows a red X for a missing optional notification.
Full changelog: v0.1.1...v0.2.0
Maintenance release — namespace move and release automation. No functional changes to the plugin's skills, scripts, or hooks.
Changed
- Repository moved to the
shck-devorg. The install path is now:(The old/plugin marketplace add shck-dev/ccx-context-system /plugin install ccx@ccx-context-systemofcRS/...path still 301-redirects, but new installs should useshck-dev.) - Plugin version bumped to v0.1.1.
Added
- CI: published releases now notify the shck.dev blog via
repository_dispatch— auto-rebuilds the changelog and opens a draft announcement PR.
Full Changelog: v0.1.0...v0.1.1
First public release: 3 skills (/ccx:start-thread, /ccx:save-state, /ccx:tidy-scratch), 2 hooks (script hygiene gate, graph backlinker), compiled parallel-session-safe INDEX. Language-agnostic, runs under bun. MIT.
Install:
/plugin marketplace add ofcRS/ccx-context-system
/plugin install ccx@ccx-context-system
Notion MCP server I built: search, export, and import pages as markdown — no OAuth, just a browser cookie
v0.5.2 — fix writes (saveTransactionsFanout migration)
v0.5.1 — validate OIDC trusted-publishing pipeline
Credential-free startup, npx support, interactive init, prompts & resources
Validation & runtime
- Server boots and enumerates tools/prompts/resources with no credentials (lazy config) — marketplace validators can connect.
- Compiled to a node bundle:
npx @shck-dev/notion-mcpworks without Bun (node ≥18).
Interactive setup
npx @shck-dev/notion-mcp init(CLI) and thenotion_inittool: paste a browser "Copy as cURL" → token/user/workspace extracted → saved to~/.notion-mcp/config.json(0600).
New surface
- MCP prompts:
notion_setup,notion_search_export. - MCP resources:
notion://guide,notion://recent.
Fixes
- Sub-pages export as markdown links; pages with no extractable text return an explanatory note instead of empty output.
Full diff: v0.3.0...v0.4.0
Backfilled release for 0.3.0 (already published to npm).
notion_append_to_page/notion_append_to_page_from_file— non-destructive append- To-do checkboxes (
- [ ]/- [x]) and strikethrough (~~…~~) in markdown - Actionable error when the
token_v2cookie is expired/invalid
Commits: 23cc441…a7250f9
Fixes
- Import/export was returning empty pages. Notion quietly added an extra
.valuewrapper inrecordMapentries, so every tool that read blocks silently parsed nothing. Unwrapped in one place via a newnormalizeBlockMap()+unwrapRecord()helper. notion_export_pagenow throws a real error on unknown / inaccessible pages instead of returning"".- Optional
NOTION_DEBUG=1env var logs raw request/response to stderr for troubleshooting (stdout stays clean for JSON-RPC).
New features
notion_list_comments— list open discussion threads on a page, including anchor context, author, and timestamps.notion_add_comment— start a new discussion. Passanchor_textfor an inline comment (yellow-highlighted on the matching text span, preserves existing bold/italic/code on that span); omit it for a block-level comment.notion_reply_comment— reply to an existing thread.
Other
- Fixed stale table unit test to match the native-table shape from 0.1.8.
- 77 unit + integration tests passing.
meeting recorder for macOS with no bot in the call — captures both sides locally, transcribes on-device with Whisper, leaves you a folder of markdown
Install
Download Kleoth-0.5.3.dmg (8.3 MB), open it, and drag Kleoth.app onto Applications. Builds are self-signed, not notarized, so macOS blocks the first launch once. On macOS 15 or later, click Done, then System Settings → Privacy & Security → Open Anyway. On macOS 14, right-click Kleoth.app → Open.
SHA-256: 09a11716b11dd58502b0442a4801bae07f5d37cea0e04c672cedb8e10f96f334
Upgrading from an earlier version: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Fixed
- Opening Settings crashed Kleoth on every Mac but the one that built it (#22). The same trap
as #17, one level down: Settings opens on General, whose keyboard-shortcut recorder (Start / stop
recording) comes from the KeyboardShortcuts package, which looked for its labels in a folder
that exists only on the build Mac. Kleoth now ships a copy of that package that finds them inside
the app, and the release build refuses any dependency still looking in the build folder.
The release before this one, 0.5.2, fixed the launch crash (#17). New in 0.5.1: dictation that fits the text you dictate into, meetings from the pill, calls that offer to be recorded, and full-width meeting covers.
Install
Download Kleoth-0.5.2.dmg (8.3 MB), open it, and drag Kleoth.app onto Applications. Builds are self-signed, not notarized, so macOS blocks the first launch once. On macOS 15 or later, click Done, then System Settings → Privacy & Security → Open Anyway. On macOS 14, right-click Kleoth.app → Open.
SHA-256: 8ce18e6f7db636a2dd9b5dc80282159d58c5b68dd2b2b2133b3bdc1f69032b10
Upgrading from an earlier version: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Fixed
- Kleoth quit at launch on every Mac but the one that built it ("could not load resource
bundle", #17). The app looked for its images in a folder that exists only on the build Mac, so
every release so far crashed on first launch anywhere else. It now finds them inside the app.
If you worked around it by copyingKleothApp_KleothApp.bundleto the top of Kleoth.app,
replacing the app with this version removes the copy (and restores the app's signature).
New in 0.5.1, the release before this fix: dictation that fits the text you dictate into, meetings from the pill, calls that offer to be recorded, and full-width meeting covers. See the 0.5.1 notes.
Install
Download Kleoth-0.5.1.dmg (8.3 MB), open it, and drag Kleoth.app onto Applications. Builds are self-signed, not notarized, so macOS blocks the first launch once. On macOS 15 or later, click Done, then System Settings → Privacy & Security → Open Anyway. On macOS 14, right-click Kleoth.app → Open.
SHA-256: 70fafe5d9aeca378561204baf35a1f4cc39aa6a1d7b0b03bdaf8095f87d4593f
Upgrading from 0.5.0: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Added
- Dictation fits the text you're dictating into. Speak a correction or an addition over a
selection and the two come back as one piece in its place: nothing outside it changes, and one
⌘Z (or History) brings the original back. At the cursor, your words continue the sentence; short
mid-sentence dictations are now cleaned up too (not in messengers). A terminal selection is only
a spelling reference. A selection of 4,000 to 20,000 characters, or one the cleanup failed on,
stays as it was with the dictation after it; an unreadable or longer one is replaced as before. - What dictation reads from the field you dictate into, and where it goes. When you press
fn+shift, Kleoth only asks the app what kind of field has focus. When you release, it reads that
one field: the selection, and up to 1,500 characters before and 500 after the cursor or
selection. It sends them, with your words, to the AI provider that already cleans those up, and
only when that is OpenRouter or Claude Code; a local server and Apple's on-device model get your
words alone, and a selection then keeps the dictation after it. Password fields and password
managers (1Password, Bitwarden, Passwords, Keychain Access) are never read. The text around the
cursor is never stored. It's on by default; turn off Use the text you're dictating into in
Settings → Dictation to read nothing (stored asdictation_context:"false"). - Meetings from the pill. Hover the pill: the dock now has four fields — Dictate · Meeting ·
Screen · More ("Record" is now "Screen", now that two fields record). Meeting starts a
meeting recording exactly like Start Recording in the menu bar, and the pill's menu has Record
meeting, or Stop meeting recording while one runs. While a meeting records — started from
the pill, the menu bar, the global hotkey or akleoth://link, even with dictation off or the
pill hidden for an hour — the pill is its control bar: elapsed time, a microphone and a
system-audio meter, and Stop. A system meter that stays flat while others talk is the sign that
System Audio Recording isn't allowed. Once the meeting is saved, the pill says "Meeting saved ·
12:03" for a few seconds; click it to open the meeting in History. Dictating during a meeting
works as before and the bar comes back afterwards; a screen recording started during a meeting
takes the bar over until it ends. With the bigger dock, a pill parked near a corner sits a little
further from it, so the dock comes out without the pill shifting. - Calls that ask to be recorded (off by default). Turn on Offer to record calls in
Settings → Meetings and, when Zoom, Teams, a Slack huddle, FaceTime, Google Meet in a browser or
another app starts using the microphone, the pill asks "Zoom call — record it?" with Record,
Never for Zoom and ✕ (with calendar access, "“Weekly sync” on Zoom — record it?"). Nothing is
recorded until you click Record, and the seconds before the click are not captured. ✕, or no
answer for 30 seconds, means not for this call: no second offer while it lasts, nor for that app
within 10 minutes. An offer never appears during a meeting or a screen recording, waits while you
dictate, and never pulls the dock out from under the pointer. When the call app lets go of the
microphone during a meeting, the pill suggests stopping once, 20 seconds later ("Zoom released the
mic — stop recording?"); Kleoth never stops a recording on its own. Settings → Meetings lists
every "Never for …" with Remove. Kleoth only notices which apps are using the microphone: no
audio is read, no permission is asked for, and nothing leaves your Mac. With Screen Recording or
Accessibility already allowed, it reads the call window's title to tell a Meet tab from other
sites; a title that names no meeting is never saved. - Every meeting remembers where it happened. A meeting now gets its
meta.jsonthe moment it
stops, transcribed or not, and whether call detection is on or off. An untranscribed recording
is named after its calendar event (with calendar access) or "Recording · Zoom · Sep 24, 14:05",
can be renamed right away, and keeps where it happened, through transcription: the app, the
service (Zoom, Google Meet…), how you started the recording, the calendar event, and a window
title only when it named a meeting. All of it is read on your Mac and kept in that file; the
event's title and attendees also go to your AI provider with the transcript, for the summary. When
the calendar event had exactly one other person in it, the other side of the transcript is named
after them instead of "Them"; renaming the speaker still wins.
Changed
- Meeting covers, full width. A cover now runs across the top of the meeting page — the whole
width, cropped to a wide band — with the title and the TL;DR under it; as you scroll, the picture
slides a little slower than the page. Click the cover to see it full size (the Quick Look panel;
Esc closes it). Right-click it, or use the…button in its corner, for New Cover, Remove
Cover and Show in Finder. A summarized meeting without a picture shows a Draw Cover chip
beside its other details ("No cover" when the meeting looked personal, "Cover failed" with the
reason and Try Again). The whole page now scrolls as one, the audio player included, and the
TL;DR is no longer repeated as the summary's first card. History rows show a bigger thumbnail,
only on meetings that have a cover or are getting one. With Reduce Motion on, the picture scrolls
with the page. - Scenes lean abstract. The scene written for a cover now leaves out photos, picture frames,
posters, paintings and mirrors, even when the meeting is about them: a picture inside the picture
invited faces and lettering, and often came out blank. It also pictures the meeting's topic as
one physical metaphor — something joined, balanced, mended or carried across, in the meeting's
own world — rather than a re-staged meeting room.
Fixed
- Telegram Desktop messages were restructured like prompts. Kleoth knew Telegram for macOS and
Telegram Desktop's Linux id, but not its Mac one, so long messages got an editor's full rework.
Telegram Desktop now gets the light touch of the other messengers: pasted as heard at the cursor
(unless "Also clean up short dictations and chat messages" is on), and a merged selection keeps
its wording and your voice. \/in meeting and dictation files.cover.json,meta.json, the other meeting files and
the dictation day files wrote every/as\/, so a model name readz-ai\/glm-5.3-flash. They are
written plainly now; files written before still read fine.- A leftover
.cover-…tmpfile. A crash or a force quit at the instant a cover was being
saved could leave that hidden temp file in the meeting folder for good. Once it is an hour old it
is now removed at the next launch, or before the next cover is saved there. - On-device meeting transcripts came out as a few long blocks. A meeting transcribed on device
showed each side in one or two long paragraphs — most of what you said, then most of what the
other person said — sometimes with a "Thank you." nobody said. The words and who said them were
right; the order of the turns was lost. Whisper's own timestamps stretch each sentence over the
silence after it, so the transcript never saw where anyone stopped. The on-device engine now
times each stretch of speech by its words, as it already did for screen recordings, and a new
turn starts wherever the other side spoke in the pause, even for a one-word "Sure." in a quick
exchange (a reply that talks over you keeps your turn whole). The "Thank you." Whisper made up
in a silence goes too: timing by words drops a sentence with no speech under it. Transcribing
takes about as long as before. Cloud (Scribe) transcripts are unchanged. A meeting transcribed
on device before this keeps its old blocks until it is transcribed again: Remove
transcription, then Transcribe.
Install
Download Kleoth-0.5.0.dmg (7.8 MB), open it, and drag Kleoth.app onto Applications. Builds are self-signed, not notarized, so macOS blocks the first launch once. On macOS 15 or later, click Done, then System Settings → Privacy & Security → Open Anyway. On macOS 14, right-click Kleoth.app → Open.
SHA-256: 2243af2c1c2c93a760cb2e6af6dae658d24a38122f09df50404aebbbb4426adf
Upgrading from 0.4.0: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Added
- Go hands-free in the middle of a dictation. Started holding fn+shift and it's turning into a
long one? Tap ⌘ while you hold, or click the pill: the same dictation keeps listening, you can let
go of the keys, and it ends like any hands-free dictation (tap fn+shift or click the pill; Esc
cancels). Nothing said so far is lost. While you hold, the pill shows a faint lock where the
hands-free dot goes. Adding ⌥ or ⌃ mid-hold still ends the dictation as before; only ⌘ changed. - A dictation is never lost to a failed transcription. When Scribe can't transcribe a
dictation — it timed out twice, the network dropped, the key was rejected — the audio is kept
and the pill says so: "Timed out — saved to History", with a Retry button that sends the
same audio again and pastes the text into the app in front. Pressing Esc while a dictation
transcribes stops waiting without throwing it away either ("Stopped — saved to History"). - Untranscribed dictations in History. They show as "Not transcribed" with an "Audio saved"
chip and the reason. The detail pane offers Try again in cloud and Try again on device
(the free on-device engine, when Scribe keeps failing) and copies the text to the clipboard when
it's ready; Show Audio in Finder reaches the clip. Deleting the row moves its audio to the
Trash. Once a dictation is transcribed its audio is deleted — dictations that paste keep none. - Meeting covers (opt-in). A summarized meeting can get a small square picture — cute animals
or everyday objects acting out what the meeting was about — on its History row and in its
header. It is off by default: Settings → Meetings → Covers turns it on by picking an engine, a
local image server (Ollama), Codex on your ChatGPT plan, or OpenRouter. The style is Animation,
Illustration, Sketch or Clay, or Automatic to match the meeting's mood. Your AI provider writes a
one-sentence scene from the title, the TL;DR and the start of the overview — no names, quotes or
transcript — and only that scene goes to the image engine. The cover is drawn once, right after
the meeting's first summary (or only when you ask), and saved in the meeting folder as
cover.jpg, next tocover.json, which records how it was drawn. A meeting that looks personal
— health, a performance review, pay, hiring, legal matters, family — gets no picture. Click a
cover for New Cover (another idea, in any style; the old picture goes to the Trash) or
Remove Cover (the picture goes to the Trash, and that meeting is never drawn again unless
you ask); select older meetings and choose Draw Covers to give them one. What OpenRouter
covers cost shows as one row in Accounts → Usage.kleoth illustratedraws covers from...
the command line;--dry-runprints the scene and the image prompt without drawing. kleoth summarize --max-output-tokens. Sets the summary's output-token budget (1 to
1000000, default 8192); an answer cut off at that limit is retried once with twice the budget.
It applies to OpenRouter and local servers — the Claude Code and Codex CLIs take no output cap.-KleothSimulateFirstRun(testing). Quit Kleoth, then
open -a Kleoth --args -KleothSimulateFirstRun YESlaunches it as if consent and onboarding had
never been done, for that launch only, so the first-run flows can be checked on a Mac that
finished them long ago. The flag stores nothing.
Changed
- First launch on macOS 15 and later. macOS 15 removed right-click → Open for apps that
aren't notarized. The DMG's Read Me and the Homebrew cask now give the step that works: click
Done, then System Settings → Privacy & Security → Open Anyway. Right-click → Open still works
on macOS 14.
Fixed
- "The focused field blocks dictation" when it didn't. Secure input is a lock any app can
hold, and a browser can keep it after you leave its password field — so dictation into Slack
was refused because a browser in the background still held it. The pill now names the app holding
it ("Dia has secure input on — dictation blocked"), so you know what to close or click out of.
The paste-time warning names it too. - Long dictations timing out. Scribe got a flat 25 s whatever the length of the clip, and
dictations of a minute or more ran out of time when the service was slow. The budget now grows
with the clip (25 s plus half its length, up to 2 minutes), and a timeout or network error gets
one automatic retry a second later. Each dictation records how long its transcription took. - "Dictation history…" opening on Meetings. History opened from the pill menu, Settings or
the popover's recording row now lands on the tab it was asked for, not only when the window was
already open. - Incomplete or cut-off summaries. An answer that left parts out — no overview, no action
items — was saved and shown as a finished summary with those sections silently missing. A
summary is now saved only when it is complete: not cut off, readable, and with a TL;DR, an
overview, action items and per-speaker highlights (an empty overview or list is fine). An answer
short of that gets one retry aimed at what went wrong — asked again more compactly with twice the
room, asked for the missing parts by name, or simply asked again after an empty answer — and
otherwise fails with a message that says why: "The summary was cut off…", "The summary came back
incomplete (no overview)…". A cut-off is now recognized as one even when the provider reports a
normal finish, as Codex, Claude Code and some local servers do. Nothing partial is ever written:
the transcript is saved, and a summary already on disk stays. - A failed summary looked like one that was never tried. When the summary step failed after a
transcription, only the popover's status line said so ("summary skipped"); the meeting itself
showed "No summary yet". The reason now shows where you look: a "Summary failed: …" card on the
meeting and a red Failed chip on its History row (hover for the message), and the popover says
"summary failed" with the reason. A run that writes no summary no longer names a model or
provider inmeta.json, so the header stops showing the model of a summary that failed. - Only the newest meeting's summary could be retried. Short of transcribing a meeting again
with the other engine (paid in the cloud, or on device for a cloud-only meeting), the app's only
retry waskleoth://summarize-latestor its app intent. Every transcribed meeting without a
summary — never summarized, or the last try failed — now has a small Summarize button beside
"No summary yet". It runs the summary alone on your current AI provider, and the meeting reads
"Summarizing…" meanwhile. It is disabled while the meeting is busy, and when no AI provider is
available — its tooltip then says why.kleoth://summarize-latestnow does the same for the
newest meeting only. It says so when that meeting isn't transcribed yet, and does nothing while
it is still processing. It never falls back to an older meeting. - The record hotkey did nothing before the consent notice was acknowledged. Neither did
kleoth://recordorkleoth://toggle: Kleoth refused, but said so only in the popover's status
line (the Start Kleoth Recording app intent answered with a dialog that offered nothing to
click). A small Before you record window now comes forward with the popover's consent
notice. I understand — start recording acknowledges and starts, and the window closes once
the recording runs — or stays and shows why the start failed. Not now records nothing. - Two Settings footers that weren't true. Meetings → Summarization called Gemini Flash the
default (it's GLM on OpenRouter); it now names the provider your summaries run on. General →
Shortcuts promised Shortcuts and Spotlight actions, which these builds don't surface; it now
points atkleoth://toggle.
Install
Download Kleoth-0.4.0.dmg (13.7 MB), open it, and drag Kleoth.app onto Applications. Current builds are self-signed: on first launch, right-click Kleoth.app → Open.
SHA-256: 03313badda5b51e895f78f7c3d3f18c35f9f6a5f1bb120b5e6d9ff6d529d0cf6
Upgrading from 0.3.0: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Added
- Bring your own AI. Summaries and dictation cleanup can now run on the Claude Code or
Codex CLI you already have signed in, on a local OpenAI-compatible server (Ollama, LM Studio),
or on Apple's on-device model (macOS 26, dictation only) — OpenRouter is one option among five.
A fresh install auto-detects what is available (Settings → Accounts → AI provider);
kleoth summarize --providerpicks one on the command line. - Settings window redesigned. A sidebar of six pages — Meetings, Dictation, Screen Recording,
Microphone, Accounts, General — each a plain grouped form under its own title, the way System
Settings does it: no banners, no illustrations, system type throughout. Every setting is where
it was, grouped by what it is for; each feature page ends in a row that opens its history, and
the Accounts page links to the providers' key pages. The window remembers the last page. The
sidebar is a flat vibrant list beside the form, not the floating glass card with an empty
toolbar band that macOS 26 gives a split view. - Pill peek dock. Hovering the resting pill now pulls out a dock of three captioned fields —
Dictate, Record, More — in Liquid Glass on macOS 26 (a dark ink look elsewhere), with a quiet
hover lift and a hand cursor. Dictate starts a hands-free dictation from a click (click the
listening pill, press fn+shift or Esc to end it), Record starts a screen recording as before,
and More (or a right-click anywhere on the pill) opens the pill's own menu: start dictation,
record the screen, pick a microphone, paste the last dictation, open the dictation history,
hide the pill for an hour, settings. The fn+shift hotkey is unchanged. - Microphone setting. Settings → Microphone (and the pill menu's Microphone submenu) picks the
input device Kleoth records from — meetings, dictation and screen recordings alike. Automatic
follows the system input; a microphone that is not connected falls back to it.
Changed
- New ident. The lyre is drawn natively in the popover header and onboarding — it breathes
at rest, quivers while a meeting records and ripples while one transcribes (still under
Reduce Motion). New charcoal app icon, green-stone empty states and README imagery.
Install
Download Kleoth-0.3.0.dmg (12.2 MB), open it, and drag Kleoth.app onto Applications. Current builds are self-signed: on first launch, right-click Kleoth.app → Open.
SHA-256: 00fd1be64f4e1cdeec84555acc8a501d6f1cd717089b7aeaf95181e09218e10c
Upgrading from 0.2.0: just replace the app. Your data in ~/Kleoth and your keys in the Keychain are untouched.
Added
- Screen recording. Hover the pill and click the red record glyph — or pick "Record screen…"
from the menu-bar popover — to record your screen with system audio and your microphone in one
file. A dimmed picker lets you drag a region or press Return for the whole display (Esc cancels);
the pill then shows a live timer, and hovering it turns the timer into a stop button. When you
stop, the pill confirms with the length and file size — click it to reveal the recording in
Finder. Files land in~/Kleoth/screen-recordings/as 1080p H.264 MP4s (about 22 MB per minute)
that you own outright: nothing is uploaded. Quitting mid-recording asks
first and still saves the file, and a recording interrupted by a crash is recovered on the next
launch. Recording and dictation coexist — dictating during a recording morphs the pill in place
and hands it back when you are done. - Recordings viewer (proof of concept). History gained a Recordings scope listing every screen recording. Each
recording is transcribed on device right after it is saved (word timings included); the viewer plays
the video with the transcript beside it, highlights the word being spoken, seeks when you click a
word, and lets you double-click a word or the title to correct it. Older recordings can be
transcribed on device or in the cloud from the viewer. The transcript lives in a small.json
next to the.mp4; the movie itself is never modified. This is deliberately a first cut: no
trimming, no sharing, no export, no live subtitles — the recording is the file, the transcript is
the sidecar, and the viewer just shows them side by side. - A live recording toolbar. While recording, the pill becomes a bar with a pulsing dot, the
elapsed time, live microphone and system-audio meters, and an explicit Stop button — only Stop
stops, so a stray click cannot end a recording. The bar stays horizontal on every screen edge and
follows a drag cleanly, and dictating mid-recording keeps the capsule horizontal too. - Menu-bar popover rows are clickable across their whole width, not just on the label text.
- The resting pill reacts to the pointer. Hovering the half-tucked capsule pulls it fully on
screen with a mic glyph; it tucks back shortly after the pointer leaves. - The pill answers the hotkey on the first frame. Pressing fn+shift now hops the resting
capsule out of its edge immediately (mic on, mic glyph); the waveform grows out of it once the
hold is confirmed, which now takes 0.2 s instead of 0.3 s. A short tap sinks it back. pillsandbox(dev tool).swift run --package-path app pillsandboxopens a control window
that drives the real pill through every phase, edge and mic level with no signing, Keychain or
Accessibility involved;--filmrenders a transition to PNG frames plus a contact sheet
so the motion can be reviewed without watching the screen.
Changed
- Dictations into a terminal get the full clean-up. Terminals (Ghostty, iTerm2, Terminal,
Warp, kitty, Alacritty) used to get a deliberately plain, single-line, keep-every-word polish.
Coding assistants such as Claude Code live inside a terminal, so long spoken prompts came back
as one unbroken paragraph with spoken self-corrections left in. Terminals now get the same
restructuring as editors and AI chats: paragraphs, lists where you enumerated, the word you
settled on. Messaging apps are unchanged. - Faster pill motion. Every rise, sink, morph and hover peek runs in roughly 0.2–0.35 s
(was 0.5–0.55 s); the stretch and content reveal are scaled to match. - Pill motion rebuilt. The capsule's size is now an explicit animated value, so it grows out of
the edge instead of popping to its full shape while still tucked; a keyframed squash-and-stretch
follows the direction of travel (squat, stretch on the way, land, settle); the waveform blooms in
only once the pill has left the edge and fades before it sinks back; the whole capsule breathes
with the mic level. The pill code moved into aKleothPillUIlibrary shared by the app and the
sandbox.
Fixed
- Bluetooth headphones stayed in phone-call quality after a dictation. Each dictation put a
headset into its hands-free profile (as any microphone use does), but the profile never released:
music and system sound stayed tinny until Kleoth was quit. A stopped audio engine still holds the
microphone open as long as the engine object exists, so the dictation and meeting captures now
build a fresh engine per session and free it the moment the session ends, cancels or fails — the
headset is back to full quality about a second after you let go of the hotkey. The pill now
acknowledges the press before the microphone opens, so the slightly slower engine start is not
felt. - Crash a few seconds after starting a dictation. Five crash reports (2026-09-04…06) shared one
signature: an Objective-C exception from AVFoundation — "Failed to create tap due to format
mismatch" — raised when the hotkey opened the mic after the default input device had changed
while Kleoth was idle (a Bluetooth headset connecting or disconnecting). AppKit swallowed the
exception, which corrupted the Swift runtime's thread state, and the app then died on the next
hotkey press or timer tick. Both causes are fixed: every capture now installs its tap at the
hardware's current input format (the engine's cached output format goes stale across a device
switch and never recovers), and every AVFoundation call that can raise is bridged into a Swift
error, so a refused tap shows a red pill instead of taking the app down. - Polish timing out on long dictations. Installs that opened Settings on 2026-09-03 had the
slow former polish model persisted as their choice and kept using it — a fifth of all polishes
ran past the 8 s budget and pasted the raw transcript. That slug now migrates to the current
default (the same texts polish in 1–2 s), the ceiling is 30 s instead of 8 s, and pressing Esc
while the pill is polishing pastes the raw transcript immediately instead of cancelling the
dictation. The dictation log records how long each polish took (polish_seconds). - External microphones. Recording and dictation failed outright with a Bluetooth headset mic
(Sony WH-1000XM5 and the like): those run at 16 kHz mono, where the AAC encoder caps at 48 kbps and
rejected Kleoth's fixed 64/128 kbps request. The bit rate now follows the encoder's own limit for
the device's format. Both captures also survive the profile switch a Bluetooth headset performs
right after its mic is opened, and a device swap mid-session: the tap is reinstalled and the audio
is converted into the file already being written, instead of the capture ending at the switch. - Dictation pill jumped sideways on a left/right edge. After every transition the pill's
window was being widened by SwiftUI to fit the capsule's un-rotated width, re-centering the
capsule 23–31 pt toward the screen edge (and pushing the resting sliver fully off-screen). The
capsule is now an overlay on a size-less root, so only Kleoth sizes that window. Bottom/top
edges were never affected.
Changed
- Short dictations and chat messages are pasted as heard. The OpenRouter clean-up call now
runs only when it earns its latency: dictations of fewer than 24 words, and every dictation into
a messenger (Telegram, Slack, Discord, Teams, WhatsApp, Messages, Linear), paste Scribe's
transcript directly —no_verbatimalready strips fillers, so a casual one-liner arrives ~1 s
sooner with no LLM in the loop and no risk of a rewrite. Longer dictations into editors, AI
chats, notes, mail and browsers are still cleaned up and structured. Settings → Dictation →
"Also clean up short dictations and chat messages" restores the old always-polish behavior
(Keychaindictation_polish_always). Skipped rows show an "As heard" badge in the Dictations
detail pane (not the orange "Raw" fallback — nothing failed).
[0.2.0] — 2026-09-03
Added
-
Dictation (hold fn+shift, speak, release). System-wide voice typing: the utterance is
transcribed by ElevenLabs Scribe (scribe_v2,no_verbatim, your personal-dictionary terms
as keyterms), cleaned up by ONE short OpenRouter "polish" call (fillers/self-corrections removed,
never translated — falls back to the raw transcript within 8 s if the model is slow, blocked, or
changes the language), and pasted into whatever app has keyboard focus via the clipboard + a
synthetic ⌘V; your previous clipboard is restored 0.5 s later. Double-tap for hands-free, tap
once to stop, Esc cancels. A small dark pill rests half-tucked into a screen edge (bottom by
default; drag it along the edge, or toward another edge to dock it there — side edges stand it
up) and springs out while you speak: a live waveform while listening, a travelling wave while
transcribing and polishing, a check when the text has landed, words only for warnings and
errors. Silence just puts it back. Text-only history lands in~/Kleoth/dictations/and is.json
browsable from the new Dictations scope in the History window; a personal dictionary lives
in~/.config/kleoth/dictionary.json(Settings → Dictation). Audio is never kept. Opt-in
(Settings → Dictation) and requires the Accessibility permission (for the hotkey and the
paste);dictateis a headless CLI probe for the pipeline. -
Polish adapts to where you're typing. In composition surfaces — AI chats (Claude, ChatGPT,
Cursor…), editors, notes, documents, mail, browsers — the polish step restructures spoken
brainstorming into the text you would have typed: ideas reordered, fragments merged, spoken
lists rendered as lists, the word you settled on kept, thinking-out-loud dropped, every point
preserved and nothing invented. Messaging apps get a light touch that keeps your sentence order
and voice; terminals get plain single-line text. The spoken language is always kept, including
Russian sentences with English technical terms. -
Choose the engine per meeting. An untranscribed recording's detail pane now offers
both Transcribe (free, on-device) and Transcribe in cloud (ElevenLabs Scribe, your
key) side by side. -
Per-tier transcript variants. Re-transcribing a meeting with the other engine keeps the
previous transcript + summary as a variant instead of overwriting it — switch between the
On-device and Cloud versions from the tier badge in the meeting detail view. -
Folder sizes. Meeting rows, the detail view, and multi-selection now show each meeting's
size on disk, and Settings totals your~/Kleothfootprint. -
Remove Transcription. Revert a meeting to its saved audio — the transcript, summary, and
any variants move to the Trash (recoverable) while the recording, title, and speaker names
stay, ready to re-transcribe. Available from the History context menu (multi-select works)
and the detail toolbar. -
Failed runs now explain themselves where you're looking. When a transcription or
summarization fails (e.g. an ElevenLabs payment/quota issue), the meeting keeps a visible
record of it: a dismissible error card in the meeting detail view and a red "Failed" chip on
its History row (hover for the message). Previously the error appeared only in the menu-bar
popover's status line, so from the History window a failed cloud transcription just silently
reverted to Untranscribed. Retrying, dismissing, or trashing the meeting clears it.
Changed
- Default summary model is now
z-ai/glm-5.3-flash(was the retired
google/gemini-3-flash-preview). The dictation polish model defaults to
google/gemini-3.5-flash-lite(median ~0.9 s per dictation in live measurements, vs 3–4 s on
GLM) and falls through toz-ai/glm-5.3-flashwhen the primary is unreachable. A stored
retired slug is migrated in memory on every launch and rewritten in the Keychain the first time
Settings opens. Why not Gemini: accounts whose OpenRouter privacy settings enforce Zero Data
Retention find everygoogle/*model blocked (404zdr-violation-by-account), while GLM works
under both the no-train and ZDR guardrails.google/gemini-3.8-flashremains selectable in the
picker — relax the guardrail at openrouter.ai/settings/privacy to use it. - Cloud transcription (Scribe) requests for dictation send
no_verbatim=true(fillers dropped
server-side); meeting transcription is unchanged. - Transcription after recording is now opt-in. Stopping a recording saves the audio and
lists it as Untranscribed; transcription starts only when you choose an engine on the
meeting (or turn on "Transcribe automatically after recording" in Settings). This applies to
existing installs too — flip the new toggle to restore the old always-transcribe behavior.
Fixed
- Playback now plays both sides in both ears. The built-in player live-downmixes the
2-channel meeting file (your mic on the left, the other side on the right), so you no longer
hear yourself only in the left ear. The file on disk keeps its channel layout. - Over-amplified microphone peaks are now clamped during loudness normalization, preventing
hard clipping in future recordings' combined audio. - Summaries are no longer silently truncated: completions cut off at the output
cap (finish_reason == "length") are retried with a larger budget and a
truncated result is surfaced as a failure rather than shipped half-empty. - The onboarding "Start your first recording" button no longer no-ops after
"Skip setup" — it routes to the permissions step so consent is acknowledged.
Removed
- Slack integration. The Slack webhook export is gone — the
kleoth slack
CLI subcommand, the Settings webhook field, the "Post to Slack" Shortcut /
App Intent, thekleoth://slack-latestURL verb, and the detail view's "Copy
for Slack" action (replaced by a Slack-free Copy Summary that copies the
rendered Markdown).
Install
Download the DMG, drag Kleoth to Applications, then right-click → Open on first launch (the build is self-signed, not notarized).
SHA-256: 46544836789b46c046d97ecb41d6b7cd5212f05eb83153b64ae49f1bc5b46e2e
Kleoth is a local-first, bot-free macOS meeting recorder: on-device transcription by default, optional cloud upgrades, and meeting files you own.
Highlights
- On-device transcription by default. Records your mic and the other participants' system audio locally and transcribes on the Apple Neural Engine via WhisperKit — free, offline, private. The ~600 MB model downloads once on first use, then nothing leaves your machine.
- Automatic multilingual transcription. The spoken language is auto-detected per meeting (English, Russian, and many more), and summaries are written in that same language. Pinnable in Settings.
- Exact "You vs. Them" separation. Mic and system audio are captured as separate channels, so speaker attribution is precise without diarization guesswork.
- Menu-bar app. A lightweight
MenuBarExtraagent: start/stop recording, browse history, read transcripts and summaries, and rename speakers — all from the menu bar. - Finder-like meeting management. Double-click to rename inline, ⌘/⇧-click to multi-select, delete to Trash (recoverable), and Show in Finder.
- First-run onboarding. A guided welcome flow (with a chime) walks you through naming yourself, granting mic and system-audio permissions, and downloading the model.
- Optional cloud transcription. A per-meeting one-click "Fully transcribe" sends a meeting to ElevenLabs Scribe (your own key) for SOTA accuracy, at 1× cost via a mono mixdown with channel-energy attribution.
- Optional AI summaries. TL;DR, overview, action items, and per-speaker highlights via OpenRouter (any model; default
google/gemini-3-flash-preview), using your own key. Titles are generated for untitled meetings. - Background processing. Stopping a recording returns instantly — the meeting moves into your list and transcribes in the background while you start the next. Jobs run one at a time.
- Files you own. Every meeting is a self-contained folder in
~/Kleothwith the audio,transcript.md,summary.md, and JSON. No database, no lock-in. kleothCLI.transcribe,summarize,rename,render, andslacksubcommands for the same pipeline outside the app.- Integrations. A Raycast extension, App Intents for Shortcuts, a
kleoth://URL scheme, a configurable global hotkey, optional calendar-based naming, and Slack export. - Settings & usage. Manage optional API keys (Keychain-stored), the summary model, and the transcription language; a Usage section reports provider-side credit/quota.
Requirements
macOS 14.4+, Apple Silicon recommended.
Install
- Download
Kleoth-0.1.0.dmgbelow. - Open it and drag Kleoth.app to Applications.
- First launch (important): this build is self-signed and not yet notarized, so Gatekeeper will block a normal double-click. Right-click Kleoth.app → Open, then confirm — just once. Notarized builds are planned.
SHA-256
6028cafbbdddccadc337eb6e482b2c5ddfb0bfea9fc16c7ffed3304ceaa81641 Kleoth-0.1.0.dmg
Known limitations
- Builds are self-signed, not yet notarized — first launch needs right-click → Open (Apple Developer Program enrollment pending).
- App Intents may not auto-surface in Spotlight/Shortcuts (SwiftPM doesn't run Apple's intents metadata extractor); the URL scheme and hotkey work regardless.
- No Whisper model-size picker yet; the default model is used.