Vader Build guide Verified 2026-09-29
Voice assistant with an animated avatar

Build the Darth Vader voice assistant, from an empty folder to a working app

You talk or type, Claude writes the reply, and the app speaks it back through a helmet avatar that reacts to every state. This guide covers the tools to install, the three API keys to collect, the prompts to give the Claude Code agent, and the checks that prove each part works.

TimeAbout 20 minutes of setup, then the agent builds in phases
AccountsClaude, Deepgram, ElevenLabs
Runs onYour own machine, localhost:5173
StackReact, Vite, Fastify, SQLite
Fig 01The finished app while it speaks. Claude, Deepgram and ElevenLabs all report Ready in the lower left.

How a turn moves through the system

  1. 01 CaptureBrowserMicrophone audio or a typed message
  2. 02 TranscribeDeepgramSpeech to text, with live captions
  3. 03 ReplyClaudeStreams the answer sentence by sentence
  4. 04 SpeakElevenLabsTurns each sentence into audio
  5. 05 PerformBrowserVoice effect chain and the avatar

The server holds every API key. The browser never sees them. One WebSocket per session carries audio in both directions along with the state events that drive the avatar.

Step 01

Prerequisites and links

Everything you need to download or sign up for, in one table. Each row is covered in detail in the step named on the right.

WhatWhy you need itLinkStep
Node.js 22 or newerRuns the server and the web app. This project was built on Node 26.nodejs.org/en/download01
NimbalystDesktop workspace that runs the Claude Code agent beside your files.nimbalyst.com/download02
Claude CodeThe coding agent that writes the project.code.claude.com/docs/en/quickstart03
Claude API keyThe assistant's replies.platform.claude.com/settings/keys04
Deepgram API keySpeech to text.console.deepgram.com04
ElevenLabs API key and voice IDText to speech.elevenlabs.io/app/developers/api-keys04
Chrome, Edge or SafariOpens the app and grants microphone access.Already installed on most machines07

Check your Node version before you start:

Terminal
node --version   # v22 or higher
npm --version
Step 02

Install Nimbalyst

Nimbalyst is a free, open-source desktop app for macOS, Windows and Linux. It works with your existing Claude Code subscription or API key.

Fig 02The download page detects your platform. Other builds are listed under the main button.
  1. Open the downloaded file. On macOS, drag Nimbalyst into Applications. On Windows, run the installer. On Linux, mark the AppImage as executable and run it.
  2. Create an empty folder for the project, for example darth-vader-assistant.
  3. Launch Nimbalyst and open that folder. The window has three panels: files on the left, the editor in the center, and the agent on the right.
Step 03

Open the Claude Code agent

Install Claude Code once, sign in, then start it from the terminal or from the agent panel in Nimbalyst. Both routes run the same agent.

Install

macOS, Linux, WSL
curl -fsSL https://claude.ai/install.sh | bash
Windows PowerShell
irm https://claude.ai/install.ps1 | iex
Alternatives
brew install --cask claude-code        # Homebrew
winget install Anthropic.ClaudeCode    # WinGet

Open a new terminal window and confirm the install. A working install prints a version number followed by (Claude Code).

Terminal
claude --version

Sign in and start a session from the terminal

Terminal
cd ~/repos/darth-vader-assistant
claude
  1. On first run Claude Code asks you to log in. Follow the prompt and finish the sign-in in your browser.
  2. Use a Claude Pro, Max, Team or Enterprise subscription, or a Claude Console account with prepaid credits.
  3. To switch accounts later, type /login inside the session. Type /help to list commands.
CommandWhat it does
claudeStart an interactive session in the current folder
claude "task"Start a session with a first prompt
claude -cContinue the most recent conversation in this folder
claude -rPick a previous conversation to resume
Shift + TabCycle the permission mode inside a session

Start a session from Nimbalyst

  1. Open the project folder in Nimbalyst.
  2. Create a new session in the agent panel on the right.
  3. Choose a Claude model and a reasoning level in the session composer.
  4. Type your prompt and send it. The agent reads and edits files in the open folder, and every change appears in the editor as it lands.
Note

Nimbalyst works with your existing Claude Code subscription or API key. If the agent panel asks you to authenticate, run claude once in a terminal and complete the login there.

Fig 03The official quickstart at code.claude.com covers every install method and login option.
Step 04

Get the API keys

Three services, three keys, plus one voice ID. Each key is shown in full only once, at the moment you create it. Copy it straight into your .env file.

Keep keys out of chat and git

Paste keys directly into .env. Do not paste them into a prompt, a screenshot or a commit. Account names, key values and balances are masked in the screenshots below.

4a. Claude API key

Open API keys Open Billing
  1. Sign in to the Claude Console at platform.claude.com.
  2. Open Organization settings, then API keys.
  3. Select Create key in the top right.
  4. The console first suggests identity federation. For a project on your own machine, choose Continue with an API key.
  5. Name the key vader-assistant, pick an expiry, choose a scope, and select Create key.
  6. Copy the key, which starts with sk-ant-, and paste it into .env as ANTHROPIC_API_KEY.
  7. Make sure the account has credits under Billing. Requests fail without them.
Fig 04API keys sits under Organization settings. Create key is in the top right.
Fig 05Choose Continue with an API key.
Fig 06Name, expiry and scope, then Create key.

4b. Deepgram API key

Open Deepgram Console
  1. Sign in at console.deepgram.com and select your project in the top left.
  2. In the left menu under Manage, open API Keys.
  3. Select Create a New API Key.
  4. Enter vader-assistant as the friendly name and choose an expiration. The default role is enough for transcription.
  5. Select Create Key, copy the secret, and paste it into .env as DEEPGRAM_API_KEY. Deepgram cannot show the secret again.
Fig 07API Keys under Manage.
Fig 08Name the key and set its expiration.

4c. ElevenLabs API key and voice ID

Open API Keys Open Voices
  1. Sign in at elevenlabs.io, open Developers at the bottom of the left menu, then the API Keys tab.
  2. Select Create Key and name it vader-assistant.
  3. Leave Restrict Key on and set Text to Speech to Access. The app needs nothing else.
  4. Select Create Key, copy the key, and paste it into .env as ELEVENLABS_API_KEY.
  5. Open Voices. Pick a deep, slow male voice. Open the three-dot menu on its row and choose Copy voice ID.
  6. Paste the ID into .env as ELEVENLABS_VOICE_ID.
Fig 09Developers, then the API Keys tab.
Fig 10Restricted key with Text to Speech access only.
Fig 11Copy voice ID is the first item in the menu on each voice row.
About the voice

Use a stock voice from the library. The Vader character comes from the effect chain in the browser: a pitch shift, a low-shelf boost, a short comb filter for helmet resonance, and a synthesized breathing loop. Cloning a film voice is not permitted by the provider and is not needed.

Step 05

Configure the project

Install dependencies and put the keys where the server reads them. The .env file is ignored by git.

Terminal
npm install
cp .env.example .env

Open .env and fill in the values from step 04:

.env
# Claude
ANTHROPIC_API_KEY=
CLAUDE_MODEL=claude-opus-5-5
CLAUDE_EFFORT=low

# Speech to text
DEEPGRAM_API_KEY=

# Text to speech
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=

# Server
USE_MOCK_PROVIDERS=false
PORT=8787
WEB_ORIGIN=http://localhost:5173
DATABASE_PATH=./data/vader.db
VariableRequiredDefaultPurpose
ANTHROPIC_API_KEYYesNoneAuthenticates requests to Claude
CLAUDE_MODELNoclaude-opus-5-5Model that writes replies. claude-sonnet-5-5 answers faster
CLAUDE_EFFORTNolowReasoning effort. Low keeps replies quick
CLAUDE_MAX_TOKENSNo2000Upper bound on reply length
DEEPGRAM_API_KEYYesNoneAuthenticates speech to text
DEEPGRAM_MODELNonova-3Transcription model
ELEVENLABS_API_KEYYesNoneAuthenticates text to speech
ELEVENLABS_VOICE_IDYesNoneThe stock voice to speak with
ELEVENLABS_MODELNoeleven_flash_v2_5Low-latency speech model
USE_MOCK_PROVIDERSNofalseSet to true to run the whole loop with no keys and no API calls
PORTNo8787Server port
WEB_ORIGINNohttp://localhost:5173The only origin allowed to connect
DATABASE_PATHNo./data/vader.dbSQLite file for sessions and messages
No keys yet

Set USE_MOCK_PROVIDERS=true and the full loop runs on built-in mocks. This is the fastest way to confirm the app starts before you spend any credits.

Step 06

Build it with the agent

Give the agent one phase at a time and review the result before moving on. The prompts below follow the phases this project was built in. Start each one in a session opened on the project folder.

Phase 0. Plan and project rules

Prompt
Plan a personal voice assistant with an animated Darth Vader style helmet avatar at the center of a dashboard. I talk, it answers out loud, and later it will act on my calendar and mail through MCP connectors.

Stack: Vite, React, TypeScript and Tailwind for the web app. Node, Fastify and ws for the server. SQLite for storage. npm workspaces with apps/web, apps/server and packages/protocol.

Providers: Claude for replies, Deepgram for streaming speech to text, ElevenLabs for streaming text to speech. Put each behind an interface in apps/server/src/providers so any one can be swapped, and add mock providers so the app runs with no keys.

Write the plan to a markdown file with phases, a latency budget, the WebSocket protocol, the data model and the risks. Then write CLAUDE.md with the layout and the rules: keys live in .env and are never printed or committed, and theme assets stay in one swappable folder. Do not write app code yet.

Phase 0. Design system

Prompt
Propose three visual directions for the dashboard as standalone HTML previews. I want a dark command bridge feel: near-black surfaces, hairline structure, monospace labels, one red accent, and the avatar as the only element that glows.

After I pick one, write DESIGN.md with binding tokens for type, color, spacing, radius, shadow, motion and icons, plus a banned list. From then on, no value outside DESIGN.md goes into the app.

Phase 1. App shell and typed chat

Prompt
Implement phase 1 of the plan. Build the design tokens and UI primitives from DESIGN.md, then the layout: session sidebar, center stage, bottom dock and transcript panel. Add the SQLite schema and migrations, session create, rename, delete and search, the WebSocket gateway, and the shared protocol package.

Wire streaming text chat with Claude through the provider interface, with prompt caching and refusal handling. The system prompt is a frozen string written for speech: short plain sentences, no markdown, numbers and dates in spoken form.

Done means a typed conversation persists, reloads and resumes. Add tests and run them.

Phase 2. Voice loop

Prompt
Implement phase 2 of the plan. Capture the microphone in an AudioWorklet and send 16 kHz PCM frames over the WebSocket. Forward them to Deepgram and show interim transcripts as live captions. On end of turn, send the final transcript to Claude.

Buffer Claude's stream in a sentence chunker and send each complete sentence to ElevenLabs so speech starts before the full reply exists. Play the returned audio through a PCM player worklet, then through the Vader effect chain: pitch shift down, low-shelf boost, low-pass, short comb filter, compressor. Add a synthesized breathing loop that ducks under speech.

Add a microphone picker, a Stop button bound to Esc, and a script that checks each provider with one small real request.

Phase 3. Avatar

Prompt
Implement phase 3 of the plan. Build the helmet as original procedural geometry in React Three Fiber, with a red key light and bloom inside the avatar canvas only. Drive it from a small state machine with four states: idle, listening, thinking and speaking. Ease between states so nothing snaps.

The mouth grille glow and the ring around the avatar follow the audio level from the analyser. Respect prefers-reduced-motion. Fall back to a 2D helmet when WebGL is unavailable.

Review pass

Prompt
Run typecheck and the full test suite, then run the provider check. Open the app in a browser, send one typed message, and take screenshots of the empty, thinking, speaking and finished states at desktop width and at 390 pixels wide. Report what you verified, what you could not verify, and any failures with their output.
Working with the agent

Ask for a plan before code. Build one phase per session. Ask for evidence such as test output and screenshots, and read it before you accept the work.

Step 07

Run and verify

Two processes: the server and the web app. Start each in its own terminal.

Terminal 1, server
npm run dev:server   # http://localhost:8787
Terminal 2, web app
npm run dev:web      # http://localhost:5173

Open http://localhost:5173. The server takes a few seconds to start. The Providers list in the lower left should show Ready three times.

Check each provider with one real request

Terminal
npm run check:providers -w @vader/server
Output on this machine, 2026-09-29
OK   Language model (Claude): first text in 1338 ms, done in 1393 ms,
     served by claude-sonnet-5-5 at low effort, stop reason end_turn
OK   Speech to text (Deepgram): connected in 386 ms, model nova-3
OK   Text to speech (ElevenLabs): first audio in 308 ms, 1.3 s of audio,
     model eleven_flash_v2_5

Run the checks

Terminal
npm run typecheck
npm test
PackageTest filesTestsResult
apps/server7151Passed
apps/web421Passed
packages/protocol110Passed

First conversation

  1. Select New session.
  2. Type a message in the dock and press Enter. The state in the top strip moves from Thinking to Speaking, and the reply is captioned under the avatar.
  3. Press Esc or select Stop to interrupt a reply.
  4. To speak, select the microphone button and allow access when the browser asks. Use the arrow beside it to pick the input device.
  5. Press / to search earlier sessions.
Step 08

The finished app

Captured from the running project on 2026-09-29 with real Claude and ElevenLabs responses. Select any image to view it at full size.

Fig 12Idle. A new session, ready for input.
Fig 13Thinking. Claude is writing and Stop is available.
Fig 14After a turn. The transcript is saved and the strip reports first audio at 2.9 s.
Fig 15At 390 wide, panels become sheets.
Fig 16Listening, with the microphone menu open. The chosen device is shown in the top strip.
StateWhat the avatar doesWhat you see in the strip
IdleSlow breathing motion, faint lens reflectionsGrey dot, Idle
ListeningRed rim light rises, ring shows microphone levelRed dot, Listening, device name
ThinkingHead lowers slightly, slow pulse on the ringRed dot, Thinking
SpeakingGrille glow follows speech level, ring shows outputRed dot, Speaking, response time
Step 09

Troubleshooting

The problems most likely to stop a first run, and the fix for each.

SymptomCauseFix
A provider does not show ReadyIts key is missing or misspelled in .envCheck the variable name, save the file, and restart the server. Run the provider check to see which one fails
Claude check fails with a billing errorThe Console account has no creditsAdd credits under Billing in the Claude Console
Text to speech is skippedELEVENLABS_VOICE_ID is emptyCopy a voice ID from the Voices page
ElevenLabs returns a permission errorThe key is restricted without Text to Speech accessEdit the key and set Text to Speech to Access
The page shows OfflineThe server is not running, or it is still startingStart npm run dev:server and wait a few seconds
The browser cannot connect after a port changeWEB_ORIGIN no longer matches the web app addressSet WEB_ORIGIN to the exact address in the browser
The microphone button does nothingMicrophone permission was deniedAllow the microphone for localhost in the browser's site settings, then reload
The wrong microphone is usedA virtual device is the system defaultOpen the menu beside the microphone button and choose the built-in device
The app stays in ThinkingA turn did not completeA watchdog resets the turn after 20 seconds and shows a notice. Send the message again
Replies feel slowTime to first text from the modelSet CLAUDE_MODEL=claude-sonnet-5-5 and keep CLAUDE_EFFORT=low
claude is not found after installThe install directory is not on your PATHOpen a new terminal window. If it persists, see the install troubleshooting page in the Claude Code docs
Step 10

Status and limits

What has been checked in a browser, what is written but not yet checked by a person speaking and listening, and what is still to build.

Verified
  • Typed conversation end to end with real Claude and real ElevenLabs speech
  • Sessions: create, rename, delete, search
  • Transcript with timestamps
  • 3D avatar with four states
  • Narrow-screen layout
  • All three providers pass the provider check
Written, not yet verified
  • Microphone capture with a person speaking
  • Deepgram transcription of real speech
  • The Vader effect chain, judged by ear
  • The breathing loop
Not built yet
  • Interrupting by voice. Stop and Esc work today
  • Push-to-talk
  • Voice settings sliders
  • Calendar and mail connectors
  • Encrypted connector tokens
Measured on typed turnsFirst text from ClaudeFirst audio
claude-sonnet-5-5, six turns0.9 to 2.3 s1.7 to 3.0 s
Short reply, fresh session1.7 to 2.2 s2.2 to 2.9 s
TargetNot set1.5 s
Personal use only

The helmet likeness and the character name belong to Lucasfilm and Disney. This is fine as a private project on your own machine. Before you publish or share the app, swap the avatar, name and sounds for original ones. They all live in apps/web/src/theme-assets for that reason.

Step 11

Checklist

Tick items as you go. Your progress is remembered in this browser.

Screenshot