hermoso 0.1.401 → 0.1.403

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.
Files changed (2) hide show
  1. package/mcp/tools.mjs +14 -6
  2. package/package.json +1 -1
package/mcp/tools.mjs CHANGED
@@ -18068,6 +18068,8 @@ function buildTools(rawServer, opts = {}, sink = null) {
18068
18068
  outputSchema: {
18069
18069
  image: z.string().optional().describe('the served absolute URL of the edited image'),
18070
18070
  model: z.string().optional().describe('the model label that rendered it'),
18071
+ editUnchanged: z.boolean().optional().describe('true when the returned picture matches the one sent: the edit changed nothing (productNote says what to try)'),
18072
+ productNote: z.string().optional(),
18071
18073
  },
18072
18074
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
18073
18075
  }, wrap(async ({ image, instruction, removal, mask, dryRun, fixLabel }) => {
@@ -18077,7 +18079,9 @@ function buildTools(rawServer, opts = {}, sink = null) {
18077
18079
  if (dryRun) { const d = await apiPost('/api/static/edit', { image: src, instruction, ...(removal === true ? { removal: true } : {}), ...(maskRef ? { mask: maskRef } : {}), ...fl, dryRun: true }); return ok(quoteText(d && d.quote, 'This image edit (a change to the existing image, not a new render)'), {}); }
18078
18080
  const d = await apiPost('/api/static/edit', { image: src, instruction, ...(removal === true ? { removal: true } : {}), ...(maskRef ? { mask: maskRef } : {}), ...fl });
18079
18081
  const img = await imageBlock(abs(d.image));
18080
- return { content: [{ type: 'text', text: `Edited image: ${abs(d.image)}${d.model ? ` (${d.model})` : ''}${d.productNote ? `\n${d.productNote}` : ''}` }, ...(img ? [img] : [])], structuredContent: { ...d, image: abs(d.image) } };
18082
+ // editUnchanged (2026-10-06): the server compared the result with the picture sent and nothing changed, so this
18083
+ // never says "Edited image" over it; productNote opens with the plain sentence and what to try instead.
18084
+ return { content: [{ type: 'text', text: `${d.editUnchanged ? 'The edit came back UNCHANGED, nothing was fixed. Returned image' : 'Edited image'}: ${abs(d.image)}${d.model ? ` (${d.model})` : ''}${d.productNote ? `\n${d.productNote}` : ''}` }, ...(img ? [img] : [])], structuredContent: { ...d, image: abs(d.image) } };
18081
18085
  }));
18082
18086
  server.registerTool('headline_variants', {
18083
18087
  title: 'Headline variants of a static ad',
@@ -21156,20 +21160,24 @@ function memoryNoteVerdict(text) {
21156
21160
 
21157
21161
  server.registerTool('dub_video', {
21158
21162
  title: 'Dub video',
21159
- description: "Localize a finished video into another language WITHOUT re-rendering it: the spoken track is transcribed, translated, re-voiced and lip-synced back onto the SAME footage, so the visuals, timing and edit are untouched. Just pass the video and the language — the script is read off the source automatically (pass `script` only to override what it heard). Paid; returns the served URL of the localized video.",
21163
+ description: "Localize a finished video into another language WITHOUT re-rendering it: each spoken line is said where it was said, in a native voice, lip-synced, background kept. Pass the video and the language; the script is read off the source (`script` only overrides it). Paid by length; dryRun quotes. Returns the localized video URL.",
21160
21164
  inputSchema: {
21161
21165
  video: z.string().describe('the source video URL'),
21162
21166
  language: z.string().describe("target language, e.g. 'Spanish', 'de', 'French (Canada)'"),
21163
21167
  script: z.string().optional().describe('OPTIONAL override for the original spoken words. Leave this out — the source video is transcribed automatically. Only pass it when you already know the exact script and the auto-transcript got it wrong.'),
21164
- voice: z.string().optional().describe("optional target voice preset, e.g. 'Aria' (warm female) or 'George' (confident male). Defaults to a voice matching the source speaker's register."),
21168
+ voice: z.string().optional().describe("optional, e.g. 'Aria' (woman) or 'George' (man): a native voice of that sex speaks. Default: the source speaker's sex."),
21169
+ dryRun: z.boolean().optional().describe('quote only'),
21165
21170
  },
21166
21171
  outputSchema: { ...JOB_OUT },
21167
21172
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
21168
- }, wrap(async ({ video, language, script, voice }) => {
21173
+ }, wrap(async ({ video, language, script, voice, dryRun }) => {
21169
21174
  // Forward `script` ONLY when the caller actually supplied one. Sending '' used to hit the worker's
21170
21175
  // empty-script guard, so the documented {video, language} call could never succeed.
21171
- const r = await renderJob('dub', { video, language, ...(String(script || '').trim() ? { script } : {}), ...(voice ? { voice } : {}) }, `Dub → ${language}`);
21172
- return okVideo(`Localized video (${language}): ${r.url}`, r);
21176
+ const _in = { video, language, ...(String(script || '').trim() ? { script } : {}), ...(voice ? { voice } : {}) };
21177
+ if (dryRun) { const q = await quoteJob('dub', _in); return ok(quoteText(q, `This dub into ${language}`), { raw: q }); }
21178
+ const r = await renderJob('dub', _in, `Dub → ${language}`);
21179
+ const p = renderPayload(r) || {};
21180
+ return okVideo(`Localized video (${language}): ${r.url}${p.note ? `\n${p.note}` : ''}`, r);
21173
21181
  }));
21174
21182
 
21175
21183
  server.registerTool('change_voice', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.401",
3
+ "version": "0.1.403",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
5
  "description": "Marketing on autopilot, run from your own AI agent. 906 tools. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on every plan; credits are only for generating creative and for Ad Spy research. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads, ChatGPT Ads and AppLovin Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and Telegram. INSTAGRAM DM AUTOMATIONS: a keyword comment, a DM, a story reply or an ig.me link gets an automatic private reply from the brand. 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",