Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .justfiles/dev.just
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Development recipes

[private]
default:
@just --list --unsorted --justfile {{source_file()}}

# Run samantha with arbitrary args
[no-cd]
run *ARGS:
uv run samantha {{ARGS}}

# Start Python REPL with project imports
[no-cd]
repl:
uv run python -c "from samantha import config, voice; import code; code.interact(local=locals())"

# Run with verbose output for debugging
[no-cd]
debug *ARGS:
uv run python -m samantha.cli {{ARGS}}
58 changes: 58 additions & 0 deletions .justfiles/install.just
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Installation recipes

package_name := "samantha-cli"

[private]
default:
@just --list --unsorted --justfile {{source_file()}}

# Install for development (editable)
[no-cd]
dev:
uv sync

# Install with all optional providers
[no-cd]
all:
uv sync --all-extras

# Install whisper extra (local STT)
[no-cd]
whisper:
uv pip install -e ".[whisper]"

# Install fish extra (Fish Audio TTS)
[no-cd]
fish:
uv pip install -e ".[fish]"

# Install as global tool via uv
[no-cd]
global:
uv tool install -e .
@echo "samantha installed globally"

# Uninstall global tool
[no-cd]
global-uninstall:
uv tool uninstall {{package_name}}

# Build distribution packages
[no-cd]
build:
uv build
@echo "Built packages in dist/"
@ls -la dist/

# Check system prerequisites
[no-cd]
check:
#!/usr/bin/env bash
echo "Checking prerequisites..."
echo ""
which claude > /dev/null 2>&1 && echo "claude CLI: OK" || echo "claude CLI: MISSING (install from https://claude.ai/download)"
which python3 > /dev/null 2>&1 && echo "Python: $(python3 --version)" || echo "Python: MISSING"
brew list portaudio > /dev/null 2>&1 && echo "portaudio: OK" || echo "portaudio: MISSING (brew install portaudio)"
uv run python -c "import edge_tts" 2>/dev/null && echo "edge-tts: OK" || echo "edge-tts: MISSING (run: just deps)"
uv run python -c "import faster_whisper" 2>/dev/null && echo "faster-whisper: OK" || echo "faster-whisper: NOT INSTALLED (optional: just install whisper)"
uv run python -c "import fishaudio" 2>/dev/null && echo "fish-audio-sdk: OK" || echo "fish-audio-sdk: NOT INSTALLED (optional: just install fish)"
87 changes: 87 additions & 0 deletions .justfiles/voice.just
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Voice & audio setup recipes

[private]
default:
@just --list --unsorted --justfile {{source_file()}}

# Test mic and speaker with current providers
[no-cd]
test:
uv run samantha test

# List available TTS voices (filtered to en-US female by default)
[no-cd]
voices:
uv run samantha voices --locale en-US --gender female

# List all available TTS voices
[no-cd]
voices-all:
uv run samantha voices

# List voices filtered by locale and gender
[no-cd]
voices-filter LOCALE GENDER:
uv run samantha voices --locale {{LOCALE}} --gender {{GENDER}}

# Show available TTS/STT providers and status
[no-cd]
providers:
uv run samantha providers

# Set TTS voice
[no-cd]
set-voice VOICE:
uv run samantha config tts_voice {{VOICE}}

# Set speech speed (0.5 = slow, 1.0 = normal, 1.5 = fast)
[no-cd]
set-speed SPEED:
uv run samantha config speech_speed {{SPEED}}

# Switch to Kokoro (local, high quality, default)
[no-cd]
use-kokoro:
uv run samantha config tts_provider kokoro
uv run samantha config tts_voice af_heart
@echo "TTS switched to Kokoro (local, no API key)"

# Switch to edge-tts (free cloud)
[no-cd]
use-edge:
uv pip install -e ".[edge]"
uv run samantha config tts_provider edge
uv run samantha config tts_voice en-US-AriaNeural
@echo "TTS switched to edge-tts (free cloud)"

# Switch to Fish Audio (requires API key)
[no-cd]
use-fish KEY:
uv pip install -e ".[fish]"
uv run samantha config tts_provider fish
uv run samantha config fish_api_key {{KEY}}
@echo "TTS switched to Fish Audio"

# Switch STT to local whisper
[no-cd]
use-whisper MODEL="base":
uv pip install -e ".[whisper]"
uv run samantha config stt_provider whisper
uv run samantha config whisper_model {{MODEL}}
@echo "STT switched to whisper (model: {{MODEL}})"

# Switch STT back to Google (free, cloud)
[no-cd]
use-google-stt:
uv run samantha config stt_provider google
@echo "STT switched to Google (free, requires internet)"

# Go fully local (kokoro + whisper)
[no-cd]
go-local MODEL="base":
uv pip install -e ".[whisper]"
uv run samantha config tts_provider kokoro
uv run samantha config tts_voice af_heart
uv run samantha config stt_provider whisper
uv run samantha config whisper_model {{MODEL}}
@echo "Fully local mode: kokoro + whisper (model: {{MODEL}})"
22 changes: 13 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
A voice wrapper around Claude Code. You speak, Claude thinks, Samantha's voice responds. Same Claude brain, same tools, same Max subscription - just with a voice and personality inspired by the AI from Her (2013).

## API Keys Needed
- **Fish Audio API key** — for Samantha's voice (TTS). Get one at https://fish.audio/app/api-keys
- **NO API keys needed by default** — STT (Whisper) and TTS (Kokoro) both run locally
- **Optional: Fish Audio API key** — only if you switch to Fish Audio TTS for a custom voice clone. Get one at https://fish.audio/app/api-keys
- **NO Anthropic API key needed** — this uses `claude -p` (Claude CLI) which runs on your Claude Max/Pro subscription. Zero API cost.

## First-Time Setup
Expand All @@ -14,35 +15,38 @@ If this is the user's first time, walk them through setup step by step:
2. **Check Python**: Need Python 3.10+. Run `python3 --version`.
3. **Install portaudio** (needed for mic): `brew install portaudio` (Mac) or `apt install portaudio19-dev` (Linux)
4. **Install the package**: `pip install -e .`
5. **Set Fish Audio key**: `export FISH_API_KEY=your_key` or run `samantha config`
6. **Test it**: Run `samantha --text` first to verify Claude + TTS works without needing a mic
7. **Go voice**: Run `samantha` for full voice mode
5. **Test it**: Run `samantha --text` first to verify Claude + TTS works without needing a mic
6. **Go voice**: Run `samantha` for full voice mode

## How to Check if Already Set Up
- Run `samantha config` — shows current config. If `fish_api_key` is set → ready to go
- Run `samantha config` — shows current config
- Config file lives at `~/.samantha/config.yaml`
- If `claude` CLI is not installed → tell them to install Claude Code first
- **They do NOT need an Anthropic API key** — `claude -p` uses their existing Claude subscription

## How It Works
```
Your voice → SpeechRecognition (Google free STT) → claude -p (Claude Max) → Fish Audio TTS → Samantha's voice
Your voice → Whisper (local STT) → claude -p (Claude Max) → Kokoro TTS (local) → Samantha's voice
```

- `samantha/cli.py` — Main entry point, Click CLI
- `samantha/voice.py` — Mic capture (SpeechRecognition) + TTS (Fish Audio with Samantha voice model)
- `samantha/voice.py` — Mic capture + TTS playback (provider-agnostic)
- `samantha/brain.py` — Wraps `claude -p` subprocess, builds prompts with personality
- `samantha/personality.py` — The Samantha persona prompt
- `samantha/config.py` — Manages `~/.samantha/config.yaml`
- `samantha/ui.py` — Rich terminal display with colored status indicators
- `samantha/stt/` — STT providers: Whisper (default, local), Google (free cloud)
- `samantha/tts/` — TTS providers: Kokoro (default, local), Edge (free cloud), Fish Audio (paid cloud)

## Key Details
- Voice model: Fish Audio `474887f7949b4d1ab3e626cddf82613a` (OS1 Samantha / Scarlett Johansson from Her)
- Default STT: Whisper (local, runs on device, no API key needed)
- Default TTS: Kokoro (local ONNX model, voice `af_heart`, no API key needed)
- Optional TTS: Fish Audio with voice model `474887f7949b4d1ab3e626cddf82613a` (paid, ~$1.25/hr of speech)
- Speech speed: 0.95x (configurable)
- Claude is called via `claude -p` with stdin, uses the user's Max subscription (zero API cost)
- Conversation history maintained in memory (last 10 exchanges)
- Phrase time limit: 30 seconds (configurable)
- All processing is local except: Google STT (free), Fish Audio TTS (paid, ~$1.25/hr of speech)
- All processing runs locally by default — no cloud APIs needed beyond Claude itself

## Commands
- `samantha` — Full voice mode (speak + hear)
Expand Down
54 changes: 54 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env just --justfile
# Samantha CLI - Give Claude a voice

set dotenv-load := true

# Modules
[doc('Voice & audio setup')]
mod voice '.justfiles/voice.just'

[doc('Development tasks')]
mod dev '.justfiles/dev.just'

[doc('Installation options')]
mod install '.justfiles/install.just'

[private]
default:
#!/usr/bin/env bash
echo "Samantha CLI - Give Claude a voice"
echo ""
just --list --unsorted

# Talk to Samantha (full voice mode)
talk:
uv run samantha

# Talk in text mode (type instead of speak, still hear her voice)
text:
uv run samantha --text

# Talk in silent mode (type + read, no audio)
silent:
uv run samantha --text --no-voice

# Resume last conversation
resume *SESSION:
uv run samantha resume {{SESSION}}

# Show current config
config:
uv run samantha config

# Set a config value
set KEY VALUE:
uv run samantha config {{KEY}} {{VALUE}}

# Install/sync dependencies
deps:
uv sync

# Clean build artifacts
clean:
rm -rf dist/ build/ *.egg-info .pytest_cache .mypy_cache htmlcov/
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
19 changes: 16 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "samantha-cli"
Expand All @@ -18,17 +18,30 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
dependencies = [
"kokoro-onnx>=0.4",
"soundfile>=0.12",
"SpeechRecognition>=3.10",
"PyAudio>=0.2.14",
"fish-audio-sdk>=0.1",
"rich>=13.0",
"click>=8.0",
"pyyaml>=6.0",
"python-dotenv>=1.0",
]

[project.optional-dependencies]
edge = ["edge-tts>=6.1"]
fish = ["fish-audio-sdk>=0.1"]
whisper = ["faster-whisper>=1.0"]
local = ["faster-whisper>=1.0"]
cloud = ["edge-tts>=6.1", "fish-audio-sdk>=0.1"]
all = ["edge-tts>=6.1", "fish-audio-sdk>=0.1", "faster-whisper>=1.0"]

[project.scripts]
samantha = "samantha.cli:main"

[tool.hatch.build.targets.wheel]
packages = ["samantha"]

[project.urls]
Homepage = "https://ethanplus.ai"
Repository = "https://github.com/ethanplusai/samantha-cli"
Loading