From bf516143b5e1de14517ab381cfaa60b28f8b58a5 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Thu, 25 Jun 2026 17:55:30 -0400 Subject: [PATCH] install: shell cc kit, opt-in autostart, bootstrap 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 --- install.sh | 147 +++++++++++++++++++++++++++++++++++++++++ shell/autostart.sh | 18 +++++ shell/cc.sh | 67 +++++++++++++++++++ shell/claudedo.service | 14 ++++ 4 files changed, 246 insertions(+) create mode 100755 install.sh create mode 100644 shell/autostart.sh create mode 100644 shell/cc.sh create mode 100644 shell/claudedo.service diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..a3f86db --- /dev/null +++ b/install.sh @@ -0,0 +1,147 @@ +#!/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:-}" + 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 +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" +install -m 0644 "$REPO_DIR/shell/autostart.sh" "$CONF_DIR/autostart.sh" +echo " wrote $CONF_DIR/cc.sh and autostart.sh" + +# 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 >>> +# voice-daemon autostart is OPT-IN: uncomment the next line to enable it. +# export CLAUDEDO_AUTOSTART=1 +[ -f ~/.config/claudedo/autostart.sh ] && source ~/.config/claudedo/autostart.sh +[ -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. optional systemd user service (only if systemd-in-WSL is available) --------- +if [ -d /run/systemd/system ] && systemctl --user show-environment >/dev/null 2>&1; then + say "systemd user instance detected — installing optional claudedo.service (NOT enabled)" + mkdir -p "$HOME/.config/systemd/user" + install -m 0644 "$REPO_DIR/shell/claudedo.service" "$HOME/.config/systemd/user/claudedo.service" + systemctl --user daemon-reload 2>/dev/null || true + echo " enable it with: systemctl --user enable --now claudedo" + echo " (or use the rc-based autostart instead — CLAUDEDO_AUTOSTART=1)" +else + echo " (no systemd user instance — using rc-based autostart; that's normal on WSL)" +fi + +say "done. next: 'claudedo test-audio' then 'claudedo start'" diff --git a/shell/autostart.sh b/shell/autostart.sh new file mode 100644 index 0000000..c416fc6 --- /dev/null +++ b/shell/autostart.sh @@ -0,0 +1,18 @@ +# claudedo autostart (OPT-IN). starts the voice daemon once per WSL session in its +# own tmux session, if not already running. WSL has no real boot and usually no +# systemd, so this rc-based guard matches WSL's "starts when you open a terminal" +# model. POSIX; safe to source under bash and zsh. +# +# this only acts when CLAUDEDO_AUTOSTART=1 is set (the rc marker block gates on it), +# so sourcing it alone does nothing. to enable: export CLAUDEDO_AUTOSTART=1 before +# the cc-kit marker block in your rc. to disable: unset it (or remove this file). +# +# the daemon runs detached; watch its logs with: tmux attach -t claudedo-daemon + +if [ "${CLAUDEDO_AUTOSTART:-0}" = "1" ]; then + if command -v claudedo >/dev/null 2>&1; then + if ! tmux has-session -t claudedo-daemon 2>/dev/null; then + tmux new-session -d -s claudedo-daemon "claudedo start" + fi + fi +fi diff --git a/shell/cc.sh b/shell/cc.sh new file mode 100644 index 0000000..29a94f9 --- /dev/null +++ b/shell/cc.sh @@ -0,0 +1,67 @@ +# claudedo cc kit — claude-code-in-tmux session helpers. +# POSIX sh; sources cleanly under bash and zsh. side-effect-free on source +# (function definitions only — nothing runs at source time). +# +# every command REQUIRES an explicit project name. the session is always +# "claude-", a stable speakable handle: "cc libs" -> claude-libs, which the +# voice daemon targets with "claudedo target libs" / "switch libs". the name->session +# mapping here MUST match target.py's session_name() in the daemon. +# +# cc start or reattach to claude-; writes ~/.claude-active +# ccr reattach only (error if it doesn't exist); writes ~/.claude-active +# ccl list running claude- sessions +# cck kill claude- +# cckl kill ALL claude- sessions + +cc() { + if [ -z "$1" ]; then + echo "usage: cc " >&2 + return 1 + fi + session="claude-$1" + echo "$session" > "$HOME/.claude-active" + if tmux has-session -t "$session" 2>/dev/null; then + tmux attach -t "$session" + else + tmux new-session -s "$session" "claude" + fi +} + +ccr() { + if [ -z "$1" ]; then + echo "usage: ccr " >&2 + return 1 + fi + session="claude-$1" + if tmux has-session -t "$session" 2>/dev/null; then + echo "$session" > "$HOME/.claude-active" + tmux attach -t "$session" + else + echo "no session '$session' — run 'cc $1' to start one" >&2 + return 1 + fi +} + +ccl() { + tmux ls 2>/dev/null | grep '^claude-' || echo "no claude sessions running" +} + +cck() { + if [ -z "$1" ]; then + echo "usage: cck " >&2 + return 1 + fi + session="claude-$1" + if tmux kill-session -t "$session" 2>/dev/null; then + echo "killed $session" + else + echo "no session '$session'" >&2 + return 1 + fi +} + +cckl() { + tmux ls 2>/dev/null | grep '^claude-' | cut -d: -f1 | while read -r s; do + tmux kill-session -t "$s" && echo "killed $s" + done +} diff --git a/shell/claudedo.service b/shell/claudedo.service new file mode 100644 index 0000000..f02affe --- /dev/null +++ b/shell/claudedo.service @@ -0,0 +1,14 @@ +[Unit] +Description=claudedo voice-control daemon for claude code +Documentation=https://github.com/dsql/claudedo +After=default.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/claudedo start +Restart=on-failure +RestartSec=3 +Environment=PULSE_SERVER=unix:/mnt/wslg/PulseServer + +[Install] +WantedBy=default.target