hermoso 0.1.390 → 0.1.392
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 +9 -7
- package/package.json +1 -1
package/mcp/tools.mjs
CHANGED
|
@@ -17866,6 +17866,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17866
17866
|
concept: z.string().optional().describe('the one-line creative concept'),
|
|
17867
17867
|
format_note: z.string().optional().describe("the plan's own read-back when it chose an IMAGE on an open format because the balance could not cover the default video — states the video's real credit price"),
|
|
17868
17868
|
budget_pick: z.any().optional().describe('{format, reason, videoCredits, balance, seconds} when format_note is set'),
|
|
17869
|
+
for_fun: z.boolean().optional().describe('true when the ask is personal or playful with nothing to sell: the plan is a plain picture or clip (no CTA, no ad copy) and nothing should be added to it'),
|
|
17869
17870
|
concept_count: z.number().optional().describe('how many distinct concepts the brief asked for; the plan builds out the first and sets variants to it'),
|
|
17870
17871
|
recipe: z.string().optional().describe('the resolved recipe id'),
|
|
17871
17872
|
recipe_label: z.string().optional().describe('the resolved recipe display name'),
|
|
@@ -17924,7 +17925,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
17924
17925
|
+ (_askedLen && _askedLen !== _len ? ` — you asked for ${_askedLen}s, which is outside the supported 4–180s range, so it was clamped to ${_len}s` : '')
|
|
17925
17926
|
+ (_len && _planned && Math.abs(_planned - _len) > 1 ? ` — ⚠ this does NOT match the ${_len}s you asked for; tell the user before rendering, or re-plan` : '');
|
|
17926
17927
|
}
|
|
17927
|
-
const text = `Concept (${c.format}${c.recipe_label ? ' · ' + c.recipe_label : ''}): "${c.concept}"${c.format_note ? '\n' + c.format_note : ''}${c.presence_note ? '\n' + c.presence_note : ''}${c.concept_count > 1 ? `\nYou asked for ${c.concept_count} concepts: the concept line names each; this plan builds out the first (variants = ${c.variants || c.concept_count}). Call plan_ad again with a different angle for the others, or render_ad with variants for takes of this one.` : ''}${refWatchedLine(c.reference_watched)}${_lenLine}${_hookLine}\nHeadline: ${c.copy?.[0]?.headline || ''}\nRender model: ${c.format === 'video' ? c.vmodel : c.imodel || '—'}. Next: ${c.format === 'video' ? 'call render_ad with THIS ENTIRE creative object (Studio quality pipeline; a storyboard that fits ONE clip of the render model renders as a single continuous pass, a longer plan renders as stitched acts automatically — never hand-stitch)' : 'generate_image with the image_concept.prompt'}.`;
|
|
17928
|
+
const text = `Concept (${c.format}${c.recipe_label ? ' · ' + c.recipe_label : ''}): "${c.concept}"${c.format_note ? '\n' + c.format_note : ''}${c.presence_note ? '\n' + c.presence_note : ''}${c.concept_count > 1 ? `\nYou asked for ${c.concept_count} concepts: the concept line names each; this plan builds out the first (variants = ${c.variants || c.concept_count}). Call plan_ad again with a different angle for the others, or render_ad with variants for takes of this one.` : ''}${refWatchedLine(c.reference_watched)}${_lenLine}${_hookLine}${c.for_fun ? '\nJust for fun: a plain picture of what they asked for, with no headline, button or ad copy. Render it as it is and do not call it an ad.' : `\nHeadline: ${c.copy?.[0]?.headline || ''}`}\nRender model: ${c.format === 'video' ? c.vmodel : c.imodel || '—'}. Next: ${c.format === 'video' ? 'call render_ad with THIS ENTIRE creative object (Studio quality pipeline; a storyboard that fits ONE clip of the render model renders as a single continuous pass, a longer plan renders as stitched acts automatically — never hand-stitch)' : 'generate_image with the image_concept.prompt'}.`;
|
|
17928
17929
|
return ok(text, c);
|
|
17929
17930
|
}));
|
|
17930
17931
|
|
|
@@ -19445,15 +19446,16 @@ function memoryNoteVerdict(text) {
|
|
|
19445
19446
|
})));
|
|
19446
19447
|
server.registerTool('update_brand', {
|
|
19447
19448
|
title: 'Update profile fields',
|
|
19448
|
-
description: 'Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal,
|
|
19449
|
+
description: 'Patch SPECIFIC fields of the active profile (name, domain, sells, summary, category, audience, positioning, voice, style, goal, logos, and how the brand and product names are pronounced) WITHOUT overwriting the rest — a read-modify-write on the saved brand. Use for “change our voice to playful”, “we sell to dentists now”. To onboard a brand from scratch, use draft_brand. If no brand is saved yet and you only INFERRED one from what the user is making, ask them to confirm it is their brand before saving it (they may be working for a client or just trying things). Only pass the fields you’re changing.',
|
|
19449
19450
|
inputSchema: { ...STORE_BRAND,
|
|
19450
19451
|
name: z.string().optional(), domain: z.string().optional().describe('website domain'), sells: z.string().optional().describe('what the brand sells'),
|
|
19451
19452
|
summary: z.string().optional().describe('one-line description'), category: z.string().optional(), audience: z.string().optional(),
|
|
19452
19453
|
positioning: z.string().optional(), voice: z.string().optional().describe('brand voice/tone'), style: z.string().optional().describe('visual style — palette, typography, aesthetic'), goal: z.string().optional().describe('current marketing goal'),
|
|
19453
19454
|
pronounce: z.string().optional().describe('how the brand NAME is said aloud, as a simple respelling with the stressed syllable in capitals (e.g. "KOH-dee-ak"). Videos use it as a delivery note beside the spoken line; set it when a render mispronounced the name.'),
|
|
19454
19455
|
pronunciations: z.record(z.string()).optional().describe('how PRODUCT names are said aloud, e.g. {"Power Cakes": "POW-er cakes"}. Merged into the saved ones; an empty string removes one.'),
|
|
19455
|
-
logo: z.string().optional()
|
|
19456
|
-
logoLight: z.string().optional().describe('
|
|
19456
|
+
logo: z.string().optional(),
|
|
19457
|
+
logoLight: z.string().optional().describe('for dark pictures; "none" removes'),
|
|
19458
|
+
logoDark: z.string().optional().describe('for light pictures'),
|
|
19457
19459
|
},
|
|
19458
19460
|
outputSchema: { ok: z.boolean().optional(), updated: z.array(z.string()).optional(), brand: z.any().optional() },
|
|
19459
19461
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
@@ -19469,11 +19471,11 @@ function memoryNoteVerdict(text) {
|
|
|
19469
19471
|
}
|
|
19470
19472
|
// THE LOGO FILES (2026-10-06): the logo, and the optional light version laid on when the original would not read on a
|
|
19471
19473
|
// dark or busy background. A local path is uploaded first (toRef); only a raster file is kept (an SVG cannot be laid on).
|
|
19472
|
-
for (const k of ['logo', 'logoLight']) {
|
|
19474
|
+
for (const k of ['logo', 'logoLight', 'logoDark']) {
|
|
19473
19475
|
if (a[k] == null || !String(a[k]).trim()) continue;
|
|
19474
|
-
if (k
|
|
19476
|
+
if (k !== 'logo' && /^(none|remove|off)$/i.test(String(a[k]).trim())) { patch[k] = ''; continue; }
|
|
19475
19477
|
const ref = await toRef(String(a[k]).trim());
|
|
19476
|
-
if (!ref || !/^(https?:\/\/|data:image\/(png|jpe?g|webp|gif);base64,)/i.test(ref) || /\.svg(\?|$)/i.test(ref)) return { content: [{ type: 'text', text: `The ${k === 'logo' ? 'logo' : 'light logo'} must be a PNG, JPEG or WebP image (a URL or a local file). Nothing was changed.` }], isError: true };
|
|
19478
|
+
if (!ref || !/^(https?:\/\/|data:image\/(png|jpe?g|webp|gif);base64,)/i.test(ref) || /\.svg(\?|$)/i.test(ref)) return { content: [{ type: 'text', text: `The ${k === 'logo' ? 'logo' : k === 'logoLight' ? 'light logo' : 'dark logo'} must be a PNG, JPEG or WebP image (a URL or a local file). Nothing was changed.` }], isError: true };
|
|
19477
19479
|
patch[k] = ref;
|
|
19478
19480
|
}
|
|
19479
19481
|
if (!Object.keys(patch).length) return { content: [{ type: 'text', text: 'Pass at least one brand field to change.' }], isError: true };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.392",
|
|
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",
|