{"id":30904544,"url":"https://github.com/gastonmorixe/elevenlabs-reader-cli","last_synced_at":"2025-10-11T02:07:59.376Z","repository":{"id":310869145,"uuid":"1041564147","full_name":"gastonmorixe/elevenlabs-reader-cli","owner":"gastonmorixe","description":"Unofficial ElevenLabs Reader CLI: create, stream, and play TTS with live karaoke","archived":false,"fork":false,"pushed_at":"2025-08-20T17:36:02.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-09T10:37:09.707Z","etag":null,"topics":["ai","cli","elevenlabs","elevenlabs-api","llm","reverse-engineering","speech","text-to-speech","tts","unofficial-api"],"latest_commit_sha":null,"homepage":"https://elevenreader.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gastonmorixe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-20T17:12:01.000Z","updated_at":"2025-08-20T21:19:00.000Z","dependencies_parsed_at":"2025-08-20T19:29:11.424Z","dependency_job_id":"48f314db-7406-47f1-8ce3-48004be8d606","html_url":"https://github.com/gastonmorixe/elevenlabs-reader-cli","commit_stats":null,"previous_names":["gastonmorixe/elevenlabs-reader-cli"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/gastonmorixe/elevenlabs-reader-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Felevenlabs-reader-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Felevenlabs-reader-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Felevenlabs-reader-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Felevenlabs-reader-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gastonmorixe","download_url":"https://codeload.github.com/gastonmorixe/elevenlabs-reader-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gastonmorixe%2Felevenlabs-reader-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005917,"owners_count":26083995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-11T02:00:06.511Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","cli","elevenlabs","elevenlabs-api","llm","reverse-engineering","speech","text-to-speech","tts","unofficial-api"],"created_at":"2025-09-09T09:34:40.319Z","updated_at":"2025-10-11T02:07:59.347Z","avatar_url":"https://github.com/gastonmorixe.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🎤 elevenlabs-reader-cli - Unofficial ElevenLabs Reader for the terminal\n\nA command-line client for interacting with ElevenLabs Reader using the same backend the iOS app uses (unofficial). It supports creating Reader documents, streaming them over WebSocket with real-time playback, and basic library operations. Authentication mirrors the app (Firebase refresh token, App Check token, device-id) and uses your ElevenLabs account via the Reader pipeline.\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [⚠️ Technical Requirements](#technical-requirements)\n- [Features](#features)\n- [Project Structure](#project-structure)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n  - [1. Get Firebase Refresh Token](#1-get-firebase-refresh-token)\n  - [2. Easy Usage (No Token Typing!)](#2-easy-usage-no-token-typing)\n  - [3. Manual Usage (If You Want Full Control)](#3-manual-usage-if-you-want-full-control)\n  - [4. Test the Implementation](#4-test-the-implementation)\n  - [5. App Check Token and Device ID (Reader WS)](#5-app-check-token-and-device-id-reader-ws)\n- [How It Works](#how-it-works)\n  - [Reader App Workflow](#reader-app-workflow)\n  - [Reader Streaming Internals (Multi‑Connection)](#reader-streaming-internals-multi-connection)\n  - [Karaoke Preview](#karaoke-preview)\n  - [Key Differences from Official API](#key-differences-from-official-api)\n- [Arguments](#arguments)\n- [Streaming Methods](#streaming-methods)\n  - [Reader WebSocket (unofficial) — default](#reader-websocket-unofficial--default)\n  - [HTTP Streaming](#http-streaming)\n  - [Direct WebSocket](#direct-websocket)\n  - [Auto Fallback](#auto-fallback)\n- [Token Management](#token-management)\n  - [Practical Usage](#practical-usage)\n  - [TokenManager Class API](#tokenmanager-class-api)\n  - [Token Security Considerations](#token-security-considerations)\n- [Verbose Logging](#verbose-logging)\n- [Authentication Flow Details](#authentication-flow-details)\n  - [Firebase Token Architecture](#firebase-token-architecture)\n  - [Firebase Token Refresh Implementation](#firebase-token-refresh-implementation)\n  - [Reader API Headers](#reader-api-headers)\n- [Error Handling](#error-handling)\n- [Security Considerations](#security-considerations)\n- [Security \u0026 Ethics](#security--ethics)\n- [Troubleshooting](#troubleshooting)\n  - [Common Issues](#common-issues)\n  - [Debug Mode](#debug-mode)\n- [What We Fixed (Reader Streaming)](#what-we-fixed-reader-streaming)\n- [Recent Improvements](#recent-improvements)\n- [Limitations](#limitations)\n- [Development Status](#development-status)\n- [Contributing](#contributing)\n\n---\n\n## Overview\n\nCommand-line utility for the ElevenLabs Reader workflow:\n\n- Create documents and stream them via the Reader WebSocket protocol\n- Stream existing Reader documents from your library\n- Real-time playback and optional text preview (karaoke-style)\n- Token management matching the app’s authentication flow\n\n## ⚠️ Technical Requirements\n\nThis tool reverse engineers the **private Reader app API** (not the official public API). It requires:\n\n- Firebase refresh tokens from the Reader mobile app\n- Proper authentication flow matching the captured network traffic\n- Document processing workflow (create → process → stream)\n\n## Features\n\n- Reader WebSocket streaming (unofficial; mirrors the app)\n  - Firebase access tokens + Firebase App Check token (xi-app-check-token)\n  - Reader-specific headers including `device-id` and `Origin`\n  - Multi-connection streaming pattern with rollover\n- Authentication and token management (Firebase refresh → access token)\n- Multiple streaming methods: Reader WebSocket (default), HTTP, official WebSocket, Auto fallback\n- Document creation and voice listing\n- Real-time playback using `mpv` (preferred) or `ffplay`\n- Audio export to MP3\n- Verbose logging for debugging\n- Live text preview (karaoke) with `--karaoke` and `--karaoke-before/--karaoke-after`\n\n## Project Structure\n\n```\nelevenlabs/\n├── elevenlabs_tts_client.py        # Main CLI client\n├── tts                             # Wrapper script (auto-extracts/caches tokens)\n├── token_manager.py                # Firebase token management\n├── utils/                          # Flow processing/analysis helpers\n│   ├── ws_dump.py\n│   ├── ws_dump2.py\n│   └── ws_flows_to_jsonl_redact.py\n├── get_refresh_token.py            # Refresh token extraction helper\n├── get_app_check_token.py          # Extract Firebase App Check token\n├── get_device_id.py                # Extract device-id\n├── extract_tokens.py               # Full token extractor from flows\n├── analyze_flows.py                # Flow analysis utilities\n├── fix_websocket_method.py         # Reader WS method adjustments (dev)\n├── reader_websocket_implementation.py # Reference Reader WS implementation (dev)\n├── tests/                          # Test suite (script-style)\n│   ├── test_basic.py\n│   ├── test_client.py\n│   ├── test_create_and_wait.py\n│   ├── test_direct_read_id.py\n│   ├── test_existing_read.py\n│   ├── test_reader_api.py\n│   └── test_user_reads.py\n├── test-results/                   # Outputs from sample/test runs\n├── examples.sh                     # Usage examples and methods\n├── requirements.txt                # Python dependencies\n├── AGENTS.md                       # Contributor guidelines\n├── TODOS.md                        # Work-in-progress notes\n└── README.md                       # Main documentation\n```\n\nNote: Temporary and large captured data should live under `tmp/` (git-ignored). Sensitive artifacts like `tokens_cache.json` and `flows.*` are ignored via `.gitignore`.\n\n## Installation\n\n```bash\npip install -r requirements.txt\nchmod +x elevenlabs_tts_client.py\nchmod +x tts\n```\n\n## Quick Start\n\n### 1. Get Firebase Refresh Token\n\nYou need a Firebase refresh token from the ElevenLabs Reader mobile app. This can be extracted from network traffic or device storage.\n\n### 2. Easy Usage (No Token Typing!)\n\nWe provide multiple convenient ways to avoid typing the long Firebase refresh token every time:\n\n#### Method 1: TTS wrapper script (easiest)\n\n```bash\n# Auto-extracts token from cache - just run once to set up tokens\n./tts --list-voices\n\n# List your Reader documents\n./tts --list-reads\n\n# Reader WebSocket streaming\n./tts --voice-id \"nPczCjzI2devNBz1zQrb\" --text \"Hello world!\" --method reader --play\n\n# HTTP streaming (instant)\n./tts --voice-id \"2EiwWnXFnvU5JabPnv8n\" --text \"Instant results\" --method http --play\n\n# Auto method (tries Reader, falls back to HTTP)\n./tts --voice-id \"nPczCjzI2devNBz1zQrb\" --text \"Auto selection\" --method auto --play\n```\n\n#### Method 2: Command substitution\n\n```bash\n# Extract token inline with $(command):\npython elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"$(python get_refresh_token.py)\" \\\n  --voice-id \"nPczCjzI2devNBz1zQrb\" \\\n  --text \"Command substitution test\" \\\n  --method reader \\\n  --play\n```\n\n#### Method 3: Environment variable\n\n```bash\n# Set once in your session:\nexport FIREBASE_REFRESH_TOKEN=$(python get_refresh_token.py)\n\n# Then use it multiple times:\npython elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"$FIREBASE_REFRESH_TOKEN\" \\\n  --voice-id \"nPczCjzI2devNBz1zQrb\" \\\n  --text \"Environment variable test\" \\\n  --method auto \\\n  --play\n```\n\n#### Method 4: Shell alias (permanent)\n\n```bash\n# Add to ~/.bashrc or ~/.zshrc:\nalias tts11='python elevenlabs_tts_client.py --firebase-refresh-token \"$(python get_refresh_token.py)\"'\n\n# Then use anywhere:\ntts11 --voice-id \"nPczCjzI2devNBz1zQrb\" --text \"Alias test\" --play\n```\n\n### 3. Manual Usage (If You Want Full Control)\n\n```bash\n# List available voices\n./elevenlabs_tts_client.py --firebase-refresh-token \"your_token\" --list-voices\n\n# List your Reader documents (reads)\n./elevenlabs_tts_client.py --firebase-refresh-token \"your_token\" --list-reads\n\n# Reader WebSocket streaming (default — create and stream documents)\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"your_token\" \\\n  --voice-id \"voice_id_from_list\" \\\n  --text \"Any text you want\" \\\n  --method reader \\\n  --play\n\n# Live text preview (karaoke-like) while streaming a document (new or existing)\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"your_token\" \\\n  --read-id \"u:READ_ID\" \\\n  --voice-id \"voice_id_from_list\" \\\n  --method reader \\\n  --play \\\n  --karaoke\n\n# Or create from new text and preview karaoke live (stdin)\npbpaste | ./tts --voice-id \"voice_id_from_list\" --method reader --play --karaoke\n\n# Tip: start without saving chunks for best real-time preview\n# ./elevenlabs_tts_client.py ... --karaoke   # (no --save-chunks)\n\n# HTTP streaming (official API)\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"your_token\" \\\n  --voice-id \"voice_id\" \\\n  --text \"Instant audio generation\" \\\n  --method http \\\n  --output speech.mp3\n\n# Auto method selection (tries Reader first, then falls back)\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"your_token\" \\\n  --voice-id \"voice_id\" \\\n  --file input.txt \\\n  --method auto \\\n  --output speech.mp3\n\n# Batch processing (recommended workflow)\necho \"Chapter 1 content...\" \u003e chapter1.txt\necho \"Chapter 2 content...\" \u003e chapter2.txt\n./elevenlabs_tts_client.py --firebase-refresh-token \"your_token\" --voice-id \"voice_id\" --file chapter1.txt --method reader --output chapter1.mp3\n./elevenlabs_tts_client.py --firebase-refresh-token \"your_token\" --voice-id \"voice_id\" --file chapter2.txt --method reader --output chapter2.mp3\n```\n\n### 4. Test the Implementation\n\n```bash\n# Quick test using wrapper\n./tts --list-voices\n\n# Or run comprehensive tests\npython tests/test_basic.py\npython tests/test_reader_api.py\n```\n\n### 5. App Check Token and Device ID (Reader WS)\n\nThe Reader WebSocket requires an additional Firebase App Check token header:\n\n- Header: `xi-app-check-token: \u003cJWT\u003e`\n- Also include: `device-id: \u003cUUID\u003e` and `Origin: https://elevenlabs.io`\n\nThe wrapper `./tts` will try to auto-extract both from `flows.elevenlabsio` and cache them in `tokens_cache.json`:\n\n- App Check: `python get_app_check_token.py [flows.elevenlabsio]`\n- Device ID: `python get_device_id.py [flows.elevenlabsio]`\n\nPass manually if needed: `--app-check-token \"\u003ctoken\u003e\" --device-id \"\u003cuuid\u003e\"`. Tokens expire; update periodically from fresh captures.\n\n#### Firebase App Check Token\n\n- Header name: `xi-app-check-token`\n- Purpose: short‑lived attestation generated by the Reader app via Firebase App Check SDK\n- Requirement: mandatory for Reader WebSocket; missing/expired → 403\n- How to obtain: capture with mitmproxy from the Reader app or use `get_app_check_token.py`; `./tts` auto-extracts from `flows.elevenlabsio`\n- Caching: persisted in `tokens_cache.json` once provided\n\n#### Device ID\n\n- Header name: `device-id`\n- Purpose: stable UUID used by the Reader app; keep consistent across requests\n- How to obtain: captured from flows or generated on first run and cached; override with `--device-id`\n\n## How It Works\n\n### Reader App Workflow\n\n1. **Firebase Authentication**\n\n   ```\n   Firebase Refresh Token → securetoken.googleapis.com → Bearer Token\n   ```\n\n2. **Content Creation**\n\n   ```\n   POST /v1/reader/reads → Create read content → Get read_id\n   ```\n\n3. **Audio Streaming**\n\n```\nWebSocket /v1/reader/reads/stream/{read_id}?voice_id={voice_id}\nHeaders:\n  Authorization: Bearer \u003cfirebase access_token\u003e\n  xi-app-check-token: \u003cFirebase App Check JWT\u003e\n  device-id: \u003cUUID\u003e\n  Origin: https://elevenlabs.io\n  User-Agent: readerapp/405 CFNetwork/3860.100.1 Darwin/25.0.0\n→ Audio chunks (base64 in JSON)\n```\n\n### Reader Streaming Internals (Multi‑Connection)\n\n- The Reader app streams long documents across multiple WebSocket connections.\n- Each connection streams a segment; server messages include `alignment` with per‑message character spans.\n- The client accumulates processed characters in the connection, PATCHes progress to the REST API, and proactively reconnects:\n  - at a budget boundary (~1.3–1.4k chars), or\n  - on short idle (≈1.5s) after receiving content in the current connection (reduces audible gaps).\n- The server may send `isFinal: true` to end a connection; the client keeps reconnecting from the last played position until the document end.\n\nObserved positions from the real app for a 4,227‑char doc:\n\n- 0 → 1,348 → 2,750 → 4,085 → final (isFinal: true)\n\nWhat this client does:\n\n- Accepts only non‑overlapping aligned blocks for playback and progress.\n- Advances durable position (`abs_char_pos`) strictly from audio actually accepted/played (prevents skips).\n- PATCHes `last_listened_char_offset` mid‑connection after accepted blocks and between connections.\n- Proactively rolls the WebSocket when cumulative chars cross ~1,348 or when short idle occurs after receiving content.\n- Reconnects with `{\"stream_id\": \"UUID\", \"position\": \u003cabs_char_pos\u003e}` until reaching the document `char_count`.\n\nFlags to help debug/validate:\n\n- `--save-chunks`: writes each received audio chunk to `chunk_XXX_\u003cread\u003e_connY_posZ.mp3` with connection/position metadata.\n- `--verbose`: logs exact WS headers, messages, alignment counts, and PATCH progress.\n\n### Karaoke Preview\n\n- Overview: Shows a live, word-by-word subtitle in your terminal during streaming.\n- Styling: Current word is bold white; surrounding context is gray. Single-line in-place updates (no scrolling).\n- Source of truth: Uses the WebSocket `alignment` (per‑block `chars` + timing) for exact spoken content; does not rely on global HTML.\n- Timing: A centralized ticker chains timing between blocks to avoid drift or “missing words” at boundaries.\n- Best practice: For tightest sync, avoid `--save-chunks` (file I/O can compete for compute); use only `--karaoke`.\n\nUsage (works for both newly created documents via --text/--file/stdin and existing --read-id):\n\n```bash\n./tts --read-id \"u:READ_ID\" --voice-id \"VOICE\" --method reader --play --karaoke\n# or pipe new text\npbpaste | ./tts --voice-id \"VOICE\" --method reader --play --karaoke\n# Optional: widen context\n./tts ... --karaoke --karaoke-before 12 --karaoke-after 12\n```\n\nNotes:\n\n- ANSI colors must be supported by your terminal.\n- If your terminal is not color-capable or strips ANSI, you may see raw escapes; try another terminal or disable colors.\n\n### Key Differences from Official API\n\n| Feature        | Official API            | Reader API                                    |\n| -------------- | ----------------------- | --------------------------------------------- |\n| Authentication | API Keys (`xi-api-key`) | Firebase Bearer tokens                        |\n| Endpoints      | `/v1/text-to-speech/`   | `/v1/reader/`                                 |\n| Workflow       | Direct streaming        | Content creation → Streaming                  |\n| WebSocket URL  | `/stream-input`         | `/reads/stream/{read_id}`                     |\n| Headers        | Standard API headers    | Reader app headers with Device-ID + App Check |\n\n## Arguments\n\n| Argument                   | Required | Description                                                             |\n| -------------------------- | -------- | ----------------------------------------------------------------------- |\n| `--firebase-refresh-token` | one of   | Firebase refresh token from Reader app                                  |\n| `--bearer-token`           | one of   | Manually provide a Bearer token (mutually exclusive with refresh token) |\n| `--voice-id`               | required | Voice ID (use --list-voices to see options)                             |\n| `--text`                   | ❌       | Text to convert (or use --file/stdin)                                   |\n| `--file`                   | ❌       | File containing text to convert                                         |\n| `--output`                 | ❌       | Output audio file path                                                  |\n| `--play`                   | ❌       | Play audio after generation (real-time via mpv/ffplay)                  |\n| `--save-chunks`            | ❌       | Save each audio chunk to separate files                                 |\n| `--position`               | ❌       | Start position for existing reads (default: 0)                          |\n| `--read-id`                | ❌       | Stream from an existing read document ID                                |\n| `--list-voices`            | ❌       | List available voices                                                   |\n| `--list-reads`             | ❌       | List your Reader documents (library)                                    |\n| `--verbose`, `-v`          | ❌       | Enable detailed logging                                                 |\n| `--cache-file`             | ❌       | Token cache file (default: tokens_cache.json)                           |\n| `--clear-cache`            | ❌       | Clear token cache and exit                                              |\n| `--method`                 | ❌       | Streaming method: `reader` (default), `http`, `websocket`, `auto`       |\n| `--app-check-token`        | ❌       | Firebase App Check token for Reader WS (xi-app-check-token)             |\n| `--karaoke`                | ❌       | Show live text preview (karaoke-like) during streaming                  |\n| `--karaoke-before`         | ❌       | Words to show before current word (default: 8)                          |\n| `--karaoke-after`          | ❌       | Words to show after current word (default: 8)                           |\n| `--device-id`              | ❌       | Override the Reader Device ID (UUID)                                    |\n\nNotes: Provide exactly one of `--firebase-refresh-token` or `--bearer-token`.\n\n## Streaming Methods\n\n### Reader WebSocket (unofficial) — default\n\n- **Command**: `--method reader` (default)\n- Use case: Any text → document creation → WebSocket streaming\n- Workflow: Create document → wait for processing → stream\n- Real-time playback: streams to `mpv`/`ffplay` when `--play` is set\n- Streaming: multi‑connection with rollover and periodic progress PATCH\n- Auth requirements: Firebase access token + xi-app-check-token + device-id\n\n### HTTP Streaming\n\n- **Command**: `--method http`\n- Use case: Any custom text generation\n- Real-time playback: streams chunks live to `mpv`/`ffplay` when `--play` is set\n- Limitations: standard API rate limits\n- Reliability: official API method\n\n### Direct WebSocket\n\n- **Command**: `--method websocket`\n- Use case: Real-time streaming with official API\n- Limitations: standard API rate limits\n- Reliability: official API method\n\n### Auto Fallback\n\n- **Command**: `--method auto`\n- Behavior: Tries Reader → HTTP → WebSocket in order\n- Use case: Maximum compatibility\n- Recommendation: useful for development/testing\n\n## Token Management\n\n### Practical Usage\n\n**You only need to provide the Firebase Refresh Token once:**\n\n```bash\n# First time - provide refresh token\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"AMf-vBw6ZWBpOHOOs-iI7...\" \\\n  --voice-id \"nPczCjzI2devNBz1zQrb\" \\\n  --text \"Hello world\" \\\n  --method reader \\\n  --play\n\n# All subsequent runs - uses cached tokens automatically\n./elevenlabs_tts_client.py \\\n  --firebase-refresh-token \"AMf-vBw6ZWBpOHOOs-iI7...\" \\\n  --voice-id \"voice_id\" \\\n  --text \"More text\" \\\n  --play\n```\n\n**What happens behind the scenes:**\n\n- Creates a Bearer token and caches it (valid ~1 hour)\n- Automatically refreshes the Bearer token when expired\n- Refresh token remains valid long-term and is reused to obtain new access tokens\n\n### TokenManager Class API\n\n```python\nfrom token_manager import TokenManager\n\n# Create manager\nmanager = TokenManager(verbose=True)\n\n# Get fresh Bearer token (auto-refreshes if needed)\nbearer_token = manager.get_fresh_bearer_token(\"firebase_refresh_token\")\n\n# Check cache status\ninfo = manager.get_cache_info()\nprint(f\"Bearer token expires at: {info['bearer_token_expires_at']}\")\nprint(f\"Token expired: {info['bearer_token_expired']}\")\n\n# Clear cache if needed\nmanager.clear_cache()\n```\n\n### Token Security Considerations\n\n| Token Type        | Sensitivity   | Best Practices                                                             |\n| ----------------- | ------------- | -------------------------------------------------------------------------- |\n| **Refresh Token** | 🔴 **HIGH**   | • Store securely\u003cbr\u003e• Don't share publicly\u003cbr\u003e• Treat like a password      |\n| **Bearer Token**  | 🟡 **MEDIUM** | • Auto-expires in 1 hour\u003cbr\u003e• Cached temporarily\u003cbr\u003e• Lower risk if leaked |\n\n**Security Notes:**\n\n- 🔒 Refresh tokens are more sensitive than Bearer tokens\n- ⚠️ Command line arguments are visible in process lists\n- 💡 Consider using environment variables for automation:\n\n  ```bash\n  export FIREBASE_REFRESH_TOKEN=\"your_token_here\"\n  ./elevenlabs_tts_client.py --firebase-refresh-token \"$FIREBASE_REFRESH_TOKEN\" ...\n  ```\n\n## Verbose Logging\n\nEnable detailed logging to debug API calls:\n\n```bash\n./elevenlabs_tts_client.py --verbose --firebase-refresh-token \"token\" --list-voices\n```\n\nLogs include:\n\n- Firebase token refresh operations\n- Content creation requests/responses\n- WebSocket connection details (authorization preview, headers)\n- Audio chunk reception progress\n- Network timing and error details\n\n## Authentication Flow Details\n\n### Firebase Token Architecture\n\nThe ElevenLabs Reader app uses Firebase's standard authentication system with three token types:\n\n#### **1. Firebase Refresh Token** 🔑\n\n- **Purpose**: Long-lived credential for getting new access tokens\n- **Lifetime**: **Months/Years** (doesn't expire automatically)\n- **Storage**: **Persistent** - cached in `tokens_cache.json`\n- **Usage**: You provide this once via `--firebase-refresh-token`\n- **Security**: High sensitivity - acts like a \"master key\"\n\n#### **2. Bearer Token (Firebase Access Token)** ⏰\n\n- **Purpose**: Short-lived JWT for API authentication\n- **Lifetime**: **1 hour** (`expires_in: 3600` seconds)\n- **Storage**: **Temporary cache** with automatic expiration tracking\n- **Usage**: Automatically generated/refreshed using refresh token\n- **Security**: Medium sensitivity - expires quickly\n\n#### **3. Token Management Flow**\n\n```\nFirebase Refresh Token (long-lived, you provide once)\n        ↓ (when Bearer token expires)\nFirebase API refresh call\n        ↓ (returns new 1-hour token)\nBearer Token (used for all API calls)\n        ↓ (in Authorization header)\nElevenLabs Reader API access\n```\n\n#### **Cache Structure Example**\n\n```json\n{\n  \"firebase_refresh_token\": \"AMf-vBw6ZWBpOHOOs-iI7...\", // 🔑 PERSISTENT\n  \"bearer_token_data\": {\n    \"bearer_token\": \"eyJhbGciOiJSUzI1NiIs...\", // ⏰ 1-HOUR EXPIRY\n    \"expires_in\": 3600,\n    \"expires_at\": \"2025-08-17T19:37:51.161875\",\n    \"refreshed_at\": \"2025-08-17T18:37:51.161937\",\n    \"token_type\": \"Bearer\"\n  }\n}\n```\n\n#### **Automatic Token Refresh**\n\n- **First run**: Creates Bearer token (valid 1 hour)\n- **Subsequent runs**: Reuses cached Bearer token if valid\n- **After expiry**: Auto-refreshes Bearer token using Refresh token\n- **No user action needed**: Completely transparent\n\n### Firebase Token Refresh Implementation\n\n```python\n# Endpoint matching Reader app\nurl = \"https://securetoken.googleapis.com/v1/token?key=AIzaSyDhSxLJa_WaR8I69a1BmlUG7ckfZHu7-ig\"\n\n# Headers matching captured traffic\nheaders = {\n    \"User-Agent\": \"FirebaseAuth.iOS/11.14.0 io.elevenlabs.readerapp/1.4.45 iPhone/26.0 hw/iPhone16_2\",\n    \"X-Client-Version\": \"iOS/FirebaseSDK/11.14.0/FirebaseCore-iOS\",\n    \"X-iOS-Bundle-Identifier\": \"io.elevenlabs.readerapp\"\n}\n\n# Payload for refresh\n{\n  \"grant_type\": \"refresh_token\",\n  \"refresh_token\": \"your_firebase_refresh_token\"\n}\n```\n\n### Reader API Headers\n\n```python\nheaders = {\n    \"User-Agent\": \"readerapp/405 CFNetwork/3860.100.1 Darwin/25.0.0\",\n    \"App-Version\": \"1.4.45\",\n    \"Device-ID\": \"generated-uuid\",\n    \"Language\": \"en\",\n    \"User-Timezone\": \"America/Montevideo\",\n    \"Authorization\": \"Bearer firebase_access_token\"\n}\n```\n\n## Error Handling\n\n- Automatic token refresh when expired\n- Network retry logic for transient failures\n- Detailed error messages with troubleshooting hints\n- Graceful fallback between authentication methods\n\n## Security Considerations\n\n- 🔒 Firebase refresh tokens cached securely\n- 🔒 Bearer tokens auto-expire (1 hour)\n- 🔒 All API calls use TLS encryption\n- ⚠️ Tokens visible in command line history\n\n## Security \u0026 Ethics\n\n- Legitimate use cases: stream content from your own Reader library and documents you have permission to access.\n- Private API: this integrates the Reader app’s private endpoints; stability is not guaranteed.\n- Token handling: keep refresh tokens secret; rotate if leaked; prefer environment variables over plain CLI args.\n- Responsibility: ensure your usage complies with ElevenLabs’ Terms and applicable law.\n\n## Troubleshooting\n\n### Common Issues\n\n1. **Authentication Failed**\n\n   ```bash\n   # Clear token cache and retry\n   ./elevenlabs_tts_client.py --clear-cache\n   ```\n\n2. **WebSocket Connection Failed**\n\n```bash\n# Enable verbose logging to debug\n./elevenlabs_tts_client.py --verbose ...\n```\n\n- 403 Forbidden causes:\n  - Missing xi-app-check-token (pass via --app-check-token)\n  - Expired App Check token (capture a fresh one)\n  - Mismatched or missing device-id (ensure stable UUID)\n  - Token about to expire (tool force-refreshes pre-WS; check logs)\n\n3. **Audio Playback Failed**\n   - Ensure `mpv` (preferred) or `ffplay` is installed and in PATH\n   - Otherwise, use `--output` to save audio and play with your system player\n\n### Debug Mode\n\n```bash\n# Maximum verbosity for troubleshooting\n./elevenlabs_tts_client.py --verbose --firebase-refresh-token \"token\" --text \"test\" --voice-id \"voice\"\n```\n\n## What We Fixed (Reader Streaming)\n\n- Fix: Streaming stopped after ~2 chunks with an early `isFinal: true`.\n- Cause: We didn’t mirror the mobile app’s multi‑connection rollover; stayed on the first WS too long.\n- Solution:\n  - Accept/play only non‑overlapping aligned blocks; advance durable position from audio actually played.\n  - PATCH `last_listened_char_offset` during streaming and between connections.\n  - Proactively reconnect after ~1.3–1.4k chars per connection or on short idle (≈1.5s) to avoid long gaps.\n  - Stop when reaching the document `char_count` (queried from REST), not solely on `isFinal`.\n- Result: Full document streams smoothly over multiple WS connections (31 chunks observed), with clean real‑time playback and saved chunk files when `--save-chunks` is used.\n\n## Recent Improvements\n\n- Multi-connection streaming: Implemented budget-based (~1.3–1.4k chars) and idle-based (~1.5s) rollovers with mid‑connection PATCH updates and safe retries. Prevents long gaps and ensures seamless continuation.\n- Ordered alignment playback: Karaoke preview is driven by a central ticker that chains timing across blocks, avoiding gaps or overlaps at boundaries (no “missing early words” on joins).\n- Non-blocking I/O: Audio feeding, base64 decode, and chunk saving are moved off the event loop to keep timing accurate.\n- Clean preview UX: In-place single-line updates (bold white current word, gray context) without noisy progress dots.\n\n## Limitations\n\n- Reader app private API (not officially supported)\n- Requires reverse-engineered Firebase tokens\n- Real-time playback requires `mpv` or `ffplay` (else save to file)\n- No official warranty or support\n\n## Development Status\n\n- Reader WebSocket streaming implemented with multi-connection rollover and progress PATCH\n- Authentication via Firebase refresh token → access token, with caching and refresh\n- Document creation workflow working against `/v1/reader/reads/add/v2`\n- Multiple methods supported: Reader, HTTP, official WebSocket, Auto\n- Karaoke preview driven by alignment data\n- Ongoing: optimize new document processing readiness/polling\n\n## Contributing\n\nContributions are welcome. High-impact areas:\n\n- Processing timing optimization for new documents (reduce initial 403s)\n- Cross-platform audio playback improvements and fallbacks\n- Additional Reader API endpoints and capabilities\n- Performance improvements (I/O, buffering, concurrency)\n- Documentation, examples, and test coverage\n\nOpen an issue to discuss approach before larger changes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgastonmorixe%2Felevenlabs-reader-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgastonmorixe%2Felevenlabs-reader-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgastonmorixe%2Felevenlabs-reader-cli/lists"}