{"slug": "show-hn-ai-gude-personal-podcast-news-aggregator", "title": "Show HN: AI Gude - Personal Podcast News Aggregator", "summary": "Developer alex0ptr released AI Gude, a free, open-source, self-hosted tool that turns user-selected RSS feeds into a personal morning podcast, generating a script and AI voice audio with chapters at scheduled times. AI Gude requires a Google AI Studio key for the Gemini language model and voice plus a Firecrawl key for article extraction, and runs via Go, ffmpeg and task installed with mise, serving on localhost:8080. The tool processes one Episode at a time in a queue, skips Content older than a configurable Max Lookback, and saves any failed Episode with its reason.", "body_md": "*Ei Gude* is Hessian for good morning, and AI Gude sounds just like it. It is your own morning podcast.\n\nYou choose the news sources you follow and how the show should sound. At the times you pick, AI Gude reads what is new, writes a script in your style, speaks it with an AI voice and delivers one audio Episode with chapters, made for listening on your phone.\n\nYou host it yourself. AI Gude is free and open source software.\n\n**Live demo:** [aigude.alexdoes.dev](https://aigude.alexdoes.dev). Sign in with the user `demo` and the password `demo`.\nYou can browse the Episodes and play them. You cannot switch users, produce, retry or delete Episodes, and the log, the editor of the User Config and the problems of its files stay closed.\n\nIn this tutorial you start AI Gude on your machine and listen to your first Episode.\n\n1. \nClone the repository. \n\n```\ngit clone https://github.com/alex0ptr/aigude.git\ncd aigude\n```\n\n2. \nInstall the tools with [mise](https://mise.jdx.dev) , which has to be[activated in your shell](https://mise.jdx.dev/getting-started.html) . It installs the versions of Go, ffmpeg and\ntask that the project pins.\n\n```\nmise install\n```\n\n3. \nGet a key at [Google AI Studio](https://aistudio.google.com/apikey) for the language model and the voice, and one\nat[Firecrawl](https://www.firecrawl.dev) for reading the articles. Export both.\n\n```\nexport GEMINI_API_KEY=\"...\"\nexport FIRECRAWL_API_KEY=\"...\"\n```\n\n4. \nStart the service with the example configuration in `hack/` .\n\n```\ntask serve:hosted\n```\n\n It can be reached from your network, so run it on a network you trust.\n5. \nOpen [http://localhost:8080](http://localhost:8080) , choose the show and press \"Generate now\". Producing the Episode takes a few minutes.\nWhen it is done it appears in the list. Press play.\n\nYou have a running AI Gude. To follow your own feeds, continue with [Configure Shows, Feeds and Schedules](#configure-shows-feeds-and-schedules).\n\nA User owns CastProfiles, and each CastProfile is a show. A CastProfile lists the Subscriptions it draws Content from, which are RSS feeds, a Max Lookback that says how old Content may be, and Schedules that say when an Episode is made. Content is one item that a Subscription delivers, such as an article. An Episode is one audio file with its Script and its Chapters, one Chapter for each Section of the Script.\n\nThe core flow, from a Schedule to a finished Episode:\n\n```\nsequenceDiagram\n    autonumber\n    participant S as Scheduler\n    participant W as Worker\n    participant DB as Database\n    participant R as RSS feeds\n    participant C as Crawler\n    participant G as Gemini\n    participant F as ffmpeg\n\n    S->>W: Schedule is due, queue the CastProfile\n    Note over S,W: \"Generate now\" queues the same request\n    W->>DB: Which Content was used by this CastProfile already?\n    W->>R: Fetch the Subscriptions, Content within the Max Lookback\n    R->>C: Retrieve the full text of each new article\n    C-->>R: Cleaned text, from the cache while it is fresh\n    R-->>W: Contents\n    alt nothing new\n        W-->>S: No Episode, only a log line\n    else new Content\n        W->>G: Rules, style guide and numbered Contents\n        G-->>W: Script with title and Sections\n        loop every Section\n            W->>G: Speak the Section\n            G-->>W: Speech as PCM\n        end\n        W->>F: Speech joined with a pause between Sections\n        F-->>W: One OGG/Opus file\n        W->>W: Write the Chapters into the file\n        W->>DB: Save Episode, Script, Chapters, sources and usage\n    end\n    Note over W,DB: Any failure saves the Episode as failed, with its reason\n```\n\nWhat to keep in mind:\n\n- One Episode is produced at a time. Requests wait in a queue.\n- Choose a Max Lookback that is larger than the interval between two Schedules. Content older than the Max Lookback is never considered, so a shorter one lets articles fall through the gap between two Episodes.\n- A larger Max Lookback does no harm, because each Episode remembers the Content it used and no article is told twice. Deleting an Episode gives its Content back.\n- A Schedule that was due while the service was not running is made up at the start, once for each CastProfile.\n- A failed Episode stays in the list with its reason. You can try again or dismiss it.\n- Each Section is spoken in its own call, which makes the Chapters exact, and the audio is encoded once at the end.\n\nBehind the steps are small interfaces, and one adapter each does the work today:\n\n``` php\nflowchart LR\n    P[\"Episode production\"]\n    P --> CR[\"ContentRepository\"] --> RSS[\"RSS feeds\"]\n    P --> CW[\"Crawler\"] --> FC[\"Firecrawl\"]\n    P --> SW[\"ScriptWriter\"] --> GM[\"Gemini text\"]\n    P --> TT[\"TTS\"] --> GT[\"Gemini speech\"]\n    P --> AE[\"AudioEncoder\"] --> FF[\"ffmpeg\"]\n    P --> ER[\"EpisodeRepository\"] --> DB[\"SQLite\"]\n    P --> AS[\"AudioStore\"] --> FS[\"Files\"]\n```\n\nThe only part you can run in two ways is the Crawler, which retrieves the full text of the articles. You choose it with\nthe setting `firecrawl_url`.\n\n- The hosted Firecrawl is the default and needs `FIRECRAWL_API_KEY` . There is nothing to run, and it bills per page.\nThe Getting Started uses it.\n- A Firecrawl of your own costs nothing per page. Set `firecrawl_url` to its address. The key is then optional and is\nsent only when it is set.`task serve` starts one in Docker from[hack/firecrawl/](https://github.com/alex0ptr/aigude/blob/main/hack/firecrawl/README.md) , but\nthat one is for development only and has no authentication, so it is not a recipe for hosting.\n\nAI Gude keeps crawled pages for `crawl_cache_ttl`, which is ten days by default, and shares them between all Users. An\narticle that is retrieved again soon costs nothing.\n\nSettings apply to the whole service. The commented example is [hack/aigude.toml](https://github.com/alex0ptr/aigude/blob/main/hack/aigude.toml).\n\n- Settings come from built-in defaults, the settings file, environment variables and flags, and each one overrides the\none before it. An environment variable is `AIGUDE_` plus the upper-case key, for example`AIGUDE_LISTEN_ADDR` . A flag\nis the key with hyphens, for example`--listen-addr` .\n- The settings file is `aigude.toml` in the working directory of the service, or the file given with`--config` . It is\noptional. The example lists every setting with its default and a comment, and a test guards that the defaults it\nshows are the real ones. Copy it and delete what you do not want to change.\n- A key the service does not know is an error, and so is an invalid value. The message names the setting. Changes need a restart.\n- The API keys `GEMINI_API_KEY` and`FIRECRAWL_API_KEY` are not settings. A key in the settings file is rejected as an\nunknown key and the service does not start, so they come from the environment only.\n\nShows, feeds and Schedules belong to a User and live in that User's file. The commented example is\n[hack/users/demo.toml](https://github.com/alex0ptr/aigude/blob/main/hack/users/demo.toml), and it is the template for new Users.\n\n- Each User has one `.toml` file in the folder`users_dir` , which is`users` by default. The file name is free, and the\nUser is identified by the Id inside.\n- A file holds the User, its CastProfiles, and for each CastProfile the Subscriptions, Max Lookback, language, voice, prompt and Schedules.\n- The files are followed while the service runs, so an edit takes effect without a restart. A file that turns invalid keeps the last valid setup of its User, and the interface shows the problem. A deleted file takes its User away.\n- A User can edit their own file in the interface with \"Edit User Config\". To check files without starting anything run this:\n\n```\naigude validate --users-dir users\n```\n\nAI Gude has no sign-in of its own. Authentication is the job of a reverse proxy in front of it. The proxy tells AI Gude who is asking in a request header, and from then on each person works in their own User and cannot reach another one.\n\nWithout the header everyone who reaches the port can switch between all Users in the interface, produce and delete\nEpisodes and edit every User Config. At home on a network you trust that is a fine way to share one instance. It is\nalso why the service listens on `127.0.0.1` by default. Do not leave it like this on a network that others can reach.\n\nTo add a User, copy [hack/users/demo.toml](https://github.com/alex0ptr/aigude/blob/main/hack/users/demo.toml) into the users folder and change the `[user]` table\nand what follows. The file is picked up at once.\n\nAfter the proxy has authenticated a person, it sets the header `X-Aigude-User` to the Id of their User. Use the Id and\nnot the display name, because only the Id never changes.\n\nA request with this header belongs to that User. The interface offers no switch to another User, a request to switch is refused, and the person sees only their own Episodes and edits only their own User Config. A header that is empty, that names no User or that names a User without a valid setup is answered with 403 for every request.\n\nTwo things have to hold for this to be safe:\n\n- The port is reachable only through the proxy.\n- The proxy sets the header itself on every request and never passes one on from the client. A request without the header gets full rights, so a proxy that forgets it opens the service to everyone.\n\nTraefik asks for a user name and a password and writes the name into `X-Aigude-User`. So the user name of the Basic Auth\nis the Id of the User. Create the users file with one line for each person, for example with `htpasswd`:\n\n```\nhtpasswd -nB alex >> /etc/traefik/users\n```\n\nThen add the route and its middleware to the dynamic configuration of Traefik. Replace the host name and the address of\nthe service. The example assumes an entry point `websecure` and a certificate resolver `letsencrypt`. Serve it over\nHTTPS, because Basic Auth sends the password with every request.\n\n```\nhttp:\n  routers:\n    aigude:\n      rule: Host(`aigude.example.org`)\n      entryPoints:\n        - websecure\n      service: aigude\n      middlewares:\n        - aigude-auth\n      tls:\n        certResolver: letsencrypt\n\n  middlewares:\n    aigude-auth:\n      basicAuth:\n        usersFile: /etc/traefik/users\n        realm: AI Gude\n        headerField: X-Aigude-User\n        removeHeader: true\n\n  services:\n    aigude:\n      loadBalancer:\n        servers:\n          - url: http://127.0.0.1:8080\n```\n\n`headerField` makes Traefik set `X-Aigude-User` from the user name and replaces a value that the client sent, and\n`removeHeader` keeps the credentials from reaching AI Gude. A name in the users file that is not a User of AI Gude is\nanswered with 403.\n\nThe User with the Id `demo` is shown in Demo Mode. Whoever looks at its pages can browse and play its Episodes, and\nnothing can be changed. The log and the editor of the User Config are closed, and the interface leaves out what only the\nperson running the service should see, such as problems in the User Config files. Because \"Generate now\" is closed too,\nthe Episodes of the demo User come from its Schedules. Create it like any User, for example from the sample\n[hack/users/demo.toml](https://github.com/alex0ptr/aigude/blob/main/hack/users/demo.toml) with the Id `demo`.\n\n- Produce an Episode with \"Generate now\" on a CastProfile, or let its Schedules do it.\n- Episodes are deleted with their audio once they are older than the Retention, the time Episodes are kept. The setting is `episode_retention` , it\nis 30 days by default, it counts from the creation of the Episode and it applies to failed Episodes too.`0` keeps\nthem for ever. The check runs at the start and once a day, and a deleted Episode gives its Content back.\n- The meter on an Episode shows what it consumed, such as tokens and crawled pages.\n- `GET /healthz` answers`200` with`ok` and checks nothing behind it, not the database, not Gemini and not Firecrawl.\nIt needs no headers of a proxy.\n- Back up the data folder `data_dir` . It holds the SQLite database and the audio files. The User Config files are the\nonly copy of your configuration.\n\n**The whole Ansible part is vibe coded.** An AI assistant wrote the collection. The roles were run on a host and\nworked, but nobody has reviewed the code. Be especially careful. Read the roles before you run them, try them with\n`ansible-playbook --check` first, and use a host that you can set up again. The only automatic check is\n`ansible-lint`.\n\nThe Ansible collection [alex0ptr.aigude](https://github.com/alex0ptr/aigude/blob/main/deploy/ansible_collections/alex0ptr/aigude/README.md) installs AI Gude on a\nLinux host with rootful Podman, as systemd quadlets. It can also install Traefik with HTTPS and a sign-in in front of it,\nand a Firecrawl of your own. The host needs Ubuntu 24.04, Debian 13 or another system with Podman 4.8 or newer.\n\n1. \nAdd the collection to the `requirements.yml` of your Ansible project and install it:\n\n```\ncollections:\n  - name: https://github.com/alex0ptr/aigude.git#/deploy/ansible_collections/alex0ptr/aigude\n    type: git\n    version: main\nansible-galaxy collection install -r requirements.yml\n```\n\n2. \nCopy the example playbook that fits from [deploy/ansible_collections/alex0ptr/aigude/playbooks/](https://github.com/alex0ptr/aigude/blob/main/deploy/ansible_collections/alex0ptr/aigude/playbooks) . There\nis one for each combination of no proxy or Traefik, and the hosted Firecrawl or one of your own.\n3. \nSet your host group and your values in it. The Gemini key and the address of the Firecrawl are the least it needs.\n4. \nRun it with `ansible-playbook` .\n\nWithout a proxy AI Gude then listens on `127.0.0.1:8080` of the host. The README of the collection lists the variables\nand says what to know about the image, the networks and the firewall.\n\nAI Gude is licensed under the GNU Affero General Public License, version 3 or later. See [LICENSE](https://github.com/alex0ptr/aigude/blob/main/LICENSE).\n\nCopyright © 2025 to 2026 Alex Krause.", "url": "https://wpnews.pro/news/show-hn-ai-gude-personal-podcast-news-aggregator", "canonical_source": "https://github.com/alex0ptr/aigude", "published_at": "2026-11-10 17:41:32+00:00", "updated_at": "2026-11-10 17:53:33.879330+00:00", "lang": "en", "topics": ["ai-tools", "generative-ai", "ai-products", "developer-tools"], "entities": ["AI Gude", "alex0ptr", "Google AI Studio", "Gemini", "Firecrawl", "mise", "ffmpeg", "Go"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-ai-gude-personal-podcast-news-aggregator", "markdown": "https://wpnews.pro/news/show-hn-ai-gude-personal-podcast-news-aggregator.md", "text": "https://wpnews.pro/news/show-hn-ai-gude-personal-podcast-news-aggregator.txt", "jsonld": "https://wpnews.pro/news/show-hn-ai-gude-personal-podcast-news-aggregator.jsonld"}}