'<wake> debug <text>' (alias echo) echoes what you said to the console as
[VOICE] debug: "..." and injects nothing — a no-target test command for checking
wake + STT transcription. added to the STT vocab so it's biased for.
Signed-off-by: disqualifier <dev@disqualifier.me>
the daemon's config lookup falls through to ./config.toml only, so without a copy in
the standard dir it was repo-cwd-only. install config.toml to ~/.config/claudedo/ —
copy if absent, else write config.toml.new beside the user's edited copy (never
clobber). also gitignore COMPACT.md (handoff doc kept on disk, untracked).
Signed-off-by: disqualifier <dev@disqualifier.me>
default stt.model -> medium (biggest accuracy gain for the coined wake word;
small/large-v3 documented alternatives). seed faster-whisper with an initial_prompt
derived from the configured wake phrases + command vocabulary (grammar.vocabulary /
initial_prompt, one source — command synonyms now live in named _*_VERBS tuples).
split the single fuzzy threshold into wake_fuzzy_threshold (0.6, lenient — a false
wake is cheap) and command_fuzzy_threshold (0.8, tight — a false command fires the
wrong action); grammar.parse() takes both. add a [vad] config section (silence_ms,
max_seconds) for the existing Alexa-style record-until-pause endpointing, which
captures a command whole and lets the trailing pause separate it from following
chatter (that chatter is a separate capture the wake gate discards). bump to 0.1.3.
Signed-off-by: disqualifier <dev@disqualifier.me>
map 'add a space'/'add space'/'insert two spaces' to the space command (count read
from either side of the noun). remove 'claude due' from the default wake list (it
double-rendered with 'claude do' and wasn't wanted). docs synced.
Signed-off-by: disqualifier <dev@disqualifier.me>
reconcile README with the shipped code and with CLAUDE.md: full 6-phrase wake list
(claudedo/claude do/claude due/hey claude/ok claude/okay claude) with the Whisper
rationale; space/backspace/erase in the grammar + flow; colored prefixed console
output description; fix the auto_target contradiction (default false = require
set/target, not auto-pick); drop the stale 'backgroundable'.
Signed-off-by: disqualifier <dev@disqualifier.me>
voice editing: 'space [<n>]' inserts spaces, 'backspace [<n>]' (alias delete)
deletes chars, 'erase' (alias clear/wipe) wipes the current input. the daemon
tracks a per-session uncommitted-input char count so backspace is capped at the
last submit boundary and erase clears exactly back to it; submit/set reset it.
keys.py gains BSpace/space; grammar gains a count parser (digits + number words).
new console.py renders every daemon line as 'HH:MM:SS [prefix] message' with
color: [<session>] for injected lines (green), [SYSTEM] for state, [VOICE] for
recognition/drops (red/dim). bump to 0.1.2.
Signed-off-by: disqualifier <dev@disqualifier.me>
add behavior.auto_target (default false): with no sticky target and exactly one
session running, false requires an explicit set/target rather than guessing; true
auto-uses it. target.resolve() takes the flag. add behavior.print_heard (default
false, debug): opt-in console echo of non-wake transcripts to see how Whisper
renders the wake word. add behavior.filler_words. expand the wake list with the
spellings Whisper actually emits for the coined word ('claude do', 'claude due',
'ok claude', 'okay claude').
Signed-off-by: disqualifier <dev@disqualifier.me>
faster-whisper's VAD loads an onnx model that prints a 'GPU device discovery
failed' warning on headless/WSL hosts and chatty INFO per transcribe. raise onnx
log severity, drop the faster_whisper logger to WARNING, and filter the C++-level
discovery line out of stderr during model load + a one-shot warm transcribe (so it
fires once at startup, not in the hot loop). real errors still pass through.
Signed-off-by: disqualifier <dev@disqualifier.me>
redefine targeting: 'set' (aliases sticky/switch) is the persistent sticky
default (~/.claude-active); 'target <name> <command>' is a one-shot override that
routes a single command without changing the sticky default. add 'unset' and
'list'. resolution moves to a single target.resolve(one_shot) implementing the
order: one-shot -> sticky-if-exists -> only-session auto -> ambiguous/none do
nothing (never falls through, never injects into a missing session).
grammar.parse now returns ParsedCommand(one_shot, action) and skips optional
leading filler words (config behavior.filler_words: select/use/choose), with a
filler-before-digit still meaning the select command. CLI gains set/unset/list
(switch kept as a set alias). daemon console shows the targeting reason per line.
docs updated; no stale 'target = sticky' wording remains. bump to 0.1.1.
Signed-off-by: disqualifier <dev@disqualifier.me>
terminal-run is the product, so remove all backgrounding: delete the
claudedo.service unit and autostart.sh, strip the systemd step and the
autostart source-line from install.sh (rc block now sources cc.sh only).
claudedo start now runs a mic check first (warm-up + brief capture, aborts with
guidance if silent; --skip-audio-check to bypass) then drops into a visible
listen loop printing the recognition/action log: a startup banner, then
heard -> matched -> target / injected per utterance, target/mode state changes,
and (listen mode) non-wake speech dropped WITHOUT the transcript per the privacy
invariant.
Signed-off-by: disqualifier <dev@disqualifier.me>
WSLg's RDPSource suspends when idle and emits ~1-2s of silence while it resumes
on the first read, so a short timed capture (test-audio) or the first utterance
after daemon start could be lost. add audio.warm_up() that opens a stream and
reads until a non-silent block arrives (or times out); call it at daemon startup
and before test-audio's capture. test-audio now primes then captures 3s.
Signed-off-by: disqualifier <dev@disqualifier.me>
extract an OutputHandler abstract base; TmuxOutputHandler is production
(send-keys, PTY-only), StdoutOutputHandler prints what would be injected so
grammar+keymap run end-to-end without a live claude session (the deterministic
test path). module-level shims default to tmux so the daemon is unchanged.
Signed-off-by: disqualifier <dev@disqualifier.me>
append escape-time 0, large history-limit, allow-passthrough, and extended-keys
to ~/.tmux.conf under an idempotent marker block (no clobber). required for
reliable keystroke injection and for notifications/modified-keys to reach the
claude pane.
Signed-off-by: disqualifier <dev@disqualifier.me>
state terminal-run as the product (the claudedo start terminal is the
recognition/action console) and frame backgrounding/autostart/systemd as
optional extras, not the default.
Signed-off-by: disqualifier <dev@disqualifier.me>
remove inline comments (CLAUDE.md: docstrings only), strip trailing periods
from single-line docstrings, and fix a PulseArmy->PulseAudio typo. no behavior
change.
Signed-off-by: disqualifier <dev@disqualifier.me>
cc kit as a sourced ~/.config/claudedo/cc.sh (bash+zsh, forced explicit names).
opt-in rc autostart guarded by CLAUDEDO_AUTOSTART + an optional systemd user
unit. install.sh is idempotent: WSL audio deps, ~/.asoundrc pulse shim, audio
verify, model prime, and source-line rc wiring with backups.
Signed-off-by: disqualifier <dev@disqualifier.me>
daemon.py runs the loop with pidfile/state, runtime mode switching, and the
privacy invariant: in listen mode any non-wake utterance is dropped the instant
grammar.parse() returns None. __main__.py exposes start|stop|status|test-audio|
install|switch.
Signed-off-by: disqualifier <dev@disqualifier.me>
word-boundary wake stripping that's lenient on the coined word 'claudedo'
(despaced-prefix match) without swallowing the command's spaces. data-driven
phrase->action map; number words normalized to digits; 'target' aliases
'switch'.
Signed-off-by: disqualifier <dev@disqualifier.me>
stt.py wraps faster-whisper for fully on-device transcription. audio.py
captures via sounddevice with two paths: silence-segmented for listen mode
and held-key for ptt. resolves the input device from config (auto/index/name).
Signed-off-by: disqualifier <dev@disqualifier.me>
keys.py is the single source of truth for claude code's prompt keystrokes,
confirmed empirically against a live v2.1.191 session: bare digits select-
and-confirm immediately (no trailing enter). inject.py only ever calls
tmux send-keys (PTY injection, never OS input). target.py resolves the
active session from ~/.claude-active with a has-session guard and a single
session_name() mapping (claude-<name>) shared with the cc kit.
Signed-off-by: disqualifier <dev@disqualifier.me>
load/validate config.toml with clear errors; defaults to listen mode and
the 'small' whisper model. all tunables (wake phrases, audio thresholds,
type_autosend) live here, no hardcoded paths or secrets in code.
Signed-off-by: disqualifier <dev@disqualifier.me>