7.4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Voice-controlled terminal assistant using OpenAI's Realtime API (via Whisper STT, GPT-4, and TTS). Single-service architecture combining Express server, React UI, and tmux-based terminal management.
Monorepo Structure
This is an npm workspace monorepo:
- Root: Workspace configuration and shared TypeScript config
- packages/voice-assistant: Main application (only active package currently)
Development Commands
All commands run from the repository root:
# Development (starts Express server with integrated Vite middleware)
npm run dev
# Build everything
npm run build
# Type checking
npm run typecheck
# Production
npm run start
Voice Assistant Package Commands
From packages/voice-assistant/:
# Development
npm run dev # Watch mode: Express server + Vite HMR
npm run dev:server # Express server only (port 3000)
# Building
npm run build # Build both UI and server
npm run build:ui # Vite build � dist/ui
npm run build:server # TypeScript � dist/server
# Production
npm start # Run built server
# Type checking
npm run typecheck # Check both UI and server
Architecture
Overall Structure
Single service with three main subsystems:
-
Server Layer (
src/server/):- Express HTTP server (port 3000)
- WebSocket server for bidirectional communication
- Vite middleware in development, static files in production
-
Agent Layer (
src/server/agent/):orchestrator.ts: Main conversation managerllm-openai.ts: GPT-4 streaming with tool executionstt-openai.ts: Whisper transcriptiontts-openai.ts: OpenAI TTS synthesissystem-prompt.ts: Agent instructions (voice-first UX focused)
-
Daemon Layer (
src/server/daemon/):terminal-manager.ts: High-level terminal operationstmux.ts: Low-level tmux integrationtool-definitions.ts: OpenAI function schemas for LLM
-
UI Layer (
src/ui/):- React 18 + Vite
- WebSocket client for real-time communication
- Audio capture and playback handling
Key Architecture Decisions
Terminal Model:
- Uses tmux session "voice-dev" with windows-as-terminals
- Each terminal = one tmux window with single pane
- Terminal ID = tmux window ID (format:
@123) - Terminal names must be unique
Voice-First Design:
- All responses designed for TTS output
- STT errors handled intelligently (see
agent-prompt.md) - No visual feedback assumptions
Streaming Architecture:
- Audio: Browser � WebSocket � Whisper STT � LLM � TTS � WebSocket � Browser
- Text chunks streamed for real-time response
- Tool calls logged as activity events
Data Flow
- Voice Input: Browser captures audio � sends chunks via WebSocket � STT transcribes � orchestrator processes
- LLM Processing: Orchestrator streams GPT-4 responses � executes tool calls � accumulates response
- Voice Output: Response � TTS synthesis � base64 audio � WebSocket � browser playback
TypeScript Configuration
Three separate configs:
tsconfig.base.json: Root workspace defaults (ES2020, ESNext modules)packages/voice-assistant/tsconfig.json: UI build config (React JSX, bundler mode, noEmit)packages/voice-assistant/tsconfig.server.json: Server build config (NodeNext modules, outputs to dist/server)
Module System:
- Server: ES modules with
.jsextensions in imports (NodeNext resolution) - UI: Vite bundler mode (no file extensions needed)
Environment Variables
Required in packages/voice-assistant/.env:
OPENAI_API_KEY=sk-... # Required for GPT-4, Whisper, and TTS
# Optional TTS configuration
TTS_VOICE=alloy # alloy|echo|fable|onyx|nova|shimmer
TTS_MODEL=tts-1 # tts-1 (fast) | tts-1-hd (quality)
# Server config
PORT=3000
NODE_ENV=development
Terminal Control (MCP-like Tools)
The LLM has access to terminal operations via function calling:
list_terminals(): Get all terminals with statuscreate_terminal(name, workingDirectory, initialCommand?): Create new terminalsend_text(terminalId, text, pressEnter, captureOutput?): Execute commandscapture_terminal(terminalId, lines?, wait?): Read terminal outputkill_terminal(terminalId): Remove terminal
See src/server/daemon/tool-definitions.ts for complete schemas.
WebSocket Protocol
Message types (see src/server/types.ts):
Client � Server:
user_message: Text inputaudio_chunk: Audio data (base64, includes format and isLast flag)recording_state: Recording started/stoppedping: Keepalive
Server � Client:
activity_log: Conversation events (transcript, tool calls, errors)assistant_chunk: Streaming response textaudio_output: Complete TTS audio (base64 MP3)transcription_result: STT outputstatus: Connection statepong: Keepalive response
Agent System Prompt
Located in agent-prompt.md (32KB) - loaded by system-prompt.ts.
Critical aspects:
- Voice-first interaction guidelines
- STT error handling strategies
- Terminal operation best practices
- Response brevity requirements (user is on phone)
Testing
Currently manual testing:
- Start dev server:
npm run dev - Open http://localhost:3000
- Test WebSocket connection (status indicator)
- Test voice recording/playback
- Test terminal operations via
/api/test-terminal
Test scripts:
test-llm.sh: Direct LLM streaming testtest-orchestrator.js: Conversation flow test
File Organization
packages/voice-assistant/
�� src/
�� server/
�� agent/ # LLM, STT, TTS, orchestration
�� daemon/ # Terminal management + tool schemas
�� index.ts # Express + WebSocket server
�� websocket-server.ts
�� types.ts # Shared server types
�� ui/
�� components/ # React components
�� hooks/ # WebSocket, audio hooks
�� lib/ # Audio capture/playback utilities
�� App.tsx
�� main.tsx
�� dist/ # Build output (gitignored)
�� agent-prompt.md # System prompt source
�� package.json
�� tsconfig.json # UI TypeScript config
�� tsconfig.server.json # Server TypeScript config
�� vite.config.ts
Common Tasks
Adding a new terminal tool:
- Add function schema to
daemon/tool-definitions.ts - Implement handler in
agent/llm-openai.tsexecuteTool function - If needed, add low-level tmux operation to
daemon/tmux.ts
Modifying agent behavior:
- Edit
agent-prompt.mdfor instructions - Restart server (prompt loaded on startup)
Changing audio formats:
- STT: Modify
agent/stt-openai.ts(currently accepts webm/ogg/mp3) - TTS: Change TTS_MODEL env var or
tts-openai.tsconfig
WebSocket message types:
- Add type to
types.tsWebSocketMessage union - Add handler in
websocket-server.tsor client hooks
Known Patterns
- No barrel files: Direct imports only (no index.ts exports)
- Function declarations: Always use
functionnot arrow functions for exports - Error handling: Let errors bubble to Express/WebSocket error handlers
- Tool execution: Synchronous within LLM streaming loop (tools modify conversation state)
- Conversation cleanup: Auto-cleanup runs every 10 minutes (60 min TTL)