From 900b7849bb18b7496de6da9e3ff017978d4247d6 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 16:31:16 +0000
Subject: [PATCH 01/10] Install example dependencies before verification runs
generate-examples.sh skips setup.sh whenever example/ already exists, but
example/ is committed without node_modules or .venv. The prompt preamble
still told the agent dependencies were installed, so on a fresh clone the
agent spent minutes searching the web and other directories for SDK types
before installing them itself, pushing slow examples past the 600s timeout.
Install dependencies when they are missing, and point the agent at the
installed packages for SDK signature checks.
Co-authored-by: tadas
---
scripts/generate-examples.sh | 46 ++++++++++++++++++++++++++++++++----
1 file changed, 42 insertions(+), 4 deletions(-)
diff --git a/scripts/generate-examples.sh b/scripts/generate-examples.sh
index 8aa80ed3..5d82ce1f 100644
--- a/scripts/generate-examples.sh
+++ b/scripts/generate-examples.sh
@@ -110,6 +110,33 @@ run_with_timeout() {
wait "${cmd_pid}"
}
+example_needs_dependencies() {
+ local example_dir="$1"
+
+ if [[ -f "${example_dir}/package.json" && ! -d "${example_dir}/node_modules" ]]; then
+ return 0
+ fi
+ if [[ -f "${example_dir}/requirements.txt" && ! -d "${example_dir}/.venv" ]]; then
+ return 0
+ fi
+ return 1
+}
+
+# Mirrors the install step at the end of each setup.sh.
+install_example_dependencies() {
+ (
+ cd "$1" || exit 1
+ if [[ -f package.json && ! -d node_modules ]]; then
+ pnpm install --config.confirmModulesPurge=false || exit $?
+ fi
+ if [[ -f requirements.txt && ! -d .venv ]]; then
+ python3 -m venv .venv || exit $?
+ .venv/bin/pip install --upgrade pip || exit $?
+ .venv/bin/pip install -r requirements.txt || exit $?
+ fi
+ )
+}
+
format_duration() {
local total_seconds="$1"
@@ -354,21 +381,32 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
printf "[%s/%s] %s\n" "${CURRENT_PROMPT}" "${TOTAL_PROMPTS}" "${relative_project_dir}"
fi
- # Run setup script if present, but skip if example/ already exists
+ # Run setup script if present, but skip if example/ already exists.
+ # A skipped setup still needs dependencies installed for the agent to verify against.
if [[ -f "${project_dir}/setup.sh" ]]; then
+ setup_cmd=()
if [[ -d "${project_dir}/example" ]]; then
if (( VERBOSE_OUTPUT == 1 )); then
echo "Skipping setup (example/ already exists): ${relative_project_dir}/setup.sh"
fi
+ if example_needs_dependencies "${project_dir}/example"; then
+ if (( VERBOSE_OUTPUT == 1 )); then
+ echo "Installing dependencies: ${relative_project_dir}/example"
+ fi
+ setup_cmd=(install_example_dependencies "${project_dir}/example")
+ fi
else
if (( VERBOSE_OUTPUT == 1 )); then
echo "Running setup: ${relative_project_dir}/setup.sh"
fi
+ setup_cmd=(bash "${project_dir}/setup.sh")
+ fi
+ if (( ${#setup_cmd[@]} > 0 )); then
if (( VERBOSE_OUTPUT == 1 )); then
- bash "${project_dir}/setup.sh"
+ "${setup_cmd[@]}"
SETUP_EXIT=$?
else
- setup_output="$(bash "${project_dir}/setup.sh" 2>&1)"
+ setup_output="$("${setup_cmd[@]}" 2>&1)"
SETUP_EXIT=$?
fi
if [[ ${SETUP_EXIT} -ne 0 ]]; then
@@ -405,7 +443,7 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
setup_detail+=" and sample assets (${asset_list})"
fi
fi
- preamble+="${setup_detail}."$'\n\n'
+ preamble+="${setup_detail}. Check SDK signatures against the packages installed in \`example/\` instead of searching the web or other directories."$'\n\n'
fi
preamble+="Implement in \`example/\` only."
if [[ -f "${REPO_ROOT}/DESIGN.md" ]]; then
From d1568ab7ba651e83de4babc5fcfe59ff35d39092 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 16:31:30 +0000
Subject: [PATCH 02/10] Point dubbing, voice-changer, and voice-isolator
prompts at their own skills
These prompts referenced /text-to-speech, so generate-examples.sh loaded the
TTS SDK reference instead of the product skill, and the agent had to go find
the right API docs on its own.
Co-authored-by: tadas
---
dubbing/nextjs/quickstart/PROMPT.md | 2 +-
voice-changer/nextjs/quickstart/PROMPT.md | 2 +-
voice-isolator/nextjs/quickstart/PROMPT.md | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/dubbing/nextjs/quickstart/PROMPT.md b/dubbing/nextjs/quickstart/PROMPT.md
index af287078..5dd95393 100644
--- a/dubbing/nextjs/quickstart/PROMPT.md
+++ b/dubbing/nextjs/quickstart/PROMPT.md
@@ -1,4 +1,4 @@
-Before writing any code, invoke the `/text-to-speech` skill to learn the correct ElevenLabs SDK patterns.
+Before writing any code, invoke the `/dubbing` skill to learn the correct ElevenLabs SDK patterns.
This example uses the Dubbing Projects API (dubbing v2): a **project** holds the source media and its transcript, and each **language target** produces one dubbed output in a single language.
diff --git a/voice-changer/nextjs/quickstart/PROMPT.md b/voice-changer/nextjs/quickstart/PROMPT.md
index f1ee591e..73b3cfa0 100644
--- a/voice-changer/nextjs/quickstart/PROMPT.md
+++ b/voice-changer/nextjs/quickstart/PROMPT.md
@@ -1,4 +1,4 @@
-Before writing any code, invoke the `/text-to-speech` skill to learn the correct ElevenLabs SDK patterns.
+Before writing any code, invoke the `/voice-changer` skill to learn the correct ElevenLabs SDK patterns.
## 1. `app/api/voice-changer/route.ts`
diff --git a/voice-isolator/nextjs/quickstart/PROMPT.md b/voice-isolator/nextjs/quickstart/PROMPT.md
index 72985f03..3a5a5231 100644
--- a/voice-isolator/nextjs/quickstart/PROMPT.md
+++ b/voice-isolator/nextjs/quickstart/PROMPT.md
@@ -1,4 +1,4 @@
-Before writing any code, invoke the `/text-to-speech` skill to learn the correct ElevenLabs SDK patterns.
+Before writing any code, invoke the `/voice-isolator` skill to learn the correct ElevenLabs SDK patterns.
## 1. `app/api/isolate/route.ts`
From 8ce04ea45394ea6b5aa9b28d12b0b103839a7552 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 16:32:49 +0000
Subject: [PATCH 03/10] Tell the generator agent to verify with typecheck and
lint only
Traces of timed-out runs showed the agent starting dev servers and
installing playwright-core for headless browser checks, which eats the
600s budget and can hang the run.
Co-authored-by: tadas
---
scripts/generate-examples.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/generate-examples.sh b/scripts/generate-examples.sh
index 5d82ce1f..12bceb05 100644
--- a/scripts/generate-examples.sh
+++ b/scripts/generate-examples.sh
@@ -445,7 +445,7 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
fi
preamble+="${setup_detail}. Check SDK signatures against the packages installed in \`example/\` instead of searching the web or other directories."$'\n\n'
fi
- preamble+="Implement in \`example/\` only."
+ preamble+="Implement in \`example/\` only. Verify with typecheck and lint only; do not start dev servers, run browsers, or install extra tooling, because this run has a fixed time limit."
if [[ -f "${REPO_ROOT}/DESIGN.md" ]]; then
preamble+=$'\n\n'"Read \`DESIGN.md\` at the repo root for styling rules. Preserve any existing template header and layout container."
fi
From 76af7358bd2559cea81df673777250136543c59d Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 16:32:49 +0000
Subject: [PATCH 04/10] Fix dubbing example object URL cleanup and prompt
mismatches
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
One cleanup effect depended on both object URLs, so setting the dubbed URL
revoked the original recording and broke its player. Revoke each URL only
when it is replaced or on unmount. Also show "Transcribing your recording…"
as soon as polling starts and use the "Dub Recording" label, as PROMPT.md
requires. The generator kept attempting these fixes and timing out midway.
Co-authored-by: tadas
---
.../nextjs/quickstart/example/app/page.tsx | 44 +++++++------------
1 file changed, 16 insertions(+), 28 deletions(-)
diff --git a/dubbing/nextjs/quickstart/example/app/page.tsx b/dubbing/nextjs/quickstart/example/app/page.tsx
index d3a8c116..584fa8d7 100644
--- a/dubbing/nextjs/quickstart/example/app/page.tsx
+++ b/dubbing/nextjs/quickstart/example/app/page.tsx
@@ -64,11 +64,14 @@ export default function Home() {
const recordStartRef = useRef(0);
useEffect(() => {
- return () => {
- if (originalUrl) URL.revokeObjectURL(originalUrl);
- if (dubbedUrl) URL.revokeObjectURL(dubbedUrl);
- };
- }, [originalUrl, dubbedUrl]);
+ if (!originalUrl) return;
+ return () => URL.revokeObjectURL(originalUrl);
+ }, [originalUrl]);
+
+ useEffect(() => {
+ if (!dubbedUrl) return;
+ return () => URL.revokeObjectURL(dubbedUrl);
+ }, [dubbedUrl]);
const stopTimer = () => {
if (timerRef.current) {
@@ -79,15 +82,9 @@ export default function Home() {
const startRecording = async () => {
setError(null);
- setOriginalUrl(prev => {
- if (prev) URL.revokeObjectURL(prev);
- return null;
- });
+ setOriginalUrl(null);
setWavFile(null);
- setDubbedUrl(prev => {
- if (prev) URL.revokeObjectURL(prev);
- return null;
- });
+ setDubbedUrl(null);
setProjectId(null);
try {
@@ -122,10 +119,7 @@ export default function Home() {
setPhase("preparing");
try {
const wav = await recordedBlobToWavFile(rawBlob);
- setOriginalUrl(prev => {
- if (prev) URL.revokeObjectURL(prev);
- return URL.createObjectURL(wav);
- });
+ setOriginalUrl(URL.createObjectURL(wav));
setWavFile(wav);
setPhase("idle");
} catch (e) {
@@ -175,13 +169,10 @@ export default function Home() {
const startDubbing = async () => {
if (!wavFile) return;
setError(null);
- setDubbedUrl(prev => {
- if (prev) URL.revokeObjectURL(prev);
- return null;
- });
+ setDubbedUrl(null);
setPhase("polling");
setProjectId(null);
- setPollNote(null);
+ setPollNote("Transcribing your recording…");
const body = new FormData();
body.set("audio", wavFile);
@@ -264,10 +255,7 @@ export default function Home() {
}
const blob = await audioRes.blob();
if (cancelled) return;
- setDubbedUrl(prev => {
- if (prev) URL.revokeObjectURL(prev);
- return URL.createObjectURL(blob);
- });
+ setDubbedUrl(URL.createObjectURL(blob));
setPhase("ready");
} catch (e) {
if (!cancelled) {
@@ -320,7 +308,7 @@ export default function Home() {
case "preparing":
return "Preparing audio…";
case "polling":
- return pollNote ?? "Dubbing in progress… checking every 5s.";
+ return pollNote ?? "Transcribing your recording…";
case "ready":
return "Dubbing complete.";
case "error":
@@ -448,7 +436,7 @@ export default function Home() {
)}
onClick={startDubbing}
>
- {phase === "polling" ? "Dubbing…" : "Dub recording"}
+ {phase === "polling" ? "Dubbing…" : "Dub Recording"}
) : null}
From 6be1629e8de2fbfe5fda11897bb0e34da525ff87 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 16:55:44 +0000
Subject: [PATCH 05/10] Make example dependency installs non-fatal and prefer
uv for Python
On runners without python3-venv, the new dependency install turned every
Python example into a setup failure even though the agent can verify those
examples without a virtualenv. Use uv when it is available, remove partial
installs, warn instead of failing, and only tell the agent dependencies are
installed when they actually are.
Co-authored-by: tadas
---
scripts/generate-examples.sh | 59 ++++++++++++++++++++++++++++++------
1 file changed, 50 insertions(+), 9 deletions(-)
diff --git a/scripts/generate-examples.sh b/scripts/generate-examples.sh
index 12bceb05..25ff50ac 100644
--- a/scripts/generate-examples.sh
+++ b/scripts/generate-examples.sh
@@ -122,17 +122,29 @@ example_needs_dependencies() {
return 1
}
-# Mirrors the install step at the end of each setup.sh.
+# Mirrors the install step at the end of each setup.sh. Partial installs are
+# removed so example_needs_dependencies stays accurate for the prompt preamble.
install_example_dependencies() {
(
cd "$1" || exit 1
if [[ -f package.json && ! -d node_modules ]]; then
- pnpm install --config.confirmModulesPurge=false || exit $?
+ if ! pnpm install --config.confirmModulesPurge=false; then
+ rm -rf node_modules
+ exit 1
+ fi
fi
if [[ -f requirements.txt && ! -d .venv ]]; then
- python3 -m venv .venv || exit $?
- .venv/bin/pip install --upgrade pip || exit $?
- .venv/bin/pip install -r requirements.txt || exit $?
+ if command -v uv >/dev/null 2>&1; then
+ uv venv .venv && uv pip install --python .venv/bin/python -r requirements.txt
+ else
+ python3 -m venv .venv &&
+ .venv/bin/pip install --upgrade pip &&
+ .venv/bin/pip install -r requirements.txt
+ fi
+ if [[ $? -ne 0 ]]; then
+ rm -rf .venv
+ exit 1
+ fi
fi
)
}
@@ -385,6 +397,7 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
# A skipped setup still needs dependencies installed for the agent to verify against.
if [[ -f "${project_dir}/setup.sh" ]]; then
setup_cmd=()
+ dependency_install_only=0
if [[ -d "${project_dir}/example" ]]; then
if (( VERBOSE_OUTPUT == 1 )); then
echo "Skipping setup (example/ already exists): ${relative_project_dir}/setup.sh"
@@ -394,6 +407,7 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
echo "Installing dependencies: ${relative_project_dir}/example"
fi
setup_cmd=(install_example_dependencies "${project_dir}/example")
+ dependency_install_only=1
fi
else
if (( VERBOSE_OUTPUT == 1 )); then
@@ -409,7 +423,15 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
setup_output="$("${setup_cmd[@]}" 2>&1)"
SETUP_EXIT=$?
fi
- if [[ ${SETUP_EXIT} -ne 0 ]]; then
+ if [[ ${SETUP_EXIT} -ne 0 && ${dependency_install_only} -eq 1 ]]; then
+ # The agent can still verify without dependencies, just more slowly.
+ if (( VERBOSE_OUTPUT == 1 )); then
+ echo " ! Dependency install failed; continuing without dependencies" >&2
+ else
+ echo " ! Dependency install failed; continuing without dependencies" >&2
+ printf "%s\n" "${setup_output}" | tail -n 5 >&2
+ fi
+ elif [[ ${SETUP_EXIT} -ne 0 ]]; then
FAILED_RUNS=$((FAILED_RUNS + 1))
if (( VERBOSE_OUTPUT == 1 )); then
echo " ✗ Setup failed" >&2
@@ -436,14 +458,33 @@ for prompt_file in "${PROMPT_FILES[@]}"; do
preamble+="IMPORTANT: Check the existing code in example/ first. Only make changes if the requirements below are not already met. If everything is already implemented correctly, confirm that no changes are needed."$'\n\n'
fi
if [[ -f "setup.sh" ]]; then
- setup_detail="Prerequisite: \`setup.sh\` has already been run. \`example/\` is ready with dependencies installed"
+ dependencies_ready=1
+ if [[ -d "example" ]] && example_needs_dependencies "example"; then
+ dependencies_ready=0
+ fi
+ setup_items=()
+ if (( dependencies_ready == 1 )); then
+ setup_items+=("dependencies installed")
+ fi
if [[ -d "assets" ]]; then
asset_list="$(ls -1 assets/ 2>/dev/null | paste -sd ', ' -)"
if [[ -n "${asset_list}" ]]; then
- setup_detail+=" and sample assets (${asset_list})"
+ setup_items+=("sample assets (${asset_list})")
fi
fi
- preamble+="${setup_detail}. Check SDK signatures against the packages installed in \`example/\` instead of searching the web or other directories."$'\n\n'
+ setup_detail="Prerequisite: \`setup.sh\` has already been run. \`example/\` is ready"
+ if (( ${#setup_items[@]} == 2 )); then
+ setup_detail+=" with ${setup_items[0]} and ${setup_items[1]}"
+ elif (( ${#setup_items[@]} == 1 )); then
+ setup_detail+=" with ${setup_items[0]}"
+ fi
+ preamble+="${setup_detail}."
+ if (( dependencies_ready == 1 )); then
+ preamble+=" Check SDK signatures against the packages installed in \`example/\` instead of searching the web or other directories."
+ else
+ preamble+=" Its dependencies are not installed."
+ fi
+ preamble+=$'\n\n'
fi
preamble+="Implement in \`example/\` only. Verify with typecheck and lint only; do not start dev servers, run browsers, or install extra tooling, because this run has a fixed time limit."
if [[ -f "${REPO_ROOT}/DESIGN.md" ]]; then
From 67e104c59e548538f5a995997200e290ad77bd9b Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Thu, 22 Oct 2026 17:26:28 +0000
Subject: [PATCH 06/10] Align realtime speech-to-text example with DESIGN.md
and lint
The page used bordered boxes, a wrapped waveform, and uppercase headings,
which DESIGN.md disallows, and it cleared state with setState inside an
effect, which fails react-hooks/set-state-in-effect. The generator rewrote
this page on almost every run and usually timed out partway.
Flatten the layout, clear transient state in onDisconnect/onError, surface
real token and connection errors, show connecting/error status, show the
waveform processing state while connecting, and key history by id.
Co-authored-by: tadas
---
.../nextjs/realtime/example/app/page.tsx | 214 +++++++++---------
1 file changed, 107 insertions(+), 107 deletions(-)
diff --git a/speech-to-text/nextjs/realtime/example/app/page.tsx b/speech-to-text/nextjs/realtime/example/app/page.tsx
index 992acccb..f55689e3 100644
--- a/speech-to-text/nextjs/realtime/example/app/page.tsx
+++ b/speech-to-text/nextjs/realtime/example/app/page.tsx
@@ -1,13 +1,25 @@
"use client";
-import { useState, useCallback, useEffect } from "react";
+import { useState, useCallback, useRef } from "react";
import { useScribe, CommitStrategy } from "@elevenlabs/react";
import { LiveWaveform } from "@/components/ui/live-waveform";
+type CommittedEntry = {
+ id: number;
+ text: string;
+};
+
+function errorMessage(err: unknown, fallback: string): string {
+ return err instanceof Error && err.message ? err.message : fallback;
+}
+
export default function Home() {
const [error, setError] = useState(null);
const [partialTranscript, setPartialTranscript] = useState("");
- const [committedHistory, setCommittedHistory] = useState([]);
+ const [committedHistory, setCommittedHistory] = useState(
+ []
+ );
+ const nextEntryIdRef = useRef(0);
const scribe = useScribe({
modelId: "scribe_v2_realtime",
@@ -18,43 +30,58 @@ export default function Home() {
setPartialTranscript(data.text || "");
},
onCommittedTranscript: data => {
- if (data.text && data.text.trim()) {
- setCommittedHistory(prev => [data.text, ...prev]);
+ const text = data.text?.trim();
+ if (text) {
+ const id = nextEntryIdRef.current++;
+ setCommittedHistory(prev => [{ id, text }, ...prev]);
}
setPartialTranscript("");
},
+ onDisconnect: () => {
+ setPartialTranscript("");
+ },
onError: err => {
console.error("Scribe error:", err);
- setError("Connection error occurred. Please try again.");
+ setPartialTranscript("");
+ setError(
+ errorMessage(err, "Connection error occurred. Please try again.")
+ );
},
});
- useEffect(() => {
- if (scribe.status === "disconnected" || scribe.status === "error") {
- setPartialTranscript("");
- }
- }, [scribe.status]);
-
- // Check both connected and transcribing states to properly show active status
+ // Status moves to "transcribing" while speech is processed, so both states mean the session is live.
const isActive =
scribe.status === "connected" || scribe.status === "transcribing";
const isConnecting = scribe.status === "connecting";
+ const statusLabel =
+ scribe.status === "connecting"
+ ? "Connecting"
+ : scribe.status === "transcribing"
+ ? "Transcribing"
+ : scribe.status === "connected"
+ ? "Connected"
+ : scribe.status === "error"
+ ? "Error"
+ : "Disconnected";
+
const handleStart = useCallback(async () => {
try {
setError(null);
setPartialTranscript("");
- // Fetch a fresh single-use token from our API
+ // Tokens are single-use, so fetch a fresh one for every session.
const response = await fetch("/api/scribe-token");
- if (!response.ok) {
- throw new Error("Failed to get transcription token");
+ const body = (await response.json().catch(() => null)) as {
+ token?: string;
+ error?: string;
+ } | null;
+ if (!response.ok || !body?.token) {
+ throw new Error(body?.error || "Failed to get transcription token");
}
- const { token } = await response.json();
- // Connect with microphone access
await scribe.connect({
- token,
+ token: body.token,
microphone: {
echoCancellation: true,
noiseSuppression: true,
@@ -64,7 +91,10 @@ export default function Home() {
} catch (err) {
console.error("Failed to start transcription:", err);
setError(
- "Failed to start transcription. Please check your permissions and try again."
+ errorMessage(
+ err,
+ "Failed to start transcription. Please check your permissions and try again."
+ )
);
}
}, [scribe]);
@@ -98,106 +128,76 @@ export default function Home() {