short confirmation tones on daemon events so the user gets eyes-free "did it hear me?" feedback without watching the terminal. NOT TTS — short pre-generated .wav beeps. - audio_out.py — reusable audio-OUT module (the reverse of audio.py's capture, the less-tested WSLg direction). three-tier player: paplay-first (a SEPARATE process, so it doesn't contend with the sounddevice mic stream on the duplex-flaky WSLg bridge), then in-process sounddevice, then powershell.exe SoundPlayer. best-effort per-backend volume. plays a wav path and knows nothing about events — v0.3 TTS reuses it. - sound.py — Earcons: the single event->tone map (wake/accept/no_match/submit) gated by [sound] config (master enabled + per-event flags). daemon._handle wiring: an injected command plays accept (submit plays submit); no-match / target-missing / unknown-context plays no_match; pure daemon-control commands (list/version/…) play nothing. - sounds/ — committed earcon wavs + generate.py (regen-only). committed (not generated at install) so the package is self-contained and a missing tone can never appear. packaged via pyproject [tool.setuptools.package-data]. - [sound] config: enabled (master, on), on_wake (OFF by default — bleed/chatty), on_accept/on_no_match/on_submit (on), volume (0-1 best-effort), [sound.files] overrides. - claudedo test-tone — plays each tone, the audio-OUT gate (mirrors test-audio). - install.sh now also checks RDPSink (audio-out) alongside RDPSource. INVARIANT: earcons are fire-and-forget on a worker thread and NEVER block or break the inject path. a missing tone file or dead speaker logs once and is swallowed, never raised — a broken speaker must never stop "claudedo yes" from injecting. de-risks the WSLg audio-OUT path that v0.3 TTS-readback will reuse. Signed-off-by: disqualifier <dev@disqualifier.me>
186 lines
7.8 KiB
Bash
Executable File
186 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# claudedo bootstrap — does the system setup pip can't. idempotent: re-running is
|
|
# safe and won't duplicate the shell-rc cc kit. run from the repo root.
|
|
set -euo pipefail
|
|
|
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ASOUNDRC="$HOME/.asoundrc"
|
|
MARKER_BEGIN="# >>> claudedo cc kit >>>"
|
|
MARKER_END="# <<< claudedo cc kit <<<"
|
|
|
|
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
|
|
warn() { printf '\033[1;33m!! %s\033[0m\n' "$*" >&2; }
|
|
die() { printf '\033[1;31mxx %s\033[0m\n' "$*" >&2; exit 1; }
|
|
|
|
# 1. windows-side checks (cannot automate — check and instruct) -----------------
|
|
say "checking WSLg audio bridge"
|
|
if [ ! -e /mnt/wslg/PulseServer ]; then
|
|
die "WSLg PulseServer missing (/mnt/wslg/PulseServer). claudedo needs WSLg audio.
|
|
update WSL ('wsl --update' in Windows) or install WSL from the Microsoft Store,
|
|
then restart WSL ('wsl --shutdown') and re-run this script."
|
|
fi
|
|
echo " /mnt/wslg/PulseServer present"
|
|
|
|
cat <<'EOF'
|
|
|
|
MANUAL WINDOWS STEP (this script cannot do it for you):
|
|
Windows Settings -> Privacy & security -> Microphone ->
|
|
enable "Let desktop apps access your microphone".
|
|
Without this, the mic is silent inside WSL. Do it now if you haven't.
|
|
|
|
EOF
|
|
|
|
# 2. WSL audio deps (apt) -------------------------------------------------------
|
|
say "installing WSL audio dependencies (apt)"
|
|
sudo apt-get update
|
|
sudo apt-get install -y libportaudio2 libasound2t64 libasound2-plugins \
|
|
alsa-utils pulseaudio-utils
|
|
|
|
# 3. ALSA -> Pulse routing ------------------------------------------------------
|
|
say "configuring ALSA -> Pulse routing (~/.asoundrc)"
|
|
if [ -f "$ASOUNDRC" ] && grep -q "type pulse" "$ASOUNDRC"; then
|
|
echo " ~/.asoundrc already routes to pulse"
|
|
else
|
|
{
|
|
echo "pcm.!default { type pulse }"
|
|
echo "ctl.!default { type pulse }"
|
|
} >> "$ASOUNDRC"
|
|
echo " wrote pulse default to ~/.asoundrc"
|
|
fi
|
|
if [ -z "${PULSE_SERVER:-}" ] && [ -e /mnt/wslg/PulseServer ]; then
|
|
export PULSE_SERVER="unix:/mnt/wslg/PulseServer"
|
|
echo " exported PULSE_SERVER=$PULSE_SERVER (WSLg usually sets this already)"
|
|
fi
|
|
|
|
# 4. verify audio (fail loudly with guidance) -----------------------------------
|
|
say "verifying audio path"
|
|
if pactl info >/dev/null 2>&1; then
|
|
DEFAULT_SRC="$(pactl info | sed -n 's/^Default Source: //p')"
|
|
echo " Default Source: ${DEFAULT_SRC:-<none>}"
|
|
DEFAULT_SINK="$(pactl info | sed -n 's/^Default Sink: //p')"
|
|
echo " Default Sink: ${DEFAULT_SINK:-<none>}"
|
|
if ! pactl list sources short 2>/dev/null | grep -q RDPSource; then
|
|
warn "RDPSource not listed by pactl — mic may not be bridged. check Windows mic permission."
|
|
fi
|
|
if ! pactl list sinks short 2>/dev/null | grep -q RDPSink; then
|
|
warn "RDPSink not listed by pactl — earcon/TTS audio-OUT may not play. run 'claudedo test-tone' to check."
|
|
fi
|
|
else
|
|
warn "pactl info failed — pulseaudio-utils installed but no server reachable yet."
|
|
fi
|
|
|
|
TESTWAV="/tmp/claudedo_test.wav"
|
|
if arecord -D default -f S16_LE -c 1 -r 16000 -d 2 "$TESTWAV" >/dev/null 2>&1 && [ -s "$TESTWAV" ]; then
|
|
echo " arecord captured 2s -> $TESTWAV ($(stat -c%s "$TESTWAV") bytes)"
|
|
else
|
|
warn "arecord could not capture. fix-chain: apt deps above + ~/.asoundrc + Windows mic permission.
|
|
debug anytime with: claudedo test-audio"
|
|
fi
|
|
|
|
# 5. python install + model prime -----------------------------------------------
|
|
say "installing the claudedo python package"
|
|
PIP="${PIP:-pip3}"
|
|
"$PIP" install -e "$REPO_DIR"
|
|
|
|
say "priming the faster-whisper model (so first run isn't slow)"
|
|
MODEL="$(sed -n 's/^model *= *"\(.*\)".*/\1/p' "$REPO_DIR/config.toml" | head -1)"
|
|
MODEL="${MODEL:-small}"
|
|
python3 - "$MODEL" <<'PY' || warn "model prime failed — first run will download it"
|
|
import sys
|
|
from faster_whisper import WhisperModel
|
|
WhisperModel(sys.argv[1], device="cpu", compute_type="int8")
|
|
print(" primed faster-whisper model:", sys.argv[1])
|
|
PY
|
|
|
|
# 6. cc kit as a sourced file + rc wiring (idempotent) --------------------------
|
|
say "installing the cc kit (~/.config/claudedo/cc.sh)"
|
|
CONF_DIR="$HOME/.config/claudedo"
|
|
mkdir -p "$CONF_DIR"
|
|
install -m 0644 "$REPO_DIR/shell/cc.sh" "$CONF_DIR/cc.sh"
|
|
echo " wrote $CONF_DIR/cc.sh"
|
|
|
|
# install config.toml to the standard location so the daemon finds it from any dir.
|
|
# never clobber an edited user config: copy only if absent, else drop a .new to diff.
|
|
if [ ! -f "$CONF_DIR/config.toml" ]; then
|
|
install -m 0644 "$REPO_DIR/config.toml" "$CONF_DIR/config.toml"
|
|
echo " wrote $CONF_DIR/config.toml"
|
|
elif ! cmp -s "$REPO_DIR/config.toml" "$CONF_DIR/config.toml"; then
|
|
install -m 0644 "$REPO_DIR/config.toml" "$CONF_DIR/config.toml.new"
|
|
echo " kept your $CONF_DIR/config.toml; new default written to config.toml.new (diff to merge)"
|
|
else
|
|
echo " $CONF_DIR/config.toml already current"
|
|
fi
|
|
|
|
# install the contexts.toml template (named blurbs for the `context` voice command).
|
|
# same policy: copy only if absent, else drop a .new — never clobber edited contexts.
|
|
if [ ! -f "$CONF_DIR/contexts.toml" ]; then
|
|
install -m 0644 "$REPO_DIR/contexts.toml" "$CONF_DIR/contexts.toml"
|
|
echo " wrote $CONF_DIR/contexts.toml"
|
|
elif ! cmp -s "$REPO_DIR/contexts.toml" "$CONF_DIR/contexts.toml"; then
|
|
install -m 0644 "$REPO_DIR/contexts.toml" "$CONF_DIR/contexts.toml.new"
|
|
echo " kept your $CONF_DIR/contexts.toml; new default written to contexts.toml.new (diff to merge)"
|
|
else
|
|
echo " $CONF_DIR/contexts.toml already current"
|
|
fi
|
|
|
|
# wire EVERY rc that exists (the user may have both zsh and bash).
|
|
wired_any=0
|
|
for RC in "$HOME/.zshrc" "$HOME/.bashrc"; do
|
|
[ -f "$RC" ] || continue
|
|
wired_any=1
|
|
if grep -qF "$MARKER_BEGIN" "$RC"; then
|
|
echo " cc kit marker already in $RC (not duplicating)"
|
|
continue
|
|
fi
|
|
cp "$RC" "$RC.claudedo.bak"
|
|
echo " backed up $RC -> $RC.claudedo.bak"
|
|
cat >> "$RC" <<'CCKIT'
|
|
|
|
# >>> claudedo cc kit >>>
|
|
[ -f ~/.config/claudedo/cc.sh ] && source ~/.config/claudedo/cc.sh
|
|
# <<< claudedo cc kit <<<
|
|
CCKIT
|
|
echo " wired source-line block into $RC (open a new shell or 'source $RC')"
|
|
done
|
|
[ "$wired_any" = 1 ] || warn "no ~/.zshrc or ~/.bashrc found — add the marker block from README.md manually."
|
|
|
|
# warn about any OLD loose cc defs outside our markers (do not auto-delete).
|
|
for RC in "$HOME/.zshrc" "$HOME/.bashrc"; do
|
|
[ -f "$RC" ] || continue
|
|
loose="$(grep -nE '^[[:space:]]*(cc|ccr|ccl|cck|cckl|_cc_name)[[:space:]]*\(\)' "$RC" \
|
|
| grep -v 'claudedo' || true)"
|
|
if [ -n "$loose" ]; then
|
|
warn "old cc-function defs found in $RC (outside the claudedo markers):"
|
|
echo "$loose" | sed 's/^/ /'
|
|
echo " review and remove them by hand — the new sourced kit overrides them, but"
|
|
echo " they are dead code. a backup is at $RC.claudedo.bak"
|
|
fi
|
|
done
|
|
|
|
# 7. tmux settings for reliable send-keys (idempotent ~/.tmux.conf append) -------
|
|
say "configuring tmux for reliable send-keys (~/.tmux.conf)"
|
|
TMUX_CONF="$HOME/.tmux.conf"
|
|
TMUX_MARKER="# >>> claudedo tmux >>>"
|
|
touch "$TMUX_CONF"
|
|
if grep -qF "$TMUX_MARKER" "$TMUX_CONF"; then
|
|
echo " claudedo tmux block already present (not duplicating)"
|
|
else
|
|
cat >> "$TMUX_CONF" <<'TMUXCONF'
|
|
|
|
# >>> claudedo tmux >>>
|
|
# settings for reliable keystroke injection + notifications (do not edit inside the
|
|
# markers; re-run install.sh to refresh). escape-time 0 stops injected Escape from
|
|
# being misread; allow-passthrough + extended-keys let notifications and modified
|
|
# keys (Shift+Enter) reach the claude pane; the larger history-limit keeps scrollback.
|
|
set -g escape-time 0
|
|
set -g history-limit 50000
|
|
set -g allow-passthrough on
|
|
set -s extended-keys on
|
|
set -as terminal-features 'xterm*:extkeys'
|
|
# <<< claudedo tmux <<<
|
|
TMUXCONF
|
|
echo " appended claudedo tmux settings to $TMUX_CONF (reload: tmux source-file ~/.tmux.conf)"
|
|
fi
|
|
|
|
say "done. next: 'claudedo test-audio' then 'claudedo start'"
|