An agent that coordinates everything needed to ship a feature launch:
Content (via Claude):
- Release notes
- Blog draft
- Developer docs / product details sheet
- Social posts, one per platform (X, LinkedIn, Instagram, Pinterest, TikTok, Facebook) — each paired with an image the agent chooses for that platform specifically, not one image reused everywhere
Assets (via Cloudinary):
- Upload launch imagery/video
- Generate social crops (Instagram, X, LinkedIn presets)
- Generate an Open Graph image with a designed text treatment (eyebrow label + title on scrim boxes, not a flat darken)
- Generate resized assets for other channels (email header, etc.)
Outreach (via Claude, when a target customer is described):
- A plan recommending outreach channels and sequencing for reaching first customers
- Ready-to-send messages per channel, each ending with an explicit call to action, each optionally paired with an image the agent chooses for that channel
- Drafts only — the agent never sends anything; every message carries a
status: "needs_review"flag rather than a warning buried in the text
Two ways, and the first is the one worth building toward:
- Tag-based discovery (recommended). Whoever preps launch materials tags
them in Cloudinary with the launch slug ahead of time — via the console,
your DAM UI, or an upload pipeline — optionally setting a
rolecustom metadata field (hero,screenshot,video). The brief then just names the slug, and the agent callsfind_launch_assetsto discover everything itself. Nobody has to hunt down or paste a URL. - URL-based (fallback). The brief includes explicit source paths/URLs.
The agent uploads and tags them via
upload_launch_image— which means the next launch that reuses these assets can switch to option 1.
The agent's system prompt (src/agent.js) tells Claude to prefer (1) and
only fall back to (2) for anything find_launch_assets doesn't return. The
sample brief in src/index.js demonstrates the tag-based path.
The interesting part isn't Cloudinary's API — it's the agent loop:
Claude is given a menu of tools (src/tools/tool-definitions.js) and a launch
brief, and it decides which tools to call, in what order, and with what
inputs. The orchestrator (src/agent.js) just executes whatever Claude asks
for and feeds results back until Claude is done. Content tools themselves
delegate to Claude with narrow, specialized prompts (src/tools/content-tools.js)
rather than one giant prompt trying to do everything — a "specialist sub-call"
pattern that scales better as you add more content types.
Cloudinary's role is intentionally narrow: store the source asset once, then
derive every crop/format/overlay on demand via URL-based transformations
(src/tools/cloudinary-tools.js). No re-uploading per channel.
generate_social_posts and generate_outreach_messages don't return one
markdown blob covering every platform/channel — they return an array of
{ platform, copy, imageUrl, assetReason, status } records (or channel /
body for outreach). Two things make that possible:
- The tool's input schema requires an image per platform/channel
(
images: [{ platform, imageUrl, assetReason }, ...]) instead of a singleimageUrlapplied to all of them. That's what actually fixes "every social post uses the same crop" — it's a schema constraint the agent has to satisfy, not a prompt instruction it can ignore under pressure. - The specialist call itself is forced into a tool call
(
tool_choice: { type: 'tool', name: ... }incontent-tools.js) so its response is parsed JSON, not markdown the report then has to reverse-engineer by platform label.report.jsnever parses generated prose to find out which image goes with which post — the data already says so.
The lesson this is meant to demonstrate: when an LLM's output needs to be consumed by deterministic code, push as much of the shape as you can into the schema. A prompt that says "please vary the images" is a request; an input schema that requires one image per platform is a constraint.
npm install
cp .env.example .env
# fill in ANTHROPIC_API_KEY and your Cloudinary credentials in .envCloudinary credentials are in your Cloudinary console under Dashboard → Product Environment Credentials.
npm startYou'll be walked through a short wizard: what kind of launch this is (technical,
retail, travel, real estate, or home improvement), what you're launching, key
changes/benefits, audience, tone, how to get imagery (already tagged in
Cloudinary, or paste source URLs), and who you're hoping to reach as first
customers (leave blank to skip outreach prep). Answering --file brief.txt
instead of the wizard is also supported, for scripting: npm start -- --file brief.txt.
This will:
- Print each tool call as the agent makes it.
- Write
output/<launch-slug>/report.html— a single browser-viewable launch kit, not a raw dump of everything the agent produced:- A cover with a hero image, launch name, a one-line description, and a
Ready / Needs-your-review status breakdown computed from each tool's
statusfield. - A condensed "launch kit" preview (release notes excerpt, blog preview, a few social cards, one outreach message, the outreach plan) linking down to the full versions.
- Each deliverable rendered in a shape that resembles what it actually is — an article for the blog, a changelog for release notes, a spec sheet for product details, platform-styled cards for social posts, email/DM-styled cards for outreach messages — each with its chosen image alongside it, not in a separate gallery.
- An asset board grouped by destination (Instagram, LinkedIn, X, Email, Open Graph, Source assets), each tile labeled with its real dimensions.
- A collapsed "How the agent built this" section holding the agent's own summary and the full tool-call log, kept separate from the polished deliverables above it.
- A cover with a hero image, launch name, a one-line description, and a
Ready / Needs-your-review status breakdown computed from each tool's
- Write
output/<launch-slug>/run-results.json— the raw tool-call log (every input/output, including every Cloudinary URL), for scripting or auditing. - Print a final summary from the agent, plus the exact path to
report.html.
- New content type that's a single document (e.g. an FAQ): add a
specialist function in
content-tools.jsreturning{ content, status }, add its schema toTOOLSand its case toexecuteToolintool-definitions.js. No changes needed toagent.jsorreport.js— a generic markdown card renders anything with acontentfield. - New content type that's per-platform/per-channel (e.g. video scripts
per platform): follow the
generate_social_postspattern — an input schema requiring oneimageUrl/assetReasonper entry, a forcedtool_choicecall in the specialist for the generated part, merged into an array of records. Givereport.jsabuildX()function to render it as its own visual shape rather than routing it through the generic card. - New crop/channel preset: add one line to
CROP_PRESETSincloudinary-tools.js— dimensions for the asset board come from the same object viaPRESET_DIMENSIONS, so labeling stays in sync automatically. - Swap the model: change
MODELinconfig.js. - Persist run history / support multiple launches at once: the agent loop
is stateless per call — wrap
runProductLaunchAgentin whatever storage/queueing layer your app needs.
src/
config.js Anthropic + Cloudinary client setup
agent.js The orchestrator tool-use loop
index.js CLI entry point
report.js Renders run results into the launch-kit HTML report
tools/
tool-definitions.js Tool schemas Claude sees + dispatcher
content-tools.js Claude-backed content generators
cloudinary-tools.js Real Cloudinary upload/transform calls