hermoso 0.1.33 → 0.1.34
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 +18 -5
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -167,11 +167,19 @@ async function renderJob(type, input, label) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// TWO SHAPES REACH THESE READERS, AND ONLY ONE OF THEM HAS `.raw` (fixed 2026-08-03). `renderJob` returns
|
|
171
|
+
// `{jobId, url, model, raw:<result>}`. `get_job` returns the JOB — `{...job, url}` — whose payload sits at
|
|
172
|
+
// `job.result` (or `job.result.data`), so a reader that only knew `.raw` returned nothing for it. That is not an
|
|
173
|
+
// edge case: the hosted transport answers any render outliving one request with `stillRendering`, which makes
|
|
174
|
+
// resume-via-get_job THE ORDINARY delivery path for a long render, and it was exactly the path that dropped the
|
|
175
|
+
// verdict from the text a human reads (it survived only in `structuredContent`, which nothing prints). One
|
|
176
|
+
// resolver, so a note added to either reader inherits both shapes.
|
|
177
|
+
const renderPayload = (r) => (r?.raw ?? jobResult(r) ?? null);
|
|
170
178
|
// A MODEL SUBSTITUTION IS NEVER SILENT (2026-08-01). The server sets `modelNote` on any render whose delivered model
|
|
171
179
|
// is not the model the caller named — an entry-belt coercion, or an in-flight content-filter/quota fallback — and the
|
|
172
180
|
// bill always follows the model that RAN. An agent that asked for one model and reads a one-line "ready" reply would
|
|
173
181
|
// otherwise never learn it got another, so the note rides the reply text every render tool prints.
|
|
174
|
-
const switchNote = (r) => { const n = r?.
|
|
182
|
+
const switchNote = (r) => { const n = renderPayload(r)?.modelNote || r?.modelNote; return n ? `\n⚠ ${n}` : ''; };
|
|
175
183
|
// THE VISION-QA READ-BACK (2026-08-03). The server WATCHES every generatively-rendered clip before it reports it
|
|
176
184
|
// done — free ffmpeg cadence/freeze probes plus ONE vision call over frames sampled at the hook, either side of
|
|
177
185
|
// each cut, and the tail — and puts what it saw on the result as `qaNote` (see vision-qa.mjs). An agent reading a
|
|
@@ -179,7 +187,7 @@ const switchNote = (r) => { const n = r?.raw?.modelNote || r?.modelNote; return
|
|
|
179
187
|
// painted its own end card, so the note rides the reply of EVERY video tool via okVideo. It REPORTS ONLY: nothing
|
|
180
188
|
// was re-rendered and nothing extra was charged, and a check it could not settle says "could not tell" rather
|
|
181
189
|
// than passing by default.
|
|
182
|
-
const qaLine = (r) => { const n = r?.
|
|
190
|
+
const qaLine = (r) => { const n = renderPayload(r)?.qaNote; return n ? `\n${n}` : ''; };
|
|
183
191
|
|
|
184
192
|
// Shared outputSchema fields for the job-based render tools (the renderJob result that becomes structuredContent).
|
|
185
193
|
// Every field is optional so validation can never fail on a sparse or still-rendering result.
|
|
@@ -4496,7 +4504,7 @@ export function registerTools(server) {
|
|
|
4496
4504
|
|
|
4497
4505
|
server.registerTool('generate_video', {
|
|
4498
4506
|
title: 'Generate video',
|
|
4499
|
-
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. Pass refVideo (a clip URL) to EDIT an existing video instead of generating from scratch — the omni engine transforms that clip per your prompt, inheriting the source clip’s canvas + length (aspectRatio/durationSeconds are ignored for an edit). Spends credits (Starter plan is video-blocked server-side).',
|
|
4507
|
+
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. AUDIO IS NOT FREE AND NOT OPTIONAL BY DEFAULT: a clip delivered with no audio of its own gets a music bed composed and CHARGED on top of the render (see musicMood and audio) — on a cheap short draft the bed can cost as much as the clip. Pass refVideo (a clip URL) to EDIT an existing video instead of generating from scratch — the omni engine transforms that clip per your prompt, inheriting the source clip’s canvas + length (aspectRatio/durationSeconds are ignored for an edit). Spends credits (Starter plan is video-blocked server-side).',
|
|
4500
4508
|
inputSchema: {
|
|
4501
4509
|
prompt: z.string().describe('the video prompt / shot description (for a refVideo edit, this is the transformation instruction)'),
|
|
4502
4510
|
refImage: z.string().optional().describe('local path or URL to anchor the first frame'),
|
|
@@ -4507,7 +4515,8 @@ export function registerTools(server) {
|
|
|
4507
4515
|
resolution: z.enum(['480p', '720p', '1080p', '4k']).optional().describe("'720p' default; '480p' = cheap fast draft pass, '1080p'/'4k' = premium final delivery (more credits)"),
|
|
4508
4516
|
ttsScript: z.string().optional().describe('voiceover script to speak'),
|
|
4509
4517
|
ttsVoice: z.string().optional().describe('voice name, e.g. Rachel / George'),
|
|
4510
|
-
musicMood: z.string().optional().describe('
|
|
4518
|
+
musicMood: z.string().optional().describe('WHICH mood the music bed is composed in (upbeat / calm / warm / epic / tense / playful / elegant / hype / chill / dramatic). It does NOT decide WHETHER there is one: a clip that comes back with no audio track — every model hermoso_capabilities lists as "silent", plus any audio model that returned mute — gets a bed composed and CHARGED automatically, at the flat per-track fee hermoso_capabilities reports as explainerMusicCredits, and omitting this field only means the mood defaults to "warm". Pass audio:false for a genuinely silent clip with no bed and no bed charge.'),
|
|
4519
|
+
audio: z.boolean().optional().describe('default true. false = render SILENT: no native model audio, no music bed, and no bed charge held or billed. This is the ONLY way to decline the automatic bed (see musicMood) — leave it alone for anything that should have sound, and do not combine it with ttsScript.'),
|
|
4511
4520
|
},
|
|
4512
4521
|
outputSchema: { ...JOB_OUT,
|
|
4513
4522
|
refused: z.string().optional().describe("set when NOTHING was rendered and nothing charged — currently 'duration_exceeds_single_clip'"),
|
|
@@ -4606,7 +4615,11 @@ export function registerTools(server) {
|
|
|
4606
4615
|
const j = await getJob(id);
|
|
4607
4616
|
const res = jobResult(j);
|
|
4608
4617
|
const url = abs(res?.video || res?.image || res?.url);
|
|
4609
|
-
|
|
4618
|
+
// A RESUMED RENDER READS THE SAME SENTENCE AS AN INLINE ONE. `stillRendering` makes this tool the ordinary way a
|
|
4619
|
+
// long render is received, so every disclosure the direct reply carries has to be here too — the model
|
|
4620
|
+
// substitution (`switchNote`) explicitly, the vision-QA verdict via `okVideo`'s own `qaLine`. Both resolve the
|
|
4621
|
+
// payload through `renderPayload`, which is why handing them the JOB works at all.
|
|
4622
|
+
const text = `Job ${id}: ${j.status}${j.progress ? ` (${Math.round(j.progress * 100)}%)` : ''}${url ? ` → ${url}` : ''}${channelOutcomeLine(res)}${j.error ? ` — ${j.error}` : ''}${j.status === 'done' ? switchNote(j) : ''}`;
|
|
4610
4623
|
if (j.status === 'done' && res?.video) return okVideo(text, { ...j, url }); // resumed video → same inline poster as a direct return
|
|
4611
4624
|
if (j.status === 'done' && res?.image) { const img = await imageBlock(url); return { content: [{ type: 'text', text }, ...(img ? [img] : [])], structuredContent: { ...j, url } }; }
|
|
4612
4625
|
return ok(text, { ...j, url });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
5
|
"description": "Generate finished VIDEO ADS, image ads and UGC avatar ads for any brand with AI — spy on competitor ads across the Meta, Google and LinkedIn ad libraries plus TikTok/Instagram/YouTube organic — then publish to Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn and Pinterest and build & manage the ad campaigns behind them on Meta, Google Ads, LinkedIn, Pinterest, Microsoft Advertising and ChatGPT Ads. MCP server (262 tools), CLI and Claude skills for Hermoso, the AI ad studio: brand onboarding, 30+ image/video models, finished-ad pipeline (script, voiceover, music, brand end card), ad scoring and competitor teardowns.",
|
|
6
6
|
"type": "module",
|