Let a dictation be a command for Claude Code, not only text

Dictation ends the same way every time: the words you said land in the window
you were in. But half of what you say to a machine is not text to place, it is
something to do, and the answer to "what is on my calendar today" is not that
sentence written out.

`claude -p` is that session run without a window: the same skills, the same
connected services, the same account. So a second shortcut records exactly as
the first one does and sends the transcript there instead, and what comes back
is pasted where the transcript would have been. Which is what puts "save that
to my calendar on Thursday at three" inside a text field that has nothing to do
with Claude.

Permissions are the part worth being deliberate about, because nothing here can
answer a prompt: a mode that would have asked denies instead. It runs in `auto`,
which decides for itself with the prompt-injection checks left on, and the other
two modes are one setting away. What Claude was not allowed to touch is reported
rather than swallowed, since a reply that reads perfectly normal is otherwise
the only sign that the job did not happen.

Commands close together stay in one conversation, so "and move that to Thursday"
knows what "that" is; half an hour of silence ends it, as does the tray. The
answer is written to be read where it lands: no headings, no lists, and a
sentence naming what was done when something was done.

Its output is read as it streams rather than waited out, so the corner names the
tool it is on. A calendar lookup took 29 s in testing, which a still indicator
would have made indistinguishable from a hang. The clock and the stop button are
watched from a thread of their own, because the stream blocks between lines and
a model that thinks for a minute sends none: they end the run by killing the
process, which closes the stream and unwinds everything else on its own.

Transcript cleanup is off on this path. Claude reads through "erm" and "hani"
without help, and skipping it saves an API call and a second or two in front of
a screen you are standing at.
This commit is contained in:
yusufipk
2026-07-28 16:57:10 +07:00
parent 352f7b6c22
commit 0206303751
10 changed files with 908 additions and 37 deletions
+10
View File
@@ -46,6 +46,7 @@ set next to it.
| --- | --- |
| Start / stop recording | `Ctrl+Space`, or click the tray icon |
| Cancel a recording | Tray menu → *Cancel recording*, or `dikte cancel` |
| Speak a command to Claude | Tray menu → *Ask Claude*, or `dikte ask` |
| Start / end a meeting | Tray menu → *Record a meeting*, or `dikte meeting` |
| Settings | Tray menu → *Settings*, or `dikte settings` |
| Reload after an update | Tray menu → *Restart*, or `dikte restart` |
@@ -81,6 +82,14 @@ elapsed time, then the stage it is on. It never takes focus. Pressing
- **A failed cleanup is never silent.** The raw transcript is still pasted so the
dictation is not lost, but the indicator turns amber with the reason instead of
looking like a normal run.
- **A dictation can be a command instead.** Its own shortcut sends the
transcript to Claude Code (`claude -p`) rather than pasting it, and pastes back
what comes of it: the answer, or a sentence saying what was done. It is the
session you would have opened yourself, so your skills and connected services
are there, which is what makes "put that in my calendar on Thursday at three"
a thing you can say to a window that is not Claude. Model, permissions and
working directory are under Settings → Claude, and commands close together
stay in one conversation.
- **Meetings** are recorded from the microphone and the speaker output at the
same time, which settles who said what by the channel a voice arrived on
instead of guessing at it. The two sides are transcribed separately and
@@ -112,6 +121,7 @@ needs your user in the `input` group: `sudo usermod -aG input $USER`.
dikte.py entry point, tray icon, state machine, IPC
audio.py PCM capture: pw-record for dictation, ffmpeg for a meeting
meeting.py channel split, speaker labelling, cleanup, minutes
assistant.py running a dictation through Claude Code and reading it back
api.py transcription on either provider, OpenRouter cleanup (stdlib only)
worker.py transcribe → clean up → clipboard → paste
vad.py deciding whether a recording holds speech at all