{"id":35711965,"url":"https://github.com/xiaolin/winterfresh","last_synced_at":"2026-01-06T04:52:03.820Z","repository":{"id":330610313,"uuid":"1118060588","full_name":"xiaolin/winterfresh","owner":"xiaolin","description":"Home AI assistant","archived":false,"fork":false,"pushed_at":"2025-12-28T05:07:07.000Z","size":274,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-28T07:09:05.829Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xiaolin.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-17T07:53:25.000Z","updated_at":"2025-12-28T05:07:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/xiaolin/winterfresh","commit_stats":null,"previous_names":["xiaolin/winterfresh"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/xiaolin/winterfresh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaolin%2Fwinterfresh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaolin%2Fwinterfresh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaolin%2Fwinterfresh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaolin%2Fwinterfresh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xiaolin","download_url":"https://codeload.github.com/xiaolin/winterfresh/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xiaolin%2Fwinterfresh/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28221942,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2026-01-06T02:00:07.049Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-06T04:52:01.899Z","updated_at":"2026-01-06T04:52:03.810Z","avatar_url":"https://github.com/xiaolin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Winterfresh 🌨️\n\nA fast, concise voice assistant for Raspberry Pi with wake word detection, conversation memory, and barge-in support.\n\n## Features\n\n- 🎤 Local wake word detection (\"winterfresh\") - no API cost for listening\n- 💬 Conversational memory (5-minute timeout)\n- 🔇 Barge-in support (interrupt while speaking)\n- 🎵 Audio feedback with elegant chimes\n- 🔄 Auto-restart on inactivity\n- 🎙️ Hardware echo cancellation (with USB speakerphone)\n\n## Hardware Requirements\n\n- Raspberry Pi 5 (8GB recommended) or Raspberry Pi 4 (4GB+)\n- Raspberry Pi 5 27W USB-C Power Supply (recommended for Pi 5 with USB peripherals)\n- SanDisk 64GB Extreme microSDXC UHS-I Memory Card (fast boot and read/write)\n- USB Speakerphone with echo cancellation (recommended):\n  - EMEET Conference Speakerphone M0 Plus\n\n## Prerequisites\n\n- Node.js v20+\n- Python 3.11+\n- OpenAI API key\n- Sox audio tools\n\n## Installation\n\n### 1. System Setup (Raspberry Pi)\n\n```bash\n# Update system\nsudo apt-get update\nsudo apt-get upgrade -y\n\n# Install Node.js v20\ncurl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\nsudo apt-get install -y nodejs\n\n# Install Python 3.11+ and pip\nsudo apt-get install -y python3 python3-pip python3-venv\n\n# Install sox for audio recording/playback\nsudo apt-get install -y sox libsox-fmt-all\n\n# Install portaudio (required for sounddevice)\nsudo apt-get install -y portaudio19-dev\n\n# Install git\nsudo apt-get install -y git\n\n# Verify versions\nnode --version    # Should be v20+\npython3 --version # Should be 3.11+\n```\n\n### 2. Clone \u0026 Setup Project\n\n```bash\n# Clone repository\ngit clone https://github.com/xiaolin/winterfresh.git ~/winterfresh\ncd ~/winterfresh\n\n# Install Node.js dependencies\nnpm install\n\n# Build TypeScript\nnpm run build\n```\n\n### 3. Python Virtual Environment Setup\n\n```bash\n# Create virtual environment\npython -m venv .venv\n\n# Activate it\nsource .venv/bin/activate\n\n# Install Python dependencies\npip install -r requirements.txt\n\n# Verify installation\npython -c \"import numpy, sounddevice, vosk; print('✅ All Python packages installed')\"\n\n# Deactivate (optional, the app will use .venv/bin/python directly)\ndeactivate\n```\n\n### 4. Download Vosk Model (for local wake word detection)\n\n```bash\n# Create models directory\nmkdir -p models\ncd models\n\n# Download small English model (~50MB)\nwget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip\nunzip vosk-model-small-en-us-0.15.zip\nrm vosk-model-small-en-us-0.15.zip\n\ncd ..\n```\n\n### 5. Environment Configuration\n\n```bash\n# Create environment file\nvi .env\n```\n\nEnvironment variables in `.env`:\n\n| Variable                | Default                  | Description                            |\n| ----------------------- | ------------------------ | -------------------------------------- |\n| `OPENAI_API_KEY`        | (required)               | Your OpenAI API key                    |\n| `CHAT_MODEL`            | `gpt-4o-mini`            | OpenAI chat model                      |\n| `TRANSCRIBE_MODEL`      | `gpt-4o-mini-transcribe` | OpenAI transcription model             |\n| `TTS_MODEL`             | `gpt-4o-mini-tts`        | OpenAI text-to-speech model            |\n| `WINTERFRESH_MAX_TURNS` | `20`                     | Max conversation turns before trimming |\n| `SAMPLE_RATE`           | `24000`                  | Audio sample rate for recording        |\n| `ARECORD_DEVICE`        | `mic_share`              | ALSA device for recording (Linux)      |\n| `ARECORD_CHANNELS`      | `2`                      | Audio channels for recording (Linux)   |\n\nExample `.env` file:\n\n```bash\nOPENAI_API_KEY=your_openai_api_key_here\nCHAT_MODEL=gpt-4o-mini\nTRANSCRIBE_MODEL=gpt-4o-mini-transcribe\nTTS_MODEL=gpt-4o-mini-tts\nWINTERFRESH_MAX_TURNS=20\nARECORD_DEVICE=mic_share\nARECORD_CHANNELS=2\n```\n\n### 6. Audio Device Setup (Raspberry Pi)\n\n#### 6.1 Check USB Audio Device\n\n```bash\n# Plug in your USB speakerphone\n\n# Check if detected\ncat /proc/asound/cards\n# Should show your USB device (e.g., card 2: EMEET OfficeCore M0 Plus)\n\narecord -l  # List capture devices\naplay -l    # List playback devices\n```\n\n#### 6.2 Configure ALSA for Shared Microphone Access\n\nCreate `~/.asoundrc` to allow multiple processes to share the microphone:\n\n```bash\ncat \u003e ~/.asoundrc \u003c\u003c 'EOF'\npcm.mic_share {\n    type dsnoop\n    ipc_key 12345\n    slave {\n        pcm \"hw:2,0\"\n        rate 16000\n        channels 2\n    }\n}\nEOF\n```\n\n**Note:** Replace `hw:2,0` with your actual card number from `arecord -l` if different.\n\n#### 6.3 Configure PulseAudio\n\n```bash\n# Check PulseAudio is running\nsystemctl --user status pulseaudio\n\n# If not running, start it\nsystemctl --user start pulseaudio\n\n# List available sinks (output) and sources (input)\npactl list sinks short\npactl list sources short\n\n# Set your USB device as default (use actual device names from above)\n# Example for EMEET speakerphone:\npactl set-default-sink alsa_output.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003cYOUR_DEVICE_ID\u003e.analog-stereo\npactl set-default-source alsa_input.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003cYOUR_DEVICE_ID\u003e.analog-stereo\n```\n\n#### 6.4 Make PulseAudio Settings Persistent\n\n```bash\nmkdir -p ~/.config/pulse\ncat \u003e ~/.config/pulse/default.pa \u003c\u003c 'EOF'\n.include /etc/pulse/default.pa\nset-default-sink alsa_output.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003cYOUR_DEVICE_ID\u003e.analog-stereo\nset-default-source alsa_input.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003cYOUR_DEVICE_ID\u003e.analog-stereo\nEOF\n```\n\n**Note:** Replace the device names with your actual device names from `pactl list sinks short` and `pactl list sources short`.\n\n#### 6.5 Test Audio\n\n```bash\n# Test recording with shared device\narecord -D mic_share -f S16_LE -c 2 -r 16000 -d 3 /tmp/test.wav\n\n# Test playback\naplay -D plughw:2,0 /tmp/test.wav\n\n# Test simultaneous recording (both should work without \"Device busy\" error)\narecord -D mic_share -f S16_LE -c 2 -r 16000 -d 10 /tmp/test1.wav \u0026\narecord -D mic_share -f S16_LE -c 2 -r 16000 -d 5 /tmp/test2.wav\n\n# Test Python audio\nsource .venv/bin/activate\npython -c \"import sounddevice as sd; print(sd.query_devices())\"\ndeactivate\n```\n\n### 7. Test the Setup\n\n```bash\n# Test wake word detection first\nsource .venv/bin/activate\npython wake.py\n# Say \"winterfresh\" - should print \"WAKE\" and exit\ndeactivate\n\n# Test full app\nnpm run dev\n# Or: npx tsx src/app.ts\n```\n\n### 8. Install PM2 for Production\n\n```bash\n# Install PM2 globally\nsudo npm install -g pm2\n\n# Build TypeScript\nnpm run build\n\n# Start winterfresh using ecosystem config\npm2 start pm2.config.cjs\n\n# Or start directly\npm2 start dist/app.js --name winterfresh\n\n# Save PM2 configuration\npm2 save\n\n# Setup auto-start on boot\npm2 startup\n# Follow the command it outputs (copy/paste and run it)\n\n# Verify it's running\npm2 status\npm2 logs winterfresh --lines 50\n```\n\n## Usage\n\n### Voice Commands\n\n1. **Wake the assistant**: Say \"winterfresh\", \"winter fresh\", or \"hey winterfresh\"\n2. **Speak your request**: After hearing the wake chime\n3. **Interrupt anytime**: Start speaking to interrupt long responses\n4. **Wait for timeout**: 7 seconds of inactivity returns to wake word mode\n\n### Audio Feedback\n\n- 🎵 **Wake chime** - Wake word detected, ready to listen\n- 🎵 **Processing chime** - Processing your request, stops when assistant speaks\n\n### PM2 Commands\n\n```bash\n# View live logs\npm2 logs winterfresh\n\n# Monitor resource usage\npm2 monit\n\n# Restart after code changes\npm2 restart winterfresh\n\n# Stop the assistant\npm2 stop winterfresh\n\n# Start the assistant\npm2 start winterfresh\n```\n\n## Development\n\n### Local Testing (Mac)\n\n**Note:** For Mac testing, use headphones to avoid echo (built-in speakers will be heard by built-in mic).\n\n```bash\n# Install system dependencies (Mac)\nbrew install sox portaudio\n\n# Setup Python venv\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\ndeactivate\n\n# Download Vosk model\nmkdir -p models \u0026\u0026 cd models\ncurl -LO https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip\nunzip vosk-model-small-en-us-0.15.zip\ncd ..\n\n# Install Node dependencies\nnpm install\n\n# Run\nnpm run dev\n```\n\n### Update Script\n\nCreate `update.sh` for easy updates:\n\n```bash\n#!/bin/bash\ncd ~/winterfresh\ngit pull\nnpm install\nnpm run build\nsource .venv/bin/activate\npip install -r requirements.txt 2\u003e/dev/null || pip install numpy sounddevice vosk\ndeactivate\npm2 restart winterfresh\necho \"✅ Winterfresh updated!\"\n```\n\nMake it executable:\n\n```bash\nchmod +x update.sh\n```\n\n## Troubleshooting\n\n### Wake word not detecting\n\n```bash\n# Test Python script directly\nsource .venv/bin/activate\npython wake.py\n# Speak and watch the audio level bar - it should move\n# Say \"winterfresh\" clearly\ndeactivate\n\n# If no audio level, check your mic:\npython -c \"import sounddevice as sd; print(sd.query_devices())\"\n```\n\n### \"No module named numpy\" error\n\n```bash\n# Make sure packages are in the venv, not global Python\n.venv/bin/pip install numpy sounddevice vosk\n\n# Verify\n.venv/bin/python -c \"import numpy, sounddevice, vosk; print('OK')\"\n```\n\n### Audio device not found\n\n```bash\n# List available devices\narecord -l\naplay -l\n\n# Check Python sees the device\nsource .venv/bin/activate\npython -c \"import sounddevice as sd; print(sd.query_devices())\"\n```\n\n### PulseAudio connection refused\n\n```bash\n# Check if PulseAudio is running\nsystemctl --user status pulseaudio\n\n# Restart PulseAudio\nsystemctl --user restart pulseaudio\n\n# Wait a moment then test\nsleep 2\npactl info\n```\n\n### PulseAudio sink missing after reboot\n\n```bash\n# Check current sinks\npactl list sinks short\n\n# If your USB device isn't the default, set it again\npactl set-default-sink alsa_output.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003cYOUR_DEVICE_ID\u003e.analog-stereo\n\n# Make sure ~/.config/pulse/default.pa exists with correct settings\ncat ~/.config/pulse/default.pa\n```\n\n### \"Device or resource busy\" error\n\nThis happens when multiple processes try to access the microphone without using the shared `dsnoop` device.\n\n```bash\n# Verify ~/.asoundrc exists\ncat ~/.asoundrc\n\n# If missing, recreate it\ncat \u003e ~/.asoundrc \u003c\u003c 'EOF'\npcm.mic_share {\n    type dsnoop\n    ipc_key 12345\n    slave {\n        pcm \"hw:2,0\"\n        rate 16000\n        channels 2\n    }\n}\nEOF\n\n# Make sure .env uses mic_share\ngrep ARECORD .env\n# Should show: ARECORD_DEVICE=mic_share\n```\n\n### No audio output / \"no default audio device\"\n\n```bash\n# Check PulseAudio sink\npactl list sinks short\npactl get-default-sink\n\n# If shows auto_null or missing, your USB device isn't detected\n# Replug the USB device and restart PulseAudio\nsystemctl --user restart pulseaudio\n\n# Verify your USB device is now the default\npactl list sinks short\npactl set-default-sink alsa_output.usb-EMEET_EMEET_OfficeCore_M0_Plus_\u003csome_long_string\u003e\n```\n\n### Permission issues (Raspberry Pi)\n\n```bash\n# Add user to audio group\nsudo usermod -a -G audio $USER\n\n# Logout and login again\n```\n\n### Mac microphone permission\n\nGo to **System Settings → Privacy \u0026 Security → Microphone** and enable for:\n\n- Terminal (or iTerm)\n- Visual Studio Code (if running from VS Code)\n\nQuit and reopen the app after enabling.\n\n## Performance\n\n- Wake word detection: Local (Vosk) - no API cost\n- Speech-to-text: OpenAI Whisper API\n- Chat: OpenAI GPT-4o-mini\n- Text-to-speech: OpenAI TTS\n\n**Raspberry Pi 5 (8GB):**\n\n- Memory usage: ~150-200MB\n- CPU usage: ~5-10% during wake word listening\n- Wake word latency: ~0.3-0.5 second\n- Response latency: ~1-2 seconds (API dependent)\n\n**Raspberry Pi 4 (4GB):**\n\n- Memory usage: ~150-200MB\n- CPU usage: ~10-20% during wake word listening\n- Wake word latency: ~0.5-1 second\n- Response latency: ~1-3 seconds (API dependent)\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiaolin%2Fwinterfresh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxiaolin%2Fwinterfresh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxiaolin%2Fwinterfresh/lists"}