hermoso 0.1.226 → 0.1.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/mcp/tools.mjs +41 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ scripts. Research the ads already winning in a market, generate finished image &
|
|
|
5
5
|
composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad
|
|
6
6
|
campaigns behind them — all over [MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**825 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
|
|
9
9
|
catalog with exact per-render credit costs plus the full capability map.
|
|
10
10
|
|
|
11
11
|
**What it connects to.** Ad platforms: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads,
|
|
@@ -171,7 +171,7 @@ block entirely if you signed in above; it is there for CI, where the process can
|
|
|
171
171
|
|
|
172
172
|
Then ask your agent: *“Generate an image ad with Hermoso.”*
|
|
173
173
|
|
|
174
|
-
### What the
|
|
174
|
+
### What the 825 tools cover
|
|
175
175
|
|
|
176
176
|
**Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
|
|
177
177
|
Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
|
package/mcp/tools.mjs
CHANGED
|
@@ -16098,6 +16098,47 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
16098
16098
|
return ok(`Cut ${clips.length} ranked clip${clips.length === 1 ? '' : 's'} [job ${r.jobId}]:\n${lines.join('\n')}${capLine}${trunc}${capNote}`, r);
|
|
16099
16099
|
}));
|
|
16100
16100
|
|
|
16101
|
+
// ADD SUBTITLES TO ANY VIDEO (2026-09-12). A plain comment, not a "── SECTION ──" header: build-docs groups tools by
|
|
16102
|
+
// those headers, and this tool belongs to the section clip_video is in.
|
|
16103
|
+
// Dave: "Do we have functionality to add subtitles to our videos or others? … it should be possible to customize the
|
|
16104
|
+
// style of them as well". Burned subtitles existed only INSIDE clip_video and make_explainer; a finished render, an
|
|
16105
|
+
// upload or someone else's video had no way to get them. The look is the same textStyle vocabulary render_ad speaks.
|
|
16106
|
+
server.registerTool('add_subtitles', {
|
|
16107
|
+
title: 'Add subtitles to a video',
|
|
16108
|
+
description: "Burn subtitles into ANY existing video and get the .srt too. It transcribes the speech and burns short readable lines onto the whole video; nothing is cut or re-rendered. Set textStyle only when the user describes a look; with none, white sentence-case text with a thin outline sits in the bottom safe band. Timing is approximate (per spoken sentence), not word-level sync. burn:false returns only the .srt. Takes a /generated/ URL, a direct .mp4/.mov/.webm, or a YouTube/Vimeo/Loom-style link; not TikTok, Instagram or Facebook. No speech is refused and refunded. Runs in the background and lands in the Library.",
|
|
16109
|
+
inputSchema: {
|
|
16110
|
+
video: z.string().describe('the video to subtitle'),
|
|
16111
|
+
textStyle: z.union([
|
|
16112
|
+
z.enum(['pill', 'editorial', 'bold', 'minimal', 'handwritten', 'boxed']),
|
|
16113
|
+
z.object({
|
|
16114
|
+
preset: z.enum(['pill', 'editorial', 'bold', 'minimal', 'handwritten', 'boxed']).optional(),
|
|
16115
|
+
font: z.enum(['sans', 'serif', 'elegant', 'condensed', 'hand']).optional(),
|
|
16116
|
+
weight: z.number().optional(), size: z.union([z.enum(['s', 'm', 'l', 'xl']), z.number()]).optional(),
|
|
16117
|
+
color: z.string().optional().describe('#hex'), background: z.string().optional().describe('none | pill | #hex box'),
|
|
16118
|
+
position: z.enum(['top', 'center', 'lower', 'bottom']).optional(), textCase: z.enum(['as-is', 'upper', 'lower', 'title']).optional(),
|
|
16119
|
+
italic: z.boolean().optional(), outline: z.boolean().optional(), shadow: z.boolean().optional(),
|
|
16120
|
+
tilt: z.number().optional().describe('degrees, ±12'),
|
|
16121
|
+
}),
|
|
16122
|
+
]).optional().describe('the look: a preset or overrides; omit for the default'),
|
|
16123
|
+
burn: z.boolean().optional().describe('false = only the .srt'),
|
|
16124
|
+
},
|
|
16125
|
+
outputSchema: { ...JOB_OUT },
|
|
16126
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
16127
|
+
}, wrap(async (a) => {
|
|
16128
|
+
const r = await renderJob('subtitles', { video: a.video, textStyle: a.textStyle, burn: a.burn }, 'MCP subtitles');
|
|
16129
|
+
if (r.stillRendering) return okVideo('', r); // resumable handle — get_job carries the result when it lands
|
|
16130
|
+
const raw = r?.raw || {};
|
|
16131
|
+
// THE HEADLINE IS THE READ-BACK: "burned" only when the returned file really carries the subtitles.
|
|
16132
|
+
const head = raw.captionsBurned && raw.video
|
|
16133
|
+
? `Subtitles burned in (${raw.captionStyle || 'default'} look, approximate per-sentence timing, not word-level sync): ${abs(raw.video)}`
|
|
16134
|
+
: 'No subtitled video came back — the subtitle file is below.';
|
|
16135
|
+
const note = raw.captionNote ? `\nNOTE: ${raw.captionNote}` : '';
|
|
16136
|
+
const trunc = raw.truncated ? `\nNOTE: only the first ${Math.round((raw.analyzedSeconds || 0) / 60)} min of ${Math.round((raw.sourceDuration || 0) / 60)} min was transcribed, so the subtitles stop there.` : '';
|
|
16137
|
+
const srt = String(raw.srt || '');
|
|
16138
|
+
const file = srt ? `\n\n.srt (${raw.cues || 0} lines):\n${srt.slice(0, 1500)}${srt.length > 1500 ? '\n…' : ''}` : '';
|
|
16139
|
+
return ok(`${head} [job ${r.jobId}]${note}${trunc}${file}`, r);
|
|
16140
|
+
}));
|
|
16141
|
+
|
|
16101
16142
|
server.registerTool('make_explainer', {
|
|
16102
16143
|
title: 'Make an explainer video',
|
|
16103
16144
|
description: "Turn a TOPIC into a finished narrated explainer video. Writes a sectioned script, paints a BURST of pictures per section (about one every 1.5s — most of them one-detail edits of the frame before, so it reads as movement rather than a slideshow), narrates each section with TTS, holds each picture PERFECTLY STILL for its own slice of the narration (the motion is the CUT RATE — a slow move on a still shimmers), then composites the end card (and any on-screen text you asked for) with the Chrome+ffmpeg engine the ads use (text is never model-painted, so it never garbles). BURNED ON-SCREEN TEXT IS OFF BY DEFAULT — the narration carries the point and the pictures carry the story, so the film ships clean unless the user asks otherwise; `captions:true` adds held key points and `subtitles:true` adds narration-timed CAPS (see both). It is an image film WITH motion, not N video-model renders — that's what keeps it affordable. `style` picks the visual family: the default 'cinematic' is photoreal editorial; every other id is a STYLED, strictly non-photoreal look (illustrated / collage / clay / pixel …) that first renders ONE style-key image and then locks every scene to it, so the whole film holds one look. Cost at the default frame density: a ~130-credit hold for a 60s explainer on the default style, ~100 styled; `frameDensity:'lean'` roughly halves it and `'minimal'` (one picture per section) is ~30. All settle to the exact per-frame image + narration spend (a longer target = more sections = more). Takes SEVERAL minutes — one image render per frame; independent frames are painted concurrently, so it is far faster than the frame count suggests. Needs the writing model and a narration voice engine connected. NOT the tool for a short product ad — use render_ad or generate_video for those, and make_template_ad for the deterministic native formats.",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.227",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
|
-
"description": "AI ad studio and marketing MCP server with
|
|
5
|
+
"description": "AI ad studio and marketing MCP server with 825 tools. Research the ads already running in any market, generate finished image, video and UGC avatar ads, publish and schedule them to your own channels, build and manage the ad campaigns behind them, and read what they achieved. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads and ChatGPT Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and Telegram. AD RESEARCH: the Meta, Google and LinkedIn ad libraries plus organic TikTok, Instagram, YouTube, Threads and Reddit. ANALYTICS: Google Analytics 4, Google Search Console and every connected platform's own post and campaign insights. Also brand onboarding, 50+ image and video generation models, ad scoring, competitor teardowns, Google Drive and OneDrive, a CLI and installable Claude skills.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"hermoso": "bin/hermoso.mjs"
|