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
1 change: 1 addition & 0 deletions .agents/skills/say/SKILL.md
11 changes: 10 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
cli/elevenlabs/custom.rs
cli/elevenlabs/workflow/

# Hand-written agent skills. The real files live under
# cli/elevenlabs/workflow/skills/ (protected above, because `generate-skills`
# embeds them with include_str!); these are the copies an agent harness
# actually scans. `.claude` is a symlink to `.agents`, so both are listed —
# losing either one loses the skills for whichever path the harness uses.
.agents/
.claude

# Hand-maintained README + assets (Fern generates a default README;
# we own it to document the agents-as-code workflow and hero image).
README.md
Expand All @@ -21,8 +29,9 @@ assets/
# its test suite leaves untracked build artifacts.
elevenlabs-types/.gitignore

# Hand-written live E2E smoke test (tests/wire_test.rs next to it is generated).
# Hand-written tests (tests/wire_test.rs next to them is generated).
tests/e2e_smoke.rs
tests/say_test.rs
.fern/replay.lock
.fern/replay.yml
.gitattributes
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ serde_yaml = "0.9.34"
secrecy = "0.10"
serde_qs = "1.1.1"
sha2 = "0.10"
tempfile = "3"
thiserror = "2"
webbrowser = "1"
rand = "0.8"
Expand Down Expand Up @@ -148,6 +149,5 @@ serde_yaml = "0.9.34"

[dev-dependencies]
serial_test = "3.4.0"
tempfile = "3"
wiremock = "0.6"
tokio = { version = "1", features = ["full"] }
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The CLI does two things:
- [Installation](#installation)
- [Authentication](#authentication)
- [Quick start](#quick-start)
- [Speaking text](#speaking-text)
- [Agents as Code](#agents-as-code)
- [Data residency](#data-residency)
- [UI components](#ui-components)
Expand Down Expand Up @@ -112,6 +113,34 @@ elevenlabs <resource> <method>

Run `elevenlabs <resource> --help` to see available methods for a resource.

## Speaking text

Turn text into speech and play it, without picking a file path or finding a player:

```bash
elevenlabs say "this came from the terminal"
echo "build finished" | elevenlabs say # or: elevenlabs say -
```

The voice, model, audio format and player default to whatever you have stored, and each can be overridden per run:

```bash
elevenlabs say config # show the current defaults
elevenlabs say config voice 21m00Tcm4TlvDq8ikWAM
elevenlabs say config model eleven_multilingual_v2
elevenlabs say config player mpv
elevenlabs say config voice --unset # back to the built-in default

elevenlabs say "one off" --voice JBFqnCBsd6RMkjVDRZzb --model eleven_flash_v2_5 # faster, no audio tags
elevenlabs say "save it" --output out.mp3 # write a file, skip playback
```

Defaults are stored in `~/.elevenlabs/config.json` alongside [data residency](#data-residency). Out of the box `say` uses `eleven_v3` — the most expressive model, and the only family that reads [audio tags](https://elevenlabs.io/docs/overview/capabilities/text-to-speech/best-practices#audio-tags) like `elevenlabs say "[whispers] it worked"` as delivery rather than as words. The audio format follows the player unless `--output-format` or an `--output` filename says otherwise.

Playback shells out to whichever player is on the box, preferring ones that read stdin so audio starts before the download finishes: `ffplay`, `mpv`, `afplay` (macOS), `paplay`/`aplay` (Linux), `Media.SoundPlayer` (Windows). Install [ffmpeg](https://ffmpeg.org) or [mpv](https://mpv.io) if none are present, or point `--player` at your own.

> `config` is a subcommand, so speaking that exact word needs `elevenlabs say -- config`.

## Agents as Code

Manage Conversational AI agents from local configuration files. `elevenlabs agents init` scaffolds a project; agent configs live as JSON on disk and sync to ElevenLabs. Pulled configs are stored as raw wire JSON and pushed back verbatim, so they round-trip losslessly.
Expand Down
2 changes: 1 addition & 1 deletion cli/elevenlabs/workflow/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ fn request_options() -> Option<RequestOptions> {
/// {"message": ..., "status": ...}}`, FastAPI's `{"detail": [{"msg": ...}]}`,
/// and bare `{"message": ..., "status": ...}`. Fall back to the whole body so
/// an unrecognized shape is still shown rather than swallowed.
fn api_error_message(body: &Value) -> String {
pub fn api_error_message(body: &Value) -> String {
let detail = body.get("detail");
if let Some(s) = detail.and_then(Value::as_str) {
return s.to_string();
Expand Down
4 changes: 4 additions & 0 deletions cli/elevenlabs/workflow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ mod api;
mod components;
mod project;
mod residency;
mod say;
mod settings;
mod skills;
mod templates;
mod tests;
mod tools;
Expand All @@ -32,5 +34,7 @@ pub fn register(app: CliApp) -> CliApp {
let app = tools::register(app);
let app = tests::register(app);
let app = residency::register(app);
let app = say::register(app);
let app = skills::register(app);
components::register(app)
}
Loading
Loading