OmniPost is an agentic TypeScript pipeline that turns one long-form article, transcript or note into a full set of platform-native posts — X thread, LinkedIn, Instagram caption, newsletter blurb — plus a weekly content calendar exportable as .ics. The LLM drafts; code enforces every character limit, hashtag rule and split.
Real output from npm run repurpose -- --mock on the bundled 1,300-word source. Same pipeline, same enforcement, no API key required.
Code spreads posts across slots — daily for X, Mon/Wed/Fri for LinkedIn, Tue/Thu/Sat for Instagram, Thursday for newsletter. Exports as calendar.csv and calendar.ics (import into Google Calendar).
| When (UTC) | Platform | Preview |
|---|
Five pure, typed steps. LLM only writes drafts. Every hard constraint — character limits, hashtag dedup, thread numbering — lives in enforce.ts. That separation is the entire reliability story.
Read a file or fetch a URL behind one ContentSource interface. HTML article extraction is built in.
Claude tool-use or Groq json_object → one zod schema. Brand voice baked into the system prompt.
Code splits tweets ≤ 280, numbers 1/n, dedups hashtags, normalizes markdown per platform.
Slot-based planner across N days. Emits CSV + RFC-5545 .ics for Google Calendar.
JSON + human-readable Markdown to data/out/. Cached by source hash — re-runs skip the LLM.
Language models cannot count characters reliably. So character limits, hashtag dedup and thread numbering all live in enforce.ts — the same pattern used in PulseReport for numbers.
Claude path uses tools + tool_choice + input_schema; Groq path uses response_format: json_object. Both validated through the same zod schema.
If the LLM call fails or returns invalid JSON, a deterministic paragraph-splitter produces a usable draft and the run is marked degraded: true. The pipeline never silently breaks.
Each LLM result is cached at data/cache/<hash>.json keyed by source + brand voice. Re-running with the same input skips the API entirely. --force drops the cache.
Tone, audience, CTA, do/don't and language come from env. The voice is part of the cache key — change it and the next run regenerates.
npm run repurpose -- --mock --dry produces a full bundle from the bundled source and the seeded cache. Same code path CI runs on every push.
tsx@anthropic-ai/sdk with tool-use + cache_controlopenai SDK pointed at Groq's OpenAI-compatible endpointzod for env + every LLM responsefetch + tiny HTML extractor, no headless browser.ics generation, zero deps# offline demo (no API key)
npm run repurpose -- --mock --dry
# real run on the bundled article
npm run repurpose
# fetch and repurpose a URL
npm run repurpose -- --url=https://example.com/article
# pick platforms
npm run repurpose -- --platforms=x,linkedin
# add a 7-day content calendar
npm run repurpose -- --schedule --days=7
# bypass cache
npm run repurpose -- --force