Skip to content
Merged
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
8 changes: 5 additions & 3 deletions .cursor/skills/scaffold-elevenlabs-example/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Ask concise follow-ups only when these are missing.

```bash
python3 .cursor/skills/scaffold-elevenlabs-example/scripts/scaffold_example.py \
--path text-to-speech/nextjs/my-example
--path text-to-speech/expo/my-example
```

Add `--with-assets` when the example should ship sample files, or `--reference <path>` to copy from a specific existing example.
Expand All @@ -43,22 +43,24 @@ Add `--with-assets` when the example should ship sample files, or `--reference <
- sections are file-by-file using `## \`path/to/file\``
- bullets call out concrete SDKs, env handling, models, voice IDs, UI states, and error handling
- do not restate repo preamble like `example/`-only rules or `DESIGN.md`; the generator adds that
- for `expo`, assume the shared template already provides the generic Expo Router shell, server-ready web config, and baseline verification scripts; keep the prompt focused on ElevenLabs-specific UI and `+api.ts` work

7. Keep `setup.sh` aligned with current patterns:

- use `set -euo pipefail`
- derive `DIR` and `REPO_ROOT`
- clean `example/` but preserve cache dirs (`node_modules`, `.venv`, `.next`) when relevant
- clean `example/` but preserve cache dirs (`node_modules`, `.venv`, `.next`, `.expo`) when relevant
- seed from `templates/<runtime>/`
- copy `README.md` into `example/README.md`
- copy `assets/` and local `.env` only when present
- install dependencies at the end
- for `nextjs`, fetch latest ElevenLabs package versions at setup time and patch `package.json`
- for `expo`, keep the shared template generic and server-capable so `PROMPT.md` only needs to describe the ElevenLabs integration

8. Keep `README.md` aligned with the closest current reference:

- always include a heading, one-sentence summary, `## Setup`, and `## Run`
- add `## Usage` for interactive examples such as Next.js and agents demos
- add `## Usage` for interactive examples such as Next.js, Expo, and agents demos
- commands should work from inside `example/`

9. Recommended when shipping the example: add it to the root `README.md`.
Expand Down
13 changes: 11 additions & 2 deletions .cursor/skills/scaffold-elevenlabs-example/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ Ignore the deprecated root `examples/` folder for new work.
| `typescript` | `templates/typescript/` | `node_modules` | `.env` | `pnpm install --config.confirmModulesPurge=false` |
| `python` | `templates/python/` | `.venv` | `.env` | create `.venv`, upgrade `pip`, `pip install -r requirements.txt` |
| `nextjs` | `templates/nextjs/` | `node_modules`, `.next` | `.env.local` | patch `package.json`, then `pnpm install --config.confirmModulesPurge=false` |
| `expo` | `templates/expo/` | `node_modules`, `.expo` | `.env` | `pnpm install --config.confirmModulesPurge=false` |

## Expo runtime notes

- `templates/expo/` should provide the generic Expo Router app shell, `web.output: "server"` config, a baseline `/api/health` route, and reusable verification scripts such as `typecheck` and `export:web`.
- Keep Expo `PROMPT.md` files focused on ElevenLabs-specific UI, SDK usage, token exchange, `+api.ts` routes, and error handling instead of generic app bootstrapping.
- Until the repo has dedicated Expo examples, use the closest same-product `nextjs` example as the authoring reference for Expo scaffolds.

## Prompt rules

Expand All @@ -69,11 +76,12 @@ Ignore the deprecated root `examples/` folder for new work.
- Keep prompts short and implementation-focused. Current prompts are direct checklists, not essays.
- Mention the concrete SDK client, env loading, output format, model ids, voice ids, API route security, and UI behavior when those details are known.
- Do not repeat repo-wide context that the generator already injects.
- For `expo`, assume the shared template already includes the app shell and baseline checks; only prompt for ElevenLabs-specific changes.

## README rules

- Always include a title, one-sentence summary, `## Setup`, and `## Run`.
- Add `## Usage` for interactive or multi-step examples such as Next.js and agents demos.
- Add `## Usage` for interactive or multi-step examples such as Next.js, Expo, and agents demos.
- Keep commands valid from inside `example/`.
- Use the closest current example as the formatting reference.

Expand All @@ -85,6 +93,7 @@ Ignore the deprecated root `examples/` folder for new work.
- CLI transcription or file-based Scribe example: start from the speech-to-text quickstarts.
- Realtime microphone UI: start from `speech-to-text/nextjs/realtime`.
- Voice agent creation and conversation UI: start from `agents/nextjs/quickstart`.
- First Expo full-stack example for a product: start from the closest same-product `nextjs` example until a dedicated Expo reference exists.
- For specialized agent behavior, start from `agents/nextjs/quickstart` and consult `agents/nextjs/guardrails` only as an existing reference, not as a scaffold mode.

## Scaffold helper
Expand All @@ -93,7 +102,7 @@ The helper script creates a new example directory by copying `PROMPT.md`, `READM

```bash
python3 .cursor/skills/scaffold-elevenlabs-example/scripts/scaffold_example.py \
--path agents/nextjs/my-agent-demo
--path agents/expo/my-agent-demo
```

Useful flags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--path",
required=True,
help="Relative path like product/runtime/my-example",
help="Relative path like product/runtime/my-example "
"(for example, text-to-speech/expo/my-example).",
)
parser.add_argument(
"--reference",
help="Explicit reference example path (e.g. music/nextjs/quickstart). "
help="Explicit reference example path (e.g. speech-to-text/nextjs/realtime). "
"Auto-detected from the repo when omitted.",
)
parser.add_argument(
Expand All @@ -44,7 +45,7 @@ def parse_example_path(path_text: str) -> tuple[str, str, str]:
if len(parts) != 3:
raise SystemExit(
"Example paths must look like <product>/<runtime>/<slug>, for example "
"text-to-speech/nextjs/my-example."
"text-to-speech/expo/my-example."
)

product, runtime, slug = parts
Expand All @@ -70,23 +71,44 @@ def find_existing_examples() -> list[tuple[str, str, str, Path]]:
return results


def pick_reference(paths: list[Path]) -> Path | None:
if not paths:
return None

return sorted(paths, key=lambda path: (path.name != "quickstart", str(path)))[0]


def find_reference(
product: str, runtime: str, examples: list[tuple[str, str, str, Path]]
) -> Path | None:
"""Pick the best existing example to copy from.

Priority: same product+runtime > same runtime > first available.
Priority: same product+runtime > same runtime > same product+nextjs for
Expo > any nextjs for Expo > first available.
"""
same_product_runtime = [d for p, r, _, d in examples if p == product and r == runtime]
if same_product_runtime:
return same_product_runtime[0]
match = pick_reference(same_product_runtime)
if match:
return match

same_runtime = [d for _, r, _, d in examples if r == runtime]
if same_runtime:
return same_runtime[0]
match = pick_reference(same_runtime)
if match:
return match

if runtime == "expo":
same_product_nextjs = [d for p, r, _, d in examples if p == product and r == "nextjs"]
match = pick_reference(same_product_nextjs)
if match:
return match

nextjs_examples = [d for _, r, _, d in examples if r == "nextjs"]
match = pick_reference(nextjs_examples)
if match:
return match

if examples:
return examples[0][3]
return pick_reference([example_dir for _, _, _, example_dir in examples])

return None

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Prompt-driven ElevenLabs examples for text-to-speech, speech-to-text, music, sou
- `setup.sh` — scaffolds the `example/` directory from a shared template
- `example/` — the generated, runnable example with its own `README.md`

Shared base templates live in `templates/` (Next.js, Python, TypeScript). UI styling rules are in `DESIGN.md`.
Shared base templates live in `templates/` (Expo, Next.js, Python, TypeScript). UI styling rules are in `DESIGN.md`.

> The legacy `examples/` folder is being deprecated and can be ignored for new work.

Expand Down
28 changes: 28 additions & 0 deletions agents/expo/quickstart/PROMPT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Before writing any code, invoke the `/agents` skill to learn the correct ElevenLabs SDK patterns.

## `app/api/agent+api.ts`

Secure Expo Router API route that creates or loads a voice agent. Never expose `ELEVENLABS_API_KEY` to the client.

- `POST` creates a new voice agent with sensible defaults (name, system prompt, first message, TTS voice). Use the CLI `voice-only` template as reference for the agent shape.
- `GET` loads an existing agent by `agentId` query param.
- Configure as voice-first: real TTS voice and model, text-only disabled, widget text input disabled.
- For English agents (`language: "en"`), use `tts.modelId: "eleven_flash_v2"`. Do not use `eleven_flash_v2_5` for English-only agents, or agent creation may fail validation.
- Enable client events needed for transcript rendering and audio.
- Return `{ agentId, agentName }`.

## `app/api/conversation-token+api.ts`

Secure GET endpoint that returns a WebRTC conversation token for a given `agentId` using `getWebrtcToken`.
Never expose `ELEVENLABS_API_KEY` to the client. Return `{ token }` as JSON.

## `app/index.tsx`

Minimal Expo Router voice agent screen.

- Use `@elevenlabs/react` and the `useConversation` hook for the web experience.
- Show a `Create Agent` button and an editable agent-id input. Auto-populate on create; allow pasting a different id to load it instead.
- Start WebRTC sessions with a token from `/api/conversation-token` using `startSession({ conversationToken, connectionType: "webrtc" })`. Request mic access before starting.
- Show a Start/Stop toggle, connection status, and running conversation transcript (append messages, don't replace).
- Handle errors gracefully and allow reconnect. Keep the UI simple and voice-first.
- Keep the verified path web-first: use relative fetch calls for Expo web, and render a brief native fallback note instead of attempting an unsupported in-app server flow.
39 changes: 39 additions & 0 deletions agents/expo/quickstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Real-Time Voice Agent (Expo)

Live voice conversations with the ElevenLabs Agents Platform in an Expo Router app with secure Expo API routes for web.

## Setup

1. Copy the environment file and add your credentials:

```bash
cp .env.example .env
```

Then edit `.env` and set:
- `ELEVENLABS_API_KEY`

2. Install dependencies:

```bash
pnpm install
```

## Run

```bash
pnpm run web
```

Open the local Expo web URL shown in the terminal.

## Usage

- Enter an agent name and a system prompt, then click **Create agent**.
- The app creates the agent server-side and stores the returned agent id in the page.
- Click **Start** and allow microphone access when prompted.
- The app fetches a fresh conversation token for the created agent and starts a WebRTC session.
- Speak naturally and watch the live conversation state update as the agent listens and responds.
- The page shows whether the agent is currently speaking and renders the interaction as a running conversation.
- Click **Stop** to end the session.
- This quickstart is verified for Expo web. Native builds need a deployed Expo server origin before the in-app client can call the secure API routes.
1 change: 1 addition & 0 deletions agents/expo/quickstart/example/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ELEVENLABS_API_KEY=
7 changes: 7 additions & 0 deletions agents/expo/quickstart/example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

.expo/
expo-env.d.ts
# @end expo-cli
39 changes: 39 additions & 0 deletions agents/expo/quickstart/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Real-Time Voice Agent (Expo)

Live voice conversations with the ElevenLabs Agents Platform in an Expo Router app with secure Expo API routes for web.

## Setup

1. Copy the environment file and add your credentials:

```bash
cp .env.example .env
```

Then edit `.env` and set:
- `ELEVENLABS_API_KEY`

2. Install dependencies:

```bash
pnpm install
```

## Run

```bash
pnpm run web
```

Open the local Expo web URL shown in the terminal.

## Usage

- Enter an agent name and a system prompt, then click **Create agent**.
- The app creates the agent server-side and stores the returned agent id in the page.
- Click **Start** and allow microphone access when prompted.
- The app fetches a fresh conversation token for the created agent and starts a WebRTC session.
- Speak naturally and watch the live conversation state update as the agent listens and responds.
- The page shows whether the agent is currently speaking and renders the interaction as a running conversation.
- Click **Stop** to end the session.
- This quickstart is verified for Expo web. Native builds need a deployed Expo server origin before the in-app client can call the secure API routes.
16 changes: 16 additions & 0 deletions agents/expo/quickstart/example/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"expo": {
"name": "Real-Time Voice Agent",
"slug": "realtime-voice-agent-expo",
"scheme": "realtime-voice-agent-expo",
"version": "1.0.0",
"orientation": "portrait",
"web": {
"output": "server"
},
"plugins": ["expo-router"],
"experiments": {
"typedRoutes": true
}
}
}
12 changes: 12 additions & 0 deletions agents/expo/quickstart/example/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider } from "react-native-safe-area-context";

export default function RootLayout() {
return (
<SafeAreaProvider>
<StatusBar style="dark" />
<Stack screenOptions={{ headerShown: false }} />
</SafeAreaProvider>
);
}
Loading
Loading