hermoso 0.1.2 → 0.1.3
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/mcp/tools.mjs +46 -0
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -169,6 +169,52 @@ export function registerTools(server) {
|
|
|
169
169
|
return okVideo(`Ad video ready: ${r.url}${r.model ? ` (${r.model})` : ''} [job ${r.jobId}]\n${notes || ''}`, r);
|
|
170
170
|
}));
|
|
171
171
|
|
|
172
|
+
|
|
173
|
+
server.registerTool('make_template_ad', {
|
|
174
|
+
description: "Render a NATIVE-STYLE TEMPLATE ad from pure HTML — no AI video/image model in the loop, renders in ~30 seconds for a couple of credits. Perfect for native-feel social ads at volume. YOU author the content (short, casual, believable — never marketing-speak). Templates (pass as config.template): 'imessage-chat' (VIDEO ~15s: a real-looking iMessage thread where a friend reveals the product as a rich-link card; config: { thread: { contactName, messages: [{from:'them'|'me', text?, product?:{image,title,domain}}] }, theme?:'dark'|'light', endCard:{headline,cta,domain,logo?,color} } — 4-6 short lowercase bubbles, product card mid-thread from 'me', 1-2 excited replies after); 'chatgpt-chat' (VIDEO: a ChatGPT answer streams the punchline; config: { question, answer (may **bold** the brand), productImage?, endCard }); 'apple-notes' (VIDEO: an iPhone note types itself out; config: { title, lines: string[], theme?, endCard }); 'value-prop' (VIDEO ~17s kinetic typography: config: { hook, claims: string[] (3-5, ≤4 words each), productImages: string[], palette: string[], endCard }); 'static-mockup' (IMAGE: config: { style:'imessage'|'notes'|'card', size?:{w,h}, ...style fields }); 'airdrop-carousel' (VIDEO ~10s: an iOS AirDrop share card springs up and cycles 3-16 REAL product photos to a full-lineup payoff; config: { brandName, products: [{image, title?}], contactLine?, endCard }); 'app-ui-tour' (VIDEO ~12-16s for APP brands: floating-iPhone mockup walks through REAL app screenshots with kinetic captions; config: { hook?, appName, iconImage?, beats: [{screenImage, caption}] (2-6), palette?, fontStack?, endCard }); 'imessage-cascade' (VIDEO ~12s: iOS notification banners spring in and stack over a blurred backdrop; config: { notifications: [{sender, text}] (4-8), backgroundImage?, endCard }). Image URLs may be any public URL — the server localizes them. Spends a couple of credits.",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
config: z.object({}).passthrough().describe("the template config — MUST include config.template (one of the template ids above) plus that template's fields"),
|
|
177
|
+
},
|
|
178
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
179
|
+
}, wrap(async (a) => {
|
|
180
|
+
const r = await renderJob('templatead', { config: a.config }, 'MCP template ad');
|
|
181
|
+
if (r?.raw?.image || /\.png($|\?)/.test(r?.url || '')) { const img = r?.url ? await imageBlock(r.url) : null; return { content: [{ type: 'text', text: `Template ad ready: ${r.url} [job ${r.jobId}]` }, ...(img ? [img] : [])], structuredContent: r ?? undefined }; }
|
|
182
|
+
return okVideo(`Template ad ready: ${r.url}${r.model ? ` (${r.model})` : ''} [job ${r.jobId}]`, r);
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
server.registerTool('finish_video', {
|
|
186
|
+
description: "Post-process an EXISTING rendered video (its served mp4 URL) with the proven direct-response 'reviewer' finish and/or a film-grain pass — no AI model, ~30s, a couple of credits. pills=true composites a header pill (e.g. '10/10 would buy again'), a brand-accent sub-pill, and 3-4 green-check proof pills cascading in on the beat (YOU author the copy: header ≤40 chars, sub ≤34, each point ≤44 — concrete real benefits, never fabricated stats). grain=true applies a subtle camera-grain finish that makes photoreal AI renders look phone-shot ('less AI') — works alone or with pills. Returns a NEW video; the original is untouched.",
|
|
187
|
+
inputSchema: {
|
|
188
|
+
videoUrl: z.string().describe('the served URL of the video to finish (from a previous render/job)'),
|
|
189
|
+
header: z.string().optional().describe('header pill copy, ≤40 chars (required when pills is on)'),
|
|
190
|
+
sub: z.string().optional().describe('accent sub-pill copy, ≤34 chars (usually the product/brand)'),
|
|
191
|
+
points: z.array(z.string()).optional().describe('3-4 proof points, ≤44 chars each'),
|
|
192
|
+
accent: z.string().optional().describe('brand accent hex for the sub-pill'),
|
|
193
|
+
pills: z.boolean().optional().describe('default true — set false for a grain-only pass'),
|
|
194
|
+
grain: z.boolean().optional().describe('default false — anti-AI film-grain finish'),
|
|
195
|
+
},
|
|
196
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
197
|
+
}, wrap(async (a) => {
|
|
198
|
+
const r = await renderJob('videofinish', { videoUrl: a.videoUrl, header: a.header, sub: a.sub, points: a.points, accent: a.accent, pills: a.pills !== false, grain: !!a.grain }, 'MCP video finish');
|
|
199
|
+
return okVideo(`Finished video ready: ${r.url} [job ${r.jobId}]`, r);
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
server.registerTool('fix_beat', {
|
|
203
|
+
description: "Surgically re-render ONE time window (1.5-8s) of an existing rendered video and splice it back on the VIDEO TRACK ONLY — the rest of the video and ALL audio stay byte-identical. Use when one beat/shot is broken ('the shot at 8 seconds glitches') and a full re-render would waste the parts that worked; bills only the replacement clip's seconds (~1/3 of a full render). Do NOT pick a window covering spoken dialogue (a video-only splice under speech breaks lip-sync) — pass speechWindows to enforce this.",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
videoUrl: z.string().describe('the served URL of the master video to fix'),
|
|
206
|
+
startSeconds: z.number().describe('window start in seconds'),
|
|
207
|
+
endSeconds: z.number().describe('window end in seconds (window 1.5-8s)'),
|
|
208
|
+
prompt: z.string().describe('what the replacement footage should show — describe the shot, matching the master\'s style'),
|
|
209
|
+
refImage: z.string().optional().describe('optional product/style anchor image URL'),
|
|
210
|
+
speechWindows: z.array(z.array(z.number())).optional().describe('[[start,end],...] windows with spoken lines — the fix window must not overlap these'),
|
|
211
|
+
},
|
|
212
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
213
|
+
}, wrap(async (a) => {
|
|
214
|
+
const r = await renderJob('fixbeat', { videoUrl: a.videoUrl, startSeconds: a.startSeconds, endSeconds: a.endSeconds, prompt: a.prompt, refImage: a.refImage, speechWindows: a.speechWindows }, 'MCP fix beat');
|
|
215
|
+
return okVideo(`Fixed beat spliced in: ${r.url} [job ${r.jobId}]`, r);
|
|
216
|
+
}));
|
|
217
|
+
|
|
172
218
|
server.registerTool('generate_video', {
|
|
173
219
|
description: 'Render a RAW video clip from your own prompt and return its served mp4 URL. For finished brand ADS prefer render_ad (it runs the Studio quality pipeline — composited text, clean speech, end card, music); use this for raw/experimental clips or precise manual control. ONE generation = one continuous clip up to the model’s longest listed duration (seedance-2 goes to 15s single-pass with a full multi-beat arc — never assume a generic 8–10s cap); durationSeconds must be one of the model’s durations from hermoso_capabilities. Renders take 1–3 min. refImage anchors the opening frame; ttsScript adds a voiceover. Spends credits (Starter plan is video-blocked server-side).',
|
|
174
220
|
inputSchema: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Drive Hermoso — the AI ad studio — from any AI agent: MCP server, CLI, and Claude skills for researching winning ads and generating finished image & video ads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|