Packages

Auto-generated Elixir client for the ElevenLabs API

Current section

Files

Jump to
eleven_labs generate.exs
Raw

generate.exs

#!/usr/bin/env elixir
# generate.exs — Auto-generates the ElevenLabs Elixir SDK from the OpenAPI spec.
#
# Usage:
# mix run generate.exs
#
# This script:
# 1. Fetches the ElevenLabs OpenAPI JSON spec
# 2. Parses all endpoints and generates Elixir wrapper functions with NimbleOptions validation
# 3. Writes lib/eleven_labs.ex
# 4. Bumps the patch version in mix.exs if the generated code changed
defmodule ElevenLabs.Generator do
@openapi_url "https://api.elevenlabs.io/openapi.json"
@output_file "lib/eleven_labs.ex"
@mix_file "mix.exs"
def run do
IO.puts("Fetching OpenAPI spec...")
{:ok, spec} = fetch_spec()
IO.puts("Generating client code...")
code = generate_module(spec)
old_code =
if File.exists?(@output_file) do
File.read!(@output_file)
else
""
end
if code != old_code do
File.write!(@output_file, code)
IO.puts("Wrote #{@output_file}")
bump_version()
else
IO.puts("No changes detected — skipping write and version bump.")
end
end
defp fetch_spec do
Application.ensure_all_started(:req)
case Req.get(@openapi_url) do
{:ok, %Req.Response{status: 200, body: body}} when is_map(body) ->
{:ok, body}
{:ok, %Req.Response{status: 200, body: body}} when is_binary(body) ->
{:ok, Jason.decode!(body)}
{:ok, %Req.Response{status: status}} ->
{:error, "HTTP #{status}"}
{:error, reason} ->
{:error, reason}
end
end
# ---------------------------------------------------------------------------
# Module template
# ---------------------------------------------------------------------------
defp generate_module(spec) do
base_url = "https://api.elevenlabs.io"
api_version = get_in(spec, ["info", "version"]) || "1.0"
paths = Map.get(spec, "paths", %{})
functions =
paths
|> Enum.flat_map(fn {path, methods} ->
path_level_params = Map.get(methods, "parameters", [])
methods
|> Enum.reject(fn {key, _} -> key == "parameters" end)
|> Enum.map(fn {method, operation} ->
{method, path, operation, path_level_params}
end)
end)
|> Enum.sort_by(fn {_method, _path, op, _} -> Map.get(op, "operationId", "") end)
|> Enum.map(&generate_function(&1, spec))
|> Enum.join("\n")
bt = <<96>>
auth_header_line = ~S' headers: [{"xi-api-key", api_key}]'
"""
# This file is auto-generated by generate.exs — DO NOT EDIT MANUALLY.
# Re-generate with: mix run generate.exs
defmodule ElevenLabs do
@moduledoc \"\"\"
Auto-generated ElevenLabs API #{api_version} client.
Generated from the OpenAPI spec at:
#{@openapi_url}
## Configuration
Set your API key in application config:
config :eleven_labs, api_key: "your-xi-api-key"
Or pass it as an option to any function:
ElevenLabs.text_to_speech_full("voice_id", [text: "Hello"], api_key: "your-xi-api-key")
## Options
All functions accept an optional keyword list as the last argument with:
* #{bt}:api_key#{bt} - Override the API key for this request
* #{bt}:base_url#{bt} - Override the default API base URL
* Any other options are passed through to the #{bt}Req#{bt} request
## Response Types
Most endpoints return JSON maps. However, several endpoints (text-to-speech,
speech-to-speech, sound generation, audio isolation, etc.) return raw binary
audio data (MP3, WAV, PCM bytes). #{bt}Req#{bt} handles this natively — the
response body will be a binary for audio endpoints and a decoded map for JSON
endpoints.
## Usage
{:ok, response} = ElevenLabs.get_voices()
{:ok, response} = ElevenLabs.text_to_speech_full(
"21m00Tcm4TlvDq8ikWAM",
text: "Hello, world!"
)
# response.body is raw MP3 binary audio data
\"\"\"
@type response :: {:ok, Req.Response.t()} | {:error, Exception.t()}
@base_url "#{base_url}"
defp client(opts) do
api_key =
Keyword.get_lazy(opts, :api_key, fn ->
Application.get_env(:eleven_labs, :api_key) ||
raise \"\"\"
ElevenLabs API key not found. Set it in your config:
config :eleven_labs, api_key: "your-xi-api-key"
Or pass it as an option:
ElevenLabs.get_voices(api_key: "your-xi-api-key")
\"\"\"
end)
{base_url, opts} = Keyword.pop(opts, :base_url, @base_url)
opts = Keyword.delete(opts, :api_key)
Req.new(
base_url: base_url,
#{auth_header_line}
)
|> Req.merge(opts)
end
defp to_body(validated_params, key_mapping) do
Map.new(validated_params, fn {k, v} ->
json_key = Map.fetch!(key_mapping, k)
{json_key, to_json_value(v)}
end)
end
defp to_query(validated_params, key_mapping) do
Enum.map(validated_params, fn {k, v} ->
{Map.fetch!(key_mapping, k), to_json_value(v)}
end)
end
defp to_multipart(validated_params, key_mapping) do
Enum.map(validated_params, fn {k, v} ->
json_key = Map.fetch!(key_mapping, k)
{json_key, to_json_value(v)}
end)
end
defp to_json_value(atom) when is_atom(atom) and atom not in [true, false, nil] do
Atom.to_string(atom)
end
defp to_json_value(map) when is_map(map), do: map
defp to_json_value(list) when is_list(list) do
if Keyword.keyword?(list) and list != [] do
Map.new(list, fn {k, v} -> {camelize(Atom.to_string(k)), to_json_value(v)} end)
else
Enum.map(list, &to_json_value/1)
end
end
defp to_json_value(value), do: value
defp camelize(string) do
[first | rest] = String.split(string, "_")
Enum.join([first | Enum.map(rest, &String.capitalize/1)])
end
#{functions}end
"""
end
# ---------------------------------------------------------------------------
# Per-function generation
# ---------------------------------------------------------------------------
defp generate_function({method, path, operation, path_level_params}, spec) do
operation_id = Map.get(operation, "operationId", "unknown")
func_name = to_snake_case(operation_id)
summary = Map.get(operation, "summary", "")
tag = operation |> Map.get("tags", []) |> List.first() || ""
op_params = Map.get(operation, "parameters", [])
all_params = path_level_params ++ op_params
# Filter out the xi-api-key header param — the client handles auth automatically
all_params =
Enum.reject(all_params, fn p ->
Map.get(p, "name") == "xi-api-key" and Map.get(p, "in") == "header"
end)
path_params =
all_params
|> Enum.filter(fn p -> Map.get(p, "in") == "path" end)
|> Enum.map(fn p -> Map.get(p, "name") end)
query_params =
all_params
|> Enum.filter(fn p -> Map.get(p, "in") == "query" end)
has_body = Map.has_key?(operation, "requestBody")
is_multipart = is_multipart_request?(operation)
http_method = String.upcase(method)
# Extract request body schema
body_properties = if has_body, do: extract_body_properties(operation, spec, is_multipart), else: []
required_keys = if has_body, do: extract_required_keys(operation, spec, is_multipart), else: []
# Build schema/mapping for body params
body_schema_code =
if has_body, do: generate_schema(func_name, body_properties, required_keys), else: nil
body_key_mapping_code =
if has_body, do: generate_key_mapping(func_name, body_properties), else: nil
# Build schema/mapping for query params (skip if endpoint has a body — query params
# can be passed via opts and we avoid generating unused module attributes)
has_query_schema = query_params != [] and not has_body
query_schema_code =
if has_query_schema, do: generate_query_schema(func_name, query_params), else: nil
query_key_mapping_code =
if has_query_schema, do: generate_query_key_mapping(func_name, query_params), else: nil
query_param_names = Enum.map(query_params, fn p -> Map.get(p, "name") end)
# Check if response is binary (audio/video)
is_binary_response = is_binary_response?(operation)
# Build docs
doc =
build_doc(
summary,
http_method,
path,
tag,
path_params,
query_param_names,
has_body,
func_name,
has_query_schema,
is_binary_response,
is_multipart
)
# Build non-bang and bang function bodies
{sig, body} =
build_function_body(
func_name,
method,
path,
path_params,
query_param_names,
has_body,
has_query_schema,
is_multipart,
false
)
{bang_sig, bang_body} =
build_function_body(
func_name,
method,
path,
path_params,
query_param_names,
has_body,
has_query_schema,
is_multipart,
true
)
# Build typespecs
spec_code = build_typespec(func_name, path_params, has_body || has_query_schema, false)
bang_spec_code = build_typespec(func_name, path_params, has_body || has_query_schema, true)
parts = [
body_schema_code,
body_key_mapping_code,
query_schema_code,
query_key_mapping_code,
doc,
spec_code,
" #{sig}",
body,
"",
doc_bang(func_name),
bang_spec_code,
" #{bang_sig}",
bang_body,
""
]
parts |> Enum.reject(&is_nil/1) |> Enum.join("\n")
end
# ---------------------------------------------------------------------------
# Request/Response Type Detection
# ---------------------------------------------------------------------------
defp is_multipart_request?(operation) do
case get_in(operation, ["requestBody", "content"]) do
%{"multipart/form-data" => _} -> true
_ -> false
end
end
defp is_binary_response?(operation) do
case get_in(operation, ["responses", "200", "content"]) do
nil ->
false
content when is_map(content) ->
content
|> Map.keys()
|> Enum.any?(fn ct ->
String.starts_with?(ct, "audio/") or
String.starts_with?(ct, "video/") or
ct == "application/zip" or
ct == "application/x-zip"
end)
end
end
# ---------------------------------------------------------------------------
# Schema Extraction
# ---------------------------------------------------------------------------
defp extract_body_properties(operation, spec, true = _is_multipart) do
schema = get_in(operation, ["requestBody", "content", "multipart/form-data", "schema"]) || %{}
resolve_properties(schema, spec)
end
defp extract_body_properties(operation, spec, false = _is_multipart) do
schema = get_in(operation, ["requestBody", "content", "application/json", "schema"]) || %{}
resolve_properties(schema, spec)
end
defp extract_required_keys(operation, spec, true = _is_multipart) do
schema = get_in(operation, ["requestBody", "content", "multipart/form-data", "schema"]) || %{}
resolve_required(schema, spec)
end
defp extract_required_keys(operation, spec, false = _is_multipart) do
schema = get_in(operation, ["requestBody", "content", "application/json", "schema"]) || %{}
resolve_required(schema, spec)
end
defp resolve_properties(schema, spec) do
cond do
Map.has_key?(schema, "$ref") ->
resolved = resolve_ref(schema["$ref"], spec)
resolve_properties(resolved, spec)
Map.has_key?(schema, "allOf") ->
schema["allOf"]
|> Enum.flat_map(fn sub -> resolve_properties(sub, spec) end)
|> Enum.uniq_by(fn {name, _} -> name end)
Map.has_key?(schema, "properties") ->
schema["properties"]
|> Enum.map(fn {name, prop_schema} ->
prop_schema = resolve_if_ref(prop_schema, spec)
{name, prop_schema}
end)
true ->
[]
end
end
defp resolve_required(schema, spec) do
cond do
Map.has_key?(schema, "$ref") ->
resolved = resolve_ref(schema["$ref"], spec)
resolve_required(resolved, spec)
Map.has_key?(schema, "allOf") ->
schema["allOf"]
|> Enum.flat_map(fn sub -> resolve_required(sub, spec) end)
|> Enum.uniq()
Map.has_key?(schema, "required") ->
schema["required"]
true ->
[]
end
end
defp resolve_ref(ref, spec) do
path = ref |> String.trim_leading("#/") |> String.split("/")
get_in(spec, path) || %{}
end
defp resolve_if_ref(%{"$ref" => ref}, spec), do: resolve_ref(ref, spec)
defp resolve_if_ref(schema, _spec), do: schema
# ---------------------------------------------------------------------------
# NimbleOptions Schema Generation (body params)
# ---------------------------------------------------------------------------
defp generate_schema(func_name, properties, required_keys) do
opts =
properties
|> Enum.map(fn {name, prop_schema} ->
snake = to_snake_case(name)
type = openapi_to_nimble_type(prop_schema)
required = name in required_keys
doc = Map.get(prop_schema, "description", "")
parts = ["type: #{type}"]
parts = if required, do: parts ++ ["required: true"], else: parts
parts = if doc != "", do: parts ++ ["doc: #{inspect(doc)}"], else: parts
" #{snake}: [#{Enum.join(parts, ", ")}]"
end)
|> Enum.join(",\n")
" @#{func_name}_schema NimbleOptions.new!([\n#{opts}\n ])\n"
end
defp generate_key_mapping(func_name, properties) do
mappings =
properties
|> Enum.map(fn {name, _} ->
snake = to_snake_case(name)
"#{snake}: \"#{name}\""
end)
|> Enum.join(", ")
" @#{func_name}_key_mapping %{#{mappings}}\n"
end
# ---------------------------------------------------------------------------
# NimbleOptions Schema Generation (query params)
# ---------------------------------------------------------------------------
defp generate_query_schema(func_name, query_params) do
opts =
query_params
|> Enum.map(fn param ->
name = Map.get(param, "name")
snake = to_snake_case(name)
param_schema = Map.get(param, "schema", %{})
type = openapi_to_nimble_type(param_schema)
required = Map.get(param, "required", false)
doc = Map.get(param, "description", "")
parts = ["type: #{type}"]
parts = if required, do: parts ++ ["required: true"], else: parts
parts = if doc != "", do: parts ++ ["doc: #{inspect(doc)}"], else: parts
" #{snake}: [#{Enum.join(parts, ", ")}]"
end)
|> Enum.join(",\n")
" @#{func_name}_query_schema NimbleOptions.new!([\n#{opts}\n ])\n"
end
defp generate_query_key_mapping(func_name, query_params) do
mappings =
query_params
|> Enum.map(fn param ->
name = Map.get(param, "name")
snake = to_snake_case(name)
"#{snake}: \"#{name}\""
end)
|> Enum.join(", ")
" @#{func_name}_query_key_mapping %{#{mappings}}\n"
end
# ---------------------------------------------------------------------------
# OpenAPI → NimbleOptions type mapping
# ---------------------------------------------------------------------------
defp openapi_to_nimble_type(%{"type" => "string", "enum" => values}) when is_list(values) do
inspected = values |> Enum.map(&atom_literal/1) |> Enum.join(", ")
"{:in, [#{inspected}]}"
end
defp openapi_to_nimble_type(%{"type" => "string", "format" => "binary"}), do: ":any"
defp openapi_to_nimble_type(%{"type" => "string"}), do: ":string"
defp openapi_to_nimble_type(%{"type" => "integer"}), do: ":integer"
defp openapi_to_nimble_type(%{"type" => "number"}), do: "{:or, [:integer, :float]}"
defp openapi_to_nimble_type(%{"type" => "boolean"}), do: ":boolean"
defp openapi_to_nimble_type(%{"type" => "array", "items" => %{"type" => "string"}}) do
"{:list, :string}"
end
defp openapi_to_nimble_type(%{"type" => "array"}) do
"{:list, :any}"
end
# Object with no properties defined → arbitrary data (JSON Schema, headers, etc.)
defp openapi_to_nimble_type(%{"type" => "object", "properties" => _}), do: ":keyword_list"
defp openapi_to_nimble_type(%{"type" => "object"}), do: ":any"
# Ref-resolved objects with known properties
defp openapi_to_nimble_type(%{"properties" => _}), do: ":keyword_list"
# allOf composition — if any sub-schema has properties, treat as keyword_list
defp openapi_to_nimble_type(%{"allOf" => sub_schemas}) do
if Enum.any?(sub_schemas, fn s -> Map.has_key?(s, "properties") or Map.has_key?(s, "$ref") end) do
":keyword_list"
else
":any"
end
end
# anyOf — pick a reasonable type
defp openapi_to_nimble_type(%{"anyOf" => sub_schemas}) do
# Filter out null types
non_null = Enum.reject(sub_schemas, fn s -> Map.get(s, "type") == "null" end)
case non_null do
[single] -> openapi_to_nimble_type(single)
_ -> ":any"
end
end
defp openapi_to_nimble_type(%{"const" => _}), do: ":any"
defp openapi_to_nimble_type(_), do: ":any"
# ---------------------------------------------------------------------------
# Doc Generation
# ---------------------------------------------------------------------------
defp build_doc(
summary,
http_method,
path,
tag,
path_params,
query_param_names,
has_body,
func_name,
has_query_schema,
is_binary_response,
is_multipart
) do
bt = <<96>>
parts = [
" @doc \"\"\"",
" #{summary}",
"",
" #{bt}#{http_method} #{path}#{bt}"
]
parts = if tag != "", do: parts ++ ["", " Tag: #{tag}"], else: parts
parts =
if is_binary_response do
parts ++ ["", " **Returns binary audio/video data** — the response body is raw bytes (e.g. MP3/WAV)."]
else
parts
end
parts =
if is_multipart do
parts ++ ["", " **Multipart form data** — file uploads are supported via this endpoint."]
else
parts
end
parts =
if path_params != [] do
param_docs =
Enum.map(path_params, fn p ->
" * #{bt}#{to_snake_case(p)}#{bt} - Path parameter #{bt}#{p}#{bt}"
end)
parts ++ ["", " ## Path Parameters", ""] ++ param_docs
else
parts
end
parts =
if has_body do
parts ++
[
"",
" ## Parameters",
"",
" Validated by #{bt}NimbleOptions#{bt}. Pass params as a keyword list with snake_case keys.",
" See #{bt}@#{func_name}_schema#{bt} for the full schema."
]
else
parts
end
parts =
if has_query_schema do
param_docs =
Enum.map(query_param_names, fn p ->
" * #{bt}#{to_snake_case(p)}#{bt} — query parameter #{bt}#{p}#{bt}"
end)
parts ++ ["", " ## Query Parameters", ""] ++ param_docs
else
parts
end
parts =
parts ++
[
"",
" ## Returns",
"",
" * #{bt}{:ok, %Req.Response{}}#{bt} on success",
" * #{bt}{:error, exception}#{bt} on HTTP or validation failure",
" \"\"\""
]
Enum.join(parts, "\n")
end
defp doc_bang(func_name) do
bt = <<96>>
" @doc \"\"\"\n Bang variant of #{bt}#{func_name}#{bt}. Raises on error.\n \"\"\""
end
# ---------------------------------------------------------------------------
# Typespec Generation
# ---------------------------------------------------------------------------
defp build_typespec(func_name, path_params, has_params, bang?) do
name = if bang?, do: "#{func_name}!", else: func_name
return_type = if bang?, do: "Req.Response.t()", else: "response()"
path_types = Enum.map(path_params, fn _ -> "String.t()" end)
param_types = if has_params, do: ["keyword()"], else: []
all_types = path_types ++ param_types ++ ["keyword()"]
" @spec #{name}(#{Enum.join(all_types, ", ")}) :: #{return_type}"
end
# ---------------------------------------------------------------------------
# Function Body Generation
# ---------------------------------------------------------------------------
defp build_function_body(
func_name,
method,
path,
path_params,
_query_param_names,
has_body,
has_query_schema,
is_multipart,
bang?
) do
elixir_path = build_elixir_path(path, path_params)
req_method = String.to_atom(method)
fn_name = if bang?, do: "#{func_name}!", else: func_name
req_fn = if bang?, do: "#{req_method}!", else: "#{req_method}"
# Determine the body option key: :form_multipart for multipart, :json for JSON
body_opt = if is_multipart, do: "form_multipart", else: "json"
body_fn = if is_multipart, do: "to_multipart", else: "to_body"
cond do
# POST/PUT/PATCH with body, no path params
has_body and path_params == [] ->
sig = "def #{fn_name}(params \\\\ [], opts \\\\ []) do"
body =
if bang? do
"""
params = NimbleOptions.validate!(params, @#{func_name}_schema)
Req.#{req_fn}(client(opts), url: "#{elixir_path}", #{body_opt}: #{body_fn}(params, @#{func_name}_key_mapping))
end
"""
else
"""
with {:ok, params} <- NimbleOptions.validate(params, @#{func_name}_schema) do
Req.#{req_fn}(client(opts), url: "#{elixir_path}", #{body_opt}: #{body_fn}(params, @#{func_name}_key_mapping))
end
end
"""
end
{sig, body}
# POST/PUT/PATCH with body and path params
has_body and path_params != [] ->
args = Enum.map(path_params, &to_snake_case/1) |> Enum.join(", ")
sig = "def #{fn_name}(#{args}, params \\\\ [], opts \\\\ []) do"
body =
if bang? do
"""
params = NimbleOptions.validate!(params, @#{func_name}_schema)
Req.#{req_fn}(client(opts), url: "#{elixir_path}", #{body_opt}: #{body_fn}(params, @#{func_name}_key_mapping))
end
"""
else
"""
with {:ok, params} <- NimbleOptions.validate(params, @#{func_name}_schema) do
Req.#{req_fn}(client(opts), url: "#{elixir_path}", #{body_opt}: #{body_fn}(params, @#{func_name}_key_mapping))
end
end
"""
end
{sig, body}
# GET/DELETE with path params and query params (validated)
path_params != [] and has_query_schema ->
args = Enum.map(path_params, &to_snake_case/1) |> Enum.join(", ")
sig = "def #{fn_name}(#{args}, params \\\\ [], opts \\\\ []) do"
body =
if bang? do
"""
params = NimbleOptions.validate!(params, @#{func_name}_query_schema)
Req.#{req_fn}(client(opts), url: "#{elixir_path}", params: to_query(params, @#{func_name}_query_key_mapping))
end
"""
else
"""
with {:ok, params} <- NimbleOptions.validate(params, @#{func_name}_query_schema) do
Req.#{req_fn}(client(opts), url: "#{elixir_path}", params: to_query(params, @#{func_name}_query_key_mapping))
end
end
"""
end
{sig, body}
# GET/DELETE with path params only
path_params != [] ->
args = Enum.map(path_params, &to_snake_case/1) |> Enum.join(", ")
sig = "def #{fn_name}(#{args}, opts \\\\ []) do"
body = " Req.#{req_fn}(client(opts), url: \"#{elixir_path}\")\n end\n"
{sig, body}
# GET/DELETE without path params but with query params (validated)
has_query_schema ->
sig = "def #{fn_name}(params \\\\ [], opts \\\\ []) do"
body =
if bang? do
"""
params = NimbleOptions.validate!(params, @#{func_name}_query_schema)
Req.#{req_fn}(client(opts), url: "#{elixir_path}", params: to_query(params, @#{func_name}_query_key_mapping))
end
"""
else
"""
with {:ok, params} <- NimbleOptions.validate(params, @#{func_name}_query_schema) do
Req.#{req_fn}(client(opts), url: "#{elixir_path}", params: to_query(params, @#{func_name}_query_key_mapping))
end
end
"""
end
{sig, body}
# No params at all
true ->
sig = "def #{fn_name}(opts \\\\ []) do"
body = " Req.#{req_fn}(client(opts), url: \"#{elixir_path}\")\n end\n"
{sig, body}
end
end
defp build_elixir_path(path, []), do: path
defp build_elixir_path(path, path_params) do
Enum.reduce(path_params, path, fn param, acc ->
snake = to_snake_case(param)
String.replace(acc, "{#{param}}", "\#{#{snake}}")
end)
end
# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------
# Returns a valid atom literal string. Quotes values with special chars.
defp atom_literal(value) do
if Regex.match?(~r/^[a-zA-Z_][a-zA-Z0-9_]*$/, value) do
":#{value}"
else
":\"#{value}\""
end
end
defp to_snake_case(str) do
str
# Normalize plural acronyms: "URLs" → "Urls", "IDs" → "Ids"
|> String.replace(~r/([A-Z]{2,})s/, fn match ->
first = String.first(match)
rest = match |> String.slice(1..-1//1) |> String.downcase()
first <> rest
end)
|> String.replace(~r/([a-z\d])([A-Z])/, "\\1_\\2")
|> String.replace(~r/([A-Z]+)([A-Z][a-z])/, "\\1_\\2")
|> String.downcase()
|> String.replace(~r/[^a-z0-9_]/, "_")
|> String.replace(~r/_+/, "_")
|> String.trim_leading("_")
|> String.trim_trailing("_")
end
defp bump_version do
content = File.read!(@mix_file)
case Regex.run(~r/@version\s+"(\d+)\.(\d+)\.(\d+)"/, content) do
[full_match, major, minor, patch] ->
new_patch = String.to_integer(patch) + 1
new_version = "#{major}.#{minor}.#{new_patch}"
new_content = String.replace(content, full_match, ~s|@version "#{new_version}"|)
File.write!(@mix_file, new_content)
IO.puts("Bumped version to #{new_version}")
_ ->
IO.puts("Warning: could not find @version in #{@mix_file} — skipping version bump.")
end
end
end
ElevenLabs.Generator.run()