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 <dev@disqualifier.me>
This commit is contained in:
parent
7780a8d47c
commit
bf516143b5
147
install.sh
Executable file
147
install.sh
Executable file
@ -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:-<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
|
||||
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'"
|
||||
18
shell/autostart.sh
Normal file
18
shell/autostart.sh
Normal file
@ -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
|
||||
67
shell/cc.sh
Normal file
67
shell/cc.sh
Normal file
@ -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-<name>", 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 <name> start or reattach to claude-<name>; writes ~/.claude-active
|
||||
# ccr <name> reattach only (error if it doesn't exist); writes ~/.claude-active
|
||||
# ccl list running claude- sessions
|
||||
# cck <name> kill claude-<name>
|
||||
# cckl kill ALL claude- sessions
|
||||
|
||||
cc() {
|
||||
if [ -z "$1" ]; then
|
||||
echo "usage: cc <project-name>" >&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 <project-name>" >&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 <project-name>" >&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
|
||||
}
|
||||
14
shell/claudedo.service
Normal file
14
shell/claudedo.service
Normal file
@ -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
|
||||
Loading…
Reference in New Issue
Block a user