A self-hostable clone of Huxe — the AI audio app shutting down 2026-05-28. Produces a daily personalized podcast briefing from your Gmail, Google Calendar, and RSS feeds, with two-host NotebookLM-style dialogue and an RSS feed any podcast client can subscribe to.
Built on OSS components: Podcastfy for the audio pipeline, edge-tts (free, keyless default) or your own local OmniVoice Studio for voices, LiteLLM for model routing (Gemini 2.5 Pro by default, Claude as fallback), and the standard Google API client for inbox + calendar access.
# 1. install
git clone https://github.com/broomva/hexu && cd hexu
uv sync
# 2. configure (edit values)
cp .env.example .env
# 3. authorize Gmail + Calendar (one-time)
uv run hexu setup-oauth
# 4. run the first briefing
uv run hexu brief
# 5. serve the feed locally + subscribe in any podcast app
uv run hexu serve
# → http://localhost:8080/feed.xmlsources → context → dialogue → audio → publish
───── ─────── ──────── ───── ───────
Gmail LiteLLM TTS RSS feed
Calendar → Gemini 2.5 Sesame feedgen
RSS feeds (Claude Kokoro Local / S3
fallback) Podcastfy
Each stage is a pluggable layer:
| Stage | Module | Default | Alternatives |
|---|---|---|---|
fetchers.gmail |
Gmail API | — | |
| Calendar | fetchers.calendar |
Google Calendar API | — |
| News | fetchers.rss |
feedparser | — |
| LLM | dialogue.generator |
gemini/gemini-2.5-pro |
any LiteLLM provider |
| TTS | tts.* |
podcastfy |
omnivoice (local, recommended), sesame (GPU), kokoro (CPU ONNX) |
| Feed | feed.publisher |
feedgen → iTunes-compatible RSS | minimal RSS fallback |
| Storage | feed.storage |
local |
s3 (AWS, R2, MinIO) |
All settings load from environment variables or .env (see .env.example).
| Variable | Required? | Default | Meaning |
|---|---|---|---|
GEMINI_API_KEY |
yes (if Gemini) | "" |
Google AI Studio key for Gemini 2.5 Pro |
ANTHROPIC_API_KEY |
optional | None |
Claude fallback |
GOOGLE_OAUTH_CLIENT_SECRET_PATH |
yes | ./client_secret.json |
Downloaded from Google Cloud Console |
GMAIL_LOOKBACK_HOURS |
no | 24 |
How far back to scan inbox |
CALENDAR_LOOKAHEAD_HOURS |
no | 24 |
How far forward to scan calendar |
RSS_FEEDS |
no | [] |
JSON array of feed URLs |
TTS_PROVIDER |
no | podcastfy |
omnivoice / podcastfy / sesame / kokoro |
OMNIVOICE_BASE_URL |
no | http://127.0.0.1:3900 |
OmniVoice backend (omnivoice only) |
OMNIVOICE_STEPS / _LANGUAGE / _SPEED |
no | 16 / Auto / 1.0 |
OmniVoice tuning |
VOICE_HOST_A, VOICE_HOST_B |
no | provider-specific | Voice IDs |
OUTPUT_DIR |
no | ./output |
Audio, manifest, feed.xml |
FEED_BASE_URL |
no | http://localhost:8080/ |
Base URL embedded in feed |
LLM_MODEL |
no | gemini/gemini-2.5-pro |
LiteLLM provider/model |
STORAGE_BACKEND |
no | local |
local or s3 |
S3_BUCKET |
yes for S3 | None |
Bucket name |
USER_NAME |
no | friend |
Threaded into the dialogue |
omnivoice (recommended — the self-owned keystone) — Points the clone at a local OmniVoice Studio backend (FastAPI on 127.0.0.1:3900). Voice cloning, 646 languages, free at any volume, nothing leaves your machine. This is the functional path that makes the stack fully durable — no rented voice, no per-character billing. Start the backend, then:
# one-time: install the skill helpers + start the backend
npx skills add broomva/omnivoice-skill
bash ~/.claude/skills/omnivoice/scripts/start-backend.sh # health: GET /system/info
# create voice profiles in the UI (localhost:3901), note the profile_ids, then:
# TTS_PROVIDER=omnivoice VOICE_HOST_A=<profile_id_a> VOICE_HOST_B=<profile_id_b>
uv run hexu briefIf the backend is unreachable the renderer raises an actionable error (or emits a silent placeholder when HEXU_ALLOW_SILENT=1). Tune OMNIVOICE_STEPS (8 draft / 16 balanced / 32 quality), OMNIVOICE_LANGUAGE, OMNIVOICE_SPEED.
podcastfy (zero-setup default) — Works out of the box, no GPU required. Hands the rendered transcript to Podcastfy's built-in pipeline. Selects the backend via PODCASTFY_TTS_MODEL: edge (default — free, keyless, no quota), openai (needs OPENAI_API_KEY + billing), elevenlabs, or gemini. Match voice names to the model (edge: en-US-AndrewNeural / en-US-AvaNeural; openai: onyx / nova). Fastest path to your first episode — but it rents the voice (except free edge), so for full durability swap to omnivoice.
sesame — Closest open-weight model to NotebookLM voice quality. Requires a CUDA GPU and the [sesame] extras:
uv pip install -e '.[sesame]'The first run downloads model weights (~6GB). When torch isn't installed, falls back to a silent WAV so the rest of the pipeline still produces a valid episode.
kokoro — Tiny CPU-only ONNX model. Useful when you have no GPU and want something faster than podcastfy:
uv pip install -e '.[kokoro]'uv run hexu serve --host 0.0.0.0 --port 8080
# subscribe in your podcast app to http://<your-machine>:8080/feed.xmlexport STORAGE_BACKEND=s3
export S3_BUCKET=my-hexu
export FEED_BASE_URL=https://cdn.example.com/
uv pip install -e '.[s3]'
uv run hexu briefThe publisher writes audio + feed.xml to the bucket, and embeds CDN URLs in the RSS.
# crontab -e
30 6 * * * /Users/you/path/to/hexu/scripts/cron-daily.shLogs land under output/logs/YYYYMMDD.log. The wrapper script runs the briefing via uv run so the right venv is picked up.
Per daily episode, with Gemini 2.5 Pro and Podcastfy/OpenAI TTS:
| Component | Cost / episode |
|---|---|
| Gemini 2.5 Pro (script generation, ~5k input + 1.5k output tokens) | ~$0.02 |
| TTS (Podcastfy → OpenAI TTS, ~1000 words, 6-min audio) | ~$0.15 |
| Google APIs (Gmail + Calendar reads) | $0 (free tier) |
| Storage (local) | $0 |
| Storage (S3, ~5MB / episode) | <$0.001 |
| Total | ~$0.17 / day, ~$5 / month |
Switch TTS_PROVIDER=omnivoice to drive the voice cost to zero with a local OmniVoice backend (free at any volume, voice cloning, 646 languages) — the recommended path. Sesame CSM or Kokoro also drive TTS to zero at the price of GPU electricity or slightly worse voice quality.
- ✅ Voice cloning (via
omnivoice— 3-sec reference → personalized hosts). - ✅ Multi-language (via
omnivoice— 646 languages,OMNIVOICE_LANGUAGE). - Web UI (one-click subscribe, episode browser).
- Slack / Telegram delivery (audio + transcript link instead of RSS).
- Smart deduplication across RSS sources (cluster + summarize related stories).
- Memory of past episodes (don't re-cover the same stories two days running).
When Huxe announced their 2026-05-28 sunset, there was no drop-in OSS replacement. The full background on the decision, the OSS landscape, and the rationale for each component choice is in research/notes/2026-05-22-huxe-sunset-investigation-raw.md (workspace root).
This project sits on top of OSS components — see each dependency's license. The hexu glue code itself is provided as-is under the same license as the parent monorepo.