Automated content pipeline for financial analysis. Turns a company's SEC filings into a narrated video, a vertical teaser short, a two-voice Q&A podcast, and social posts — one analysis, multiple formats.
- Campaign-Driven — reusable campaign templates define the editorial angle, analytical framework, and output specs; apply them to any ticker.
- AI Content Generation — Claude Cowork analyzes filings via RoboSystems MCP tools and writes the brief, video script, Q&A script, and social posts.
- On-Brand Decks — slides are composed in Claude Design from the
@robosystems/coredesign system (no hand-authored HTML). - Automated Production — the pipeline slices the deck, synthesizes voiceover, assembles the video, and produces the short + podcast.
🎙️ Voiceover & music run on ElevenLabs. Setting this up? Signing up through our referral link costs you nothing extra and directly supports the project. Affiliate link.
git clone https://github.com/RoboFinSystems/robosystems-content-machine.git
cd robosystems-content-machine
# Scaffold a project from a campaign
just campaign TICKER campaign_name
# Or scaffold from the base template (no campaign)
just new TICKERThe first just command auto-creates .env from .env.example. Fill in your API keys (see Setup).
Three stages: research and production automate; the design step in the middle is human-in-the-loop (the craft step).
Every project starts from the base template/ (folder structure + stage instructions + assets). Projects are company-centric — sources accumulate over time, each run produces a new set of outputs.
just new TICKER # base template
just campaign TICKER campaign_name # with a campaign overlay
just campaigns # list available campaignsCampaigns add an editorial layer for thematic coverage across many companies — the voice, analytical framework, target tickers, and shared reference data.
campaigns/
my_campaign/
CAMPAIGN_BRIEF.md # Editorial strategy and analytical framework
COWORK_INSTRUCTIONS.md # Production instructions (overrides base)
tickers.md # Target companies and production calendar
sources/ # Third-party research and reference data (gitignored)
overrides/ # File replacements (custom assets/instructions)
The base template is applied first, then the campaign overlays its instructions, brief, and shared sources on top.
Point a Cowork session at the scaffolded project folder. Claude reads the instructions (COWORK_INSTRUCTIONS.md + PRODUCTION_CONTRACT.md) and produces:
- Narrative brief — the written analysis (Markdown), authored first.
- Video script (
scripts/{TICKER}_script.json) — the source of truth: ordered segments with narration + per-slide content, thethumbnailblock, and ashortblock (the teaser). - Q&A script (
scripts/{TICKER}_qa.json) — a two-voice interviewer/analyst conversation for the podcast. - Social posts — X post + YouTube description.
Cowork authors no HTML — slides and the thumbnail are specced in the script and built in Claude Design.
Generate the hand-off brief, then compose the deck + thumbnail on-brand:
just deck-brief TICKER # render the Claude Design hand-off from the scriptPaste the brief into claude.ai/design (on @robosystems/core), compose a 16:9 deck and thumbnail, then export both as PDF (Claude Design exports PDF only) → deck/{TICKER}_deck.pdf and deck/{TICKER}_thumbnail.pdf. The slice step rasterizes the thumbnail PDF to charts/png/{TICKER}_thumbnail.png automatically.
just pipeline PROJECT # validate -> slice -> voiceover -> assemble (long-form video)
just short PROJECT # 9:16 teaser short (b-roll + music + VO + caption cards)
just podcast-qa PROJECT # two-voice Q&A podcast (MP3 for Spotify + MP4 for YouTube)
just podcast PROJECT # extract podcast MP3 from the long-form video
just publish PROJECT # upload final deliverables to the public S3 artifact store
just postpack PROJECT # assemble the per-platform publish pack (paste-ready copy + S3 links)| Step | Command | What it does |
|---|---|---|
| Validate | just validate PROJECT |
Checks Cowork outputs exist and the script matches the deck contract |
| Slice | just slice PROJECT |
Slices the exported deck PDF into per-slide 1920×1080 PNGs (pdftoppm) |
| Voiceover | just voiceover PROJECT |
Sends narration to ElevenLabs TTS (idempotent; --force to regen) |
| Assemble | just assemble PROJECT |
Uploads assets to S3, builds the Shotstack timeline, renders the MP4 (--production for 1080p) |
| Short | just short PROJECT |
Renders a 9:16 teaser locally with ffmpeg |
| Podcast (Q&A) | just podcast-qa PROJECT |
Synthesizes the two-voice conversation → MP3 + MP4 |
Assembly writes videos/{TICKER}_timestamps.txt with the actual YouTube chapter times.
just publish {TICKER} uploads the final deliverables (long-form, short, podcast MP3/MP4,
thumbnail, brief, social copy) to s3://$AWS_S3_BUCKET/content/{TICKER}/ and prints public URLs
(served via $AWS_CDN_DOMAIN_URL when set, else https://$AWS_S3_BUCKET.s3.amazonaws.com/content/{TICKER}/…)
— a durable artifact store, separate from posting to YouTube / Spotify / X. The bucket policy grants
public read on the content/* + blog/* prefixes only (no user data — the store is public by
design); Shotstack staging assets elsewhere stay private. The bucket + CloudFront CDN are managed by
cloudformation/content.yaml (just infra-deploy — see Infrastructure below).
A lighter sibling of the research pipeline for markdown essays. A post is one file —
blog/<slug>/post.md (YAML frontmatter + body), authored and git-versioned in this repo.
Narration, cover image, and social copy are all optional and additive; a post with just
post.md publishes cleanly.
just blog-new <slug> # scaffold blog/<slug>/post.md from the template
just blog-publish <slug> # auto-narrate (default-on) + upload blog/<slug>/* to S3 + reindex
just blog-narrate <slug> # (re)generate narration on its own; --force to redo
just blog-social <slug> # optional: paste-ready distribution pack (uses <slug>_x_post.txt if present)
just blog-reindex # rebuild blog/index.json (the catalog the app's /blog routes read)Every post ships with a "Listen to this story" narration — blog-publish auto-narrates any
post that has no audio yet (pass --no-audio to skip), so the feature stays consistent across
the whole catalog. Narration reuses the same ElevenLabs path as the research voiceover + Q&A
podcast (one brand voice; body stripped of code/tables, chunked for TTS, concatenated with
ffmpeg). blog-publish also writes a self-describing meta.json and refreshes blog/index.json
— a versioned contract (version: 1) with absolute CDN asset URLs, the same consumption shape
the /research catalog uses. The app consumes it via SSG/ISR; publishing or editing a post no
longer needs an app redeploy.
The short pulls from reusable, mood/tag-tagged libraries that compound across every ticker:
just broll # show the b-roll library + coverage by category
just broll-sync # register new clips dropped into assets/broll/
just music-sync # register new tracks dropped into assets/music/
just music "<prompt>" # generate a music bed via the ElevenLabs Music APICowork selects clips/tracks by theme: a broll_theme / music_mood (tags) or an explicit list. Manifests are tracked; the heavy .mp4/.mp3 binaries are gitignored (local-only).
just projects # List all projects
just play PROJECT # Play the final video
just durations PROJECT # Show media durations via ffprobe
just clean PROJECT # Remove generated assets (keeps source files)- uv — Python package manager
- just — command runner
- ffmpeg / ffprobe — media processing (short, podcast, slicing)
- poppler —
pdftoppmfor slicing the deck PDF + rasterizing the thumbnail - AWS CLI — S3 uploads for Shotstack
Configure in .env after first run:
| Service | Keys | Purpose |
|---|---|---|
| ElevenLabs | ELEVEN_LABS_API_KEY, ELEVEN_LABS_VOICE_ID, ELEVEN_LABS_INTERVIEWER_VOICE_ID |
Voiceover (narrator) + Q&A interviewer voice + Music API |
| Shotstack | SHOTSTACK_API_KEY, SHOTSTACK_OWNER_ID (+ sandbox keys) |
Cloud video assembly |
| AWS | AWS_PROFILE, AWS_REGION, AWS_S3_BUCKET, AWS_CDN_DOMAIN_URL (optional), AWS_ROUTE53_HOSTED_ZONE_ID (optional, auto-resolved) |
Asset uploads + CloudFront CDN |
The ElevenLabs link above is a referral link.
Content generation uses Claude Desktop with the RoboSystems MCP server configured; the deck is composed in claude.ai/design on the @robosystems/core design system.
The content bucket + CloudFront CDN are defined in cloudformation/content.yaml and deployed
locally via the AWS CLI (no GitHub Actions), mirroring the platform repo's just bootstrap flow.
Config comes from .env (AWS_PROFILE, AWS_S3_BUCKET, optional AWS_CDN_DOMAIN_URL;
AWS_ROUTE53_HOSTED_ZONE_ID is auto-resolved from the CDN domain).
just infra-validate # validate the template
just infra-deploy # create the bucket + CDN stack (+ wait, + print outputs)
just content-migrate # copy existing content from the legacy bucket into the new one
just reindex # rebuild content/index.json on the new bucket (CDN urls)
just infra-outputs # show bucket / CDN url / distribution idinfra-deploy creates a new bucket (default robosystems-content); the legacy
robosystems-marketing-assets bucket is left untouched. After migrating + reindexing, point the apps
at the CDN (assets.robosystems.ai) and retire the old bucket when ready.
Backed by an ElevenLabs Grant — the credits power the voiceover, Q&A interviewer voice, and music generation behind every video this pipeline produces.
Using ElevenLabs yourself? Our referral link costs you nothing extra and supports the project.
This project is licensed under the MIT License - see the LICENSE file for details.
MIT © 2026 RFS LLC