CastForge
Open-source Python framework for repeatable, source-transparent podcast pipelines.
CastForge separates reusable production mechanics from show-owned editorial policy. It can normalize cited sources, record an auditable episode manifest, generate NotebookLM audio, publish MP3s to Cloudflare R2, update RSS atomically, and validate the result. Each show keeps its own sources, prompts, identity, schedule, and feed.
CastForge powers Nitan Podcast, a production Chinese podcast generated from USCardForum discussions.
Install
The v0.1.1 wheel is attached to the GitHub release. Until the first PyPI upload is authorized, install the immutable release tag:
pip install "castforge @ git+https://github.com/lifan-builds/castforge.git@v0.1.1"
# Optional production integrations
pip install "castforge[notebooklm,r2] @ git+https://github.com/lifan-builds/castforge.git@v0.1.1"
Python 3.10 or newer is supported. Gemini, NotebookLM, and R2 dependencies remain optional.
First episode in under 20 minutes
mkdir my-show && cd my-show
castforge init
castforge run --config podcast.yaml --date 2026-08-11
castforge validate --config podcast.yaml --date 2026-08-11
The fixture-backed starter creates:
- a cited NotebookLM source document;
- a deterministic episode manifest;
- an RSS feed with a positive audio enclosure contract.
Fixture mode does not call an LLM, audio service, or public endpoint. Replace the example sources and public values before production use.
Core contracts
from castforge import EpisodeManifest, SourceItem, StoryCluster
from castforge.contracts import AudioProvider, Publisher, SourceAdapter
SourceItemnormalizes one primary, independent, or trend-signal source.StoryClustergroups duplicate coverage and qualifies only a primary source or two independent reports.EpisodeManifestpreserves the selected stories, citations, source document, pipeline version, and public audio identity.SourceAdapter,AudioProvider, andPublisherare intentionally small protocols implemented by show repositories or integrations.
The generic CLI consumes podcast.yaml; show-specific collectors and ranking remain in the show repository.
Commands
castforge init [directory]
castforge run --config podcast.yaml --date YYYY-MM-DD [--shadow]
castforge validate --config podcast.yaml [--date YYYY-MM-DD] [--check-public]
--shadow creates the source and manifest artifacts but does not mutate RSS or R2. Same-date production reruns replace the date-keyed RSS item instead of duplicating it.
Production configuration
See examples/podcast.yaml for the complete schema.
For NotebookLM:
audio:
provider: notebooklm
output_dir: build/audio
duration: 00:06:00
public_url_template: https://audio.example.com/episodes/{filename}
language: en
audio_length: short
Install and authenticate the integration once on the runner:
pip install "castforge[notebooklm]"
playwright install chromium
notebooklm login
Set NOTEBOOKLM_NOTEBOOK_ID. Authentication state and notebook ownership stay outside the show repository.
For Cloudflare R2:
publication:
provider: r2
bucket: podcast-audio
endpoint_url: https://ACCOUNT_ID.r2.cloudflarestorage.com
public_base_url: https://audio.example.com
access_key_env: R2_ACCESS_KEY_ID
secret_key_env: R2_SECRET_ACCESS_KEY
download_url_prefix: https://op3.dev/e/
max_bucket_bytes: 9000000000
CastForge totals the bucket before each upload and refuses a projected size above max_bucket_bytes; overwrites subtract the prior object and listings paginate safely. It then uploads MP3s as audio/mpeg and sends a public HEAD request to verify status, MIME type, and byte length before updating RSS. A show may apply a privacy-respecting download redirect such as OP3 after the R2 origin passes validation. A failed capacity check, generation, upload, or public check leaves the feed unchanged.
Existing hook pipeline
Production shows can continue wiring show-specific extraction and publishing through PipelineHooks while migrating to config-driven artifacts:
from castforge.pipeline import PipelineHooks, main as castforge_main
def main(argv=None):
hooks = PipelineHooks(
extract_weekly_key_info=extract,
fetch_thread_details=fetch_details,
list_mcp_tools=list_tools,
select_threads=select,
threads_to_source_markdown=to_markdown,
write_forum_post=write_post,
generate_rss_feed=generate_feed,
episode_file_prefix="weekly_episode",
week_episode_filename=episode_filename,
week_episode_url=episode_url,
)
return castforge_main(argv, hooks=hooks)
CastForge owns execution and provider integrations. The show owns sources, editorial policy, branding, secrets, scheduling, feeds, episodes, and public compatibility.
Development
python -m pip install -e ".[test]"
python -m pytest
python -m build
Tests are offline and use fake provider clients. Live NotebookLM and R2 checks require explicit credentials and are not part of the ordinary suite.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file castforge-0.1.1.tar.gz.
File metadata
- Download URL: castforge-0.1.1.tar.gz
- Upload date:
- Size: 29.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6085a3e383a95e806ac2a9b55157b83f9a8c91754293a51767198a6ffe8dd6eb
|
|
| MD5 |
2a8dccc718a1147c136851ac409c6d80
|
|
| BLAKE2b-256 |
034d015b0ceef476e34b9683d8342d87d37d3176e1b1d802ab5ca2cb5020e24e
|
Provenance
The following attestation bundles were made for castforge-0.1.1.tar.gz:
Publisher:
publish-pypi.yml on lifan-builds/castforge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
castforge-0.1.1.tar.gz -
Subject digest:
6085a3e383a95e806ac2a9b55157b83f9a8c91754293a51767198a6ffe8dd6eb - Sigstore transparency entry: 2429132282
- Sigstore integration time:
-
Permalink:
lifan-builds/castforge@ba43eab99529ccb52d9278d961467130147a5ac4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lifan-builds
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ba43eab99529ccb52d9278d961467130147a5ac4 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file castforge-0.1.1-py3-none-any.whl.
File metadata
- Download URL: castforge-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93471ad160362b4b1897fe7d03fdff35859b90f974bd192880d51acfc7f7d328
|
|
| MD5 |
bf2040e783a6fc23886e33b15fb694d1
|
|
| BLAKE2b-256 |
48f18bfdbbcc6f6e6ac68b581eed183ae8db28f98662787d217dbd37701c8266
|
Provenance
The following attestation bundles were made for castforge-0.1.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on lifan-builds/castforge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
castforge-0.1.1-py3-none-any.whl -
Subject digest:
93471ad160362b4b1897fe7d03fdff35859b90f974bd192880d51acfc7f7d328 - Sigstore transparency entry: 2429132312
- Sigstore integration time:
-
Permalink:
lifan-builds/castforge@ba43eab99529ccb52d9278d961467130147a5ac4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/lifan-builds
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ba43eab99529ccb52d9278d961467130147a5ac4 -
Trigger Event:
workflow_dispatch
-
Statement type: